Logo Search packages:      
Sourcecode: kdenetwork version File versions

events.c

/* $Id: events.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>
 *
 *  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/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "libgadu-config.h"

#include <errno.h>
#ifdef __GG_LIBGADU_HAVE_PTHREAD
#  include <pthread.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#ifdef __GG_LIBGADU_HAVE_OPENSSL
#  include <openssl/err.h>
#  include <openssl/x509.h>
#endif

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

/*
 * gg_event_free()
 *
 * zwalnia pamięć zajmowaną przez informację o zdarzeniu.
 *
 *  - e - wskaźnik do informacji o zdarzeniu
 */
void gg_event_free(struct gg_event *e)
{
      gg_debug(GG_DEBUG_FUNCTION, "** gg_event_free(%p);\n", e);
                  
      if (!e)
            return;
      
      switch (e->type) {
            case GG_EVENT_MSG:
                  free(e->event.msg.message);
                  free(e->event.msg.formats);
                  free(e->event.msg.recipients);
                  break;
      
            case GG_EVENT_NOTIFY:
                  free(e->event.notify);
                  break;
      
            case GG_EVENT_NOTIFY60:
            {
                  int i;

                  for (i = 0; e->event.notify60[i].uin; i++)
                        free(e->event.notify60[i].descr);
            
                  free(e->event.notify60);

                  break;
            }

            case GG_EVENT_STATUS60:
                  free(e->event.status60.descr);
                  break;
      
            case GG_EVENT_STATUS:
                  free(e->event.status.descr);
                  break;

            case GG_EVENT_NOTIFY_DESCR:
                  free(e->event.notify_descr.notify);
                  free(e->event.notify_descr.descr);
                  break;

            case GG_EVENT_DCC_VOICE_DATA:
                  free(e->event.dcc_voice_data.data);
                  break;

            case GG_EVENT_PUBDIR50_SEARCH_REPLY:
            case GG_EVENT_PUBDIR50_READ:
            case GG_EVENT_PUBDIR50_WRITE:
                  gg_pubdir50_free(e->event.pubdir50);
                  break;

            case GG_EVENT_USERLIST:
                  free(e->event.userlist.reply);
                  break;
      
            case GG_EVENT_IMAGE_REPLY:
                  free(e->event.image_reply.filename);
                  free(e->event.image_reply.image);
                  break;
      }

      free(e);
}

/*
 * gg_image_queue_remove()
 *
 * usuwa z kolejki dany wpis.
 *
 *  - s - sesja
 *  - q - kolejka
 *  - freeq - czy zwolnić kolejkę
 *
 * 0/-1
 */
int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq)
{
      if (!s || !q) {
            errno = EINVAL;
            return -1;
      }

      if (s->images == q)
            s->images = q->next;
      else {
            struct gg_image_queue *qq;

            for (qq = s->images; qq; qq = qq->next) {
                  if (qq->next == q) {
                        qq->next = q->next;
                        break;
                  }
            }
      }

      if (freeq) {
            free(q->image);
            free(q->filename);
            free(q);
      }

      return 0;
}

/*
 * gg_image_queue_parse() // funkcja wewnętrzna
 *
 * parsuje przychodzący pakiet z obrazkiem.
 *
 *  - e - opis zdarzenia
 *  - 
 */
static void gg_image_queue_parse(struct gg_event *e, char *p, int len, struct gg_session *sess, uin_t sender)
{
      struct gg_msg_image_reply *i = (void*) p;
      struct gg_image_queue *q, *qq;

      if (!p || !sess || !e)
            return;

      /* znajdź dany obrazek w kolejce danej sesji */
      
      for (qq = sess->images, q = NULL; qq; qq = qq->next) {
            if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) {
                  q = qq;
                  break;
            }
      }

      if (!q) {
            gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32);
            return;
      }

      if (p[0] == 0x05) {
            int i, ok = 0;
            
            q->done = 0;

            len -= sizeof(struct gg_msg_image_reply);
            p += sizeof(struct gg_msg_image_reply);

            /* sprawdź, czy mamy tekst zakończony \0 */

            for (i = 0; i < len; i++) {
                  if (!p[i]) {
                        ok = 1;
                        break;
                  }
            }

            if (!ok) {
                  gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender);
                  return;
            }

            if (!(q->filename = strdup(p))) {
                  gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n");
                  return;
            }

            len -= strlen(p) + 1;
            p += strlen(p) + 1;
      } else {
            len -= sizeof(struct gg_msg_image_reply);
            p += sizeof(struct gg_msg_image_reply);
      }

      if (q->done + len > q->size)
            len = q->size - q->done;
            
      memcpy(q->image + q->done, p, len);
      q->done += len;

      /* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */

      if (q->done >= q->size) {
            e->type = GG_EVENT_IMAGE_REPLY;
            e->event.image_reply.sender = sender;
            e->event.image_reply.size = q->size;
            e->event.image_reply.crc32 = q->crc32;
            e->event.image_reply.filename = q->filename;
            e->event.image_reply.image = q->image;

            gg_image_queue_remove(sess, q, 0);

            free(q);
      }
}

/*
 * gg_handle_recv_msg() // funkcja wewnętrzna
 *
 * obsługuje pakiet z przychodzącą wiadomością, rozbijając go na dodatkowe
 * struktury (konferencje, kolorki) w razie potrzeby.
 *
 *  - h - nagłówek pakietu
 *  - e - opis zdarzenia
 *
 * 0, -1.
 */
static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess)
{
      struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header));
      char *p, *packet_end = (char*) r + h->length;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e);

      if (!r->seq && !r->msgclass) {
            gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n");
            e->type = GG_EVENT_NONE;
            return 0;
      }
      
      for (p = (char*) r + sizeof(*r); *p; p++) {
            if (*p == 0x02 && p == packet_end - 1) {
                  gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n");
                  break;
            }
            if (p >= packet_end) {
                  gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n");
                  goto malformed;
            }
      }
      
      p++;

      /* przeanalizuj dodatkowe opcje */
      while (p < packet_end) {
            switch (*p) {
                  case 0x01:        /* konferencja */
                  {
                        struct gg_msg_recipients *m = (void*) p;
                        uint32_t i, count;
                  
                        p += sizeof(*m);
                  
                        if (p > packet_end) {
                              gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1)\n");
                              goto malformed;
                        }

                        count = gg_fix32(m->count);

                        if (p + count * sizeof(uin_t) > packet_end) {
                              gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1.5)\n");
                              goto malformed;
                        }
                  
                        if (!(e->event.msg.recipients = (void*) malloc(count * sizeof(uin_t)))) {
                              gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for recipients data\n");
                              errno = ENOMEM;
                              goto fail;
                        }
                  
                        for (i = 0; i < count; i++, p += sizeof(uin_t))
                              e->event.msg.recipients[i] = gg_fix32(*((uint32_t*) p));
                        
                        e->event.msg.recipients_count = count;
                        
                        break;
                  }

                  case 0x02:        /* richtext */
                  {
                        unsigned short len;
                        char *buf;
                  
                        if (p + 3 > packet_end) {
                              gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (2)\n");
                              goto malformed;
                        }

                        len = gg_fix16(*((unsigned short*) (p + 1)));

                        if (!(buf = malloc(len))) {
                              gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for richtext data\n");
                              errno = ENOMEM;
                              goto fail;
                        }

                        p += 3;

                        if (p + len > packet_end) {
                              gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n");
                              free(buf);
                              goto malformed;
                        }
                        
                        memcpy(buf, p, len);

                        e->event.msg.formats = buf;
                        e->event.msg.formats_length = len;

                        p += len;

                        break;
                  }

                  case 0x04:        /* image_request */
                  {
                        struct gg_msg_image_request *i = (void*) p;

                        if (p + sizeof(*i) > packet_end) {
                              gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n");
                              goto malformed;
                        }

                        e->event.image_request.sender = gg_fix32(r->sender);
                        e->event.image_request.size = gg_fix32(i->size);
                        e->event.image_request.crc32 = gg_fix32(i->crc32);

                        e->type = GG_EVENT_IMAGE_REQUEST;

                        return 0;
                  }

                  case 0x05:        /* image_reply */
                  case 0x06:
                  {
                        if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) {
                              gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n");
                              goto malformed;
                        }

                        gg_image_queue_parse(e, p, (int)(packet_end - p), sess, gg_fix32(r->sender));

                        return 0;
                  }

                  default:
                  {
                        gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p);
                        p = packet_end;
                  }
            }
      }

      e->type = GG_EVENT_MSG;
      e->event.msg.msgclass = gg_fix32(r->msgclass);
      e->event.msg.sender = gg_fix32(r->sender);
      e->event.msg.time = gg_fix32(r->time);
      e->event.msg.message = strdup((char*) r + sizeof(*r));

      return 0;

malformed:
      e->type = GG_EVENT_NONE;

      free(e->event.msg.recipients);
      free(e->event.msg.formats);

      return 0;

fail:
      free(e->event.msg.recipients);
      free(e->event.msg.formats);
      return -1;
}

/*
 * gg_watch_fd_connected() // funkcja wewnętrzna
 *
 * patrzy na gniazdo, odbiera pakiet i wypełnia strukturę zdarzenia.
 *
 *  - sess - struktura opisująca sesję
 *  - e - opis zdarzenia
 *
 * 0, -1.
 */
static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e)
{
      struct gg_header *h = NULL;
      char *p;

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

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

      if (!(h = gg_recv_packet(sess))) {
            gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno));
            goto fail;
      }

      p = (char*) h + sizeof(struct gg_header);
      
      switch (h->type) {
            case GG_RECV_MSG:
            {
                  if (h->length >= sizeof(struct gg_recv_msg))
                        if (gg_handle_recv_msg(h, e, sess))
                              goto fail;
                  
                  break;
            }

            case GG_NOTIFY_REPLY:
            {
                  struct gg_notify_reply *n = (void*) p;
                  int count, i;
                  char *tmp;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");

                  if (h->length < sizeof(*n)) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n");
                        errno = EINVAL;
                        goto fail;
                  }

                  if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status == GG_STATUS_NOT_AVAIL_DESCR) || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) {
                        e->type = GG_EVENT_NOTIFY_DESCR;
                        
                        if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
                              errno = ENOMEM;
                              goto fail;
                        }
                        e->event.notify_descr.notify[1].uin = 0;
                        memcpy(e->event.notify_descr.notify, p, sizeof(*n));
                        e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin);
                        e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status);
                        e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port);

                        count = h->length - sizeof(*n);
                        if (!(tmp = malloc(count + 1))) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
                              errno = ENOMEM;
                              goto fail;
                        }
                        memcpy(tmp, p + sizeof(*n), count);
                        tmp[count] = 0;
                        e->event.notify_descr.descr = tmp;
                        
                  } else {
                        e->type = GG_EVENT_NOTIFY;
                        
                        if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
                              errno = ENOMEM;
                              goto fail;
                        }
                        
                        memcpy(e->event.notify, p, h->length);
                        count = h->length / sizeof(*n);
                        e->event.notify[count].uin = 0;
                        
                        for (i = 0; i < count; i++) {
                              e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin);
                              e->event.notify[i].status = gg_fix32(e->event.notify[i].status);
                              e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port);          
                        }
                  }

                  break;
            }

            case GG_STATUS:
            {
                  struct gg_status *s = (void*) p;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");

                  if (h->length >= sizeof(*s)) {
                        e->type = GG_EVENT_STATUS;
                        memcpy(&e->event.status, p, sizeof(*s));
                        e->event.status.uin = gg_fix32(e->event.status.uin);
                        e->event.status.status = gg_fix32(e->event.status.status);
                        if (h->length > sizeof(*s)) {
                              int len = h->length - sizeof(*s);
                              char *buf = malloc(len + 1);
                              if (buf) {
                                    memcpy(buf, p + sizeof(*s), len);
                                    buf[len] = 0;
                              }
                              e->event.status.descr = buf;
                        } else
                              e->event.status.descr = NULL;
                  }

                  break;
            }

            case GG_NOTIFY_REPLY60:
            {
                  struct gg_notify_reply60 *n = (void*) p;
                  unsigned int length = h->length, i = 0;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");

                  e->type = GG_EVENT_NOTIFY60;
                  e->event.notify60 = malloc(sizeof(*e->event.notify60));

                  if (!e->event.notify60) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
                        goto fail;
                  }

                  e->event.notify60[0].uin = 0;
                  
                  while (length >= sizeof(struct gg_notify_reply60)) {
                        uin_t uin = gg_fix32(n->uin);
                        char *tmp;

                        e->event.notify60[i].uin = uin & 0x00ffffff;
                        e->event.notify60[i].status = n->status;
                        e->event.notify60[i].remote_ip = n->remote_ip;
                        e->event.notify60[i].remote_port = gg_fix16(n->remote_port);
                        e->event.notify60[i].version = n->version;
                        e->event.notify60[i].image_size = n->image_size;
                        e->event.notify60[i].descr = NULL;
                        e->event.notify60[i].time = 0;

                        if (GG_S_D(n->status)) {
                              unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60));

                              if (descr_len < length) {
                                    if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) {
                                          gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
                                          goto fail;
                                    }

                                    memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len);
                                    e->event.notify60[i].descr[descr_len] = 0;

                                    /* XXX czas */
                              }
                              
                              length -= sizeof(struct gg_notify_reply60) + descr_len + 1;
                              n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1);
                        } else {
                              length -= sizeof(struct gg_notify_reply60);
                              n = (void*) ((char*) n + sizeof(struct gg_notify_reply60));
                        }

                        if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
                              free(e->event.notify60);
                              goto fail;
                        }

                        e->event.notify60 = (void*) tmp;
                        e->event.notify60[++i].uin = 0;
                  }

                  break;
            }
            
            case GG_STATUS60:
            {
                  struct gg_status60 *s = (void*) p;
                  uint32_t uin;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");

                  if (h->length < sizeof(*s))
                        break;

                  uin = gg_fix32(s->uin);

                  e->type = GG_EVENT_STATUS60;
                  e->event.status60.uin = uin & 0x00ffffff;
                  e->event.status60.status = s->status;
                  e->event.status60.remote_ip = s->remote_ip;
                  e->event.status60.remote_port = gg_fix16(s->remote_port);
                  e->event.status60.version = s->version;
                  e->event.status60.image_size = s->image_size;
                  e->event.status60.descr = NULL;
                  e->event.status60.time = 0;

                  if (uin & 0x40000000)
                        e->event.status60.version |= GG_HAS_AUDIO_MASK;

                  if (h->length > sizeof(*s)) {
                        int len = h->length - sizeof(*s);
                        char *buf = malloc(len + 1);

                        if (buf) {
                              memcpy(buf, (char*) p + sizeof(*s), len);
                              buf[len] = 0;
                        }

                        e->event.status60.descr = buf;

                        if (len > 4 && p[h->length - 5] == 0)
                              e->event.status60.time = *((int*) (p + h->length - 4));
                  }

                  break;
            }

            case GG_SEND_MSG_ACK:
            {
                  struct gg_send_msg_ack *s = (void*) p;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n");

                  if (h->length < sizeof(*s))
                        break;

                  e->type = GG_EVENT_ACK;
                  e->event.ack.status = gg_fix32(s->status);
                  e->event.ack.recipient = gg_fix32(s->recipient);
                  e->event.ack.seq = gg_fix32(s->seq);

                  break;
            }

            case GG_PONG: 
            {
                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n");

                  e->type = GG_EVENT_PONG;
                  sess->last_pong = time(NULL);

                  break;
            }

            case GG_DISCONNECTING:
            {
                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n");
                  e->type = GG_EVENT_DISCONNECT;
                  break;
            }

            case GG_PUBDIR50_REPLY:
            {
                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n");
                  if (gg_pubdir50_handle_reply(e, p, h->length) == -1)
                        goto fail;
                  break;
            }

            case GG_USERLIST_REPLY:
            {
                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n");

                  if (h->length < 1)
                        break;

                  /* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko
                   * gdy otrzymano wszystkie odpowiedzi */
                  if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) {
                        if (--sess->userlist_blocks)
                              break;

                        p[0] = GG_USERLIST_PUT_REPLY;
                  }

                  if (h->length > 1) {
                        char *tmp;
                        int len = (sess->userlist_reply) ? strlen(sess->userlist_reply) : 0;
                        
                        gg_debug(GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len);
                        
                        if (!(tmp = realloc(sess->userlist_reply, len + h->length))) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n");
                              free(sess->userlist_reply);
                              sess->userlist_reply = NULL;
                              goto fail;
                        }

                        sess->userlist_reply = tmp;
                        sess->userlist_reply[len + h->length - 1] = 0;
                        memcpy(sess->userlist_reply + len, p + 1, h->length - 1);
                  }

                  if (p[0] == GG_USERLIST_GET_MORE_REPLY)
                        break;

                  e->type = GG_EVENT_USERLIST;
                  e->event.userlist.type = p[0];
                  e->event.userlist.reply = sess->userlist_reply;
                  sess->userlist_reply = NULL;

                  break;
            }

            default:
                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type);
      }
      
      free(h);
      return 0;

fail:
      free(h);
      return -1;
}

/*
 * gg_watch_fd()
 *
 * funkcja, którą należy wywołać, gdy coś się stanie z obserwowanym
 * deskryptorem. zwraca klientowi informację o tym, co się dzieje.
 *
 *  - sess - opis sesji
 *
 * wskaźnik do struktury gg_event, którą trzeba zwolnić później
 * za pomocą gg_event_free(). jesli rodzaj zdarzenia jest równy
 * GG_EVENT_NONE, należy je zignorować. jeśli zwróciło NULL,
 * stało się coś niedobrego -- albo zabrakło pamięci albo zerwało
 * połączenie.
 */
struct gg_event *gg_watch_fd(struct gg_session *sess)
{
      struct gg_event *e;
      int res = 0;
      int port = 0;

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

      if (!(e = (void*) calloc(1, sizeof(*e)))) {
            gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n");
            return NULL;
      }

      e->type = GG_EVENT_NONE;

      switch (sess->state) {
            case GG_STATE_RESOLVING:
            {
                  struct in_addr addr;
                  int failed = 0;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n");

                  if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n");
                        failed = 1;
                  }
                  
                  close(sess->fd);
                  sess->fd = -1;

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

                  if (failed)
                        goto fail_resolving;

                  /* jeśli jesteśmy w resolverze i mamy ustawiony port
                   * proxy, znaczy, że resolvowaliśmy proxy. zatem
                   * wpiszmy jego adres. */
                  if (sess->proxy_port)
                        sess->proxy_addr = addr.s_addr;

                  /* zapiszmy sobie adres huba i adres serwera (do
                   * bezpośredniego połączenia, jeśli hub leży)
                   * z resolvera. */
                  if (sess->proxy_addr && sess->proxy_port)
                        port = sess->proxy_port;
                  else {
                        sess->server_addr = sess->hub_addr = addr.s_addr;
                        port = GG_APPMSG_PORT;
                  }

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port);
                  
                  /* łączymy się albo z hubem, albo z proxy, zależnie
                   * od tego, co resolvowaliśmy. */
                  if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) {
                        /* jeśli w trybie asynchronicznym gg_connect()
                         * zwróci błąd, nie ma sensu próbować dalej. */
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno));
                        goto fail_connecting;
                  }

                  /* jeśli podano serwer i łączmy się przez proxy,
                   * jest to bezpośrednie połączenie, inaczej jest
                   * do huba. */
                  sess->state = (sess->proxy_addr && sess->proxy_port && sess->server_addr) ? GG_STATE_CONNECTING_GG : GG_STATE_CONNECTING_HUB;
                  sess->check = GG_CHECK_WRITE;
                  sess->timeout = GG_DEFAULT_TIMEOUT;

                  break;
            }

            case GG_STATE_CONNECTING_HUB:
            {
                  char buf[1024], *client, *auth;
                  int res = 0, res_size = sizeof(res);
                  const char *host, *appmsg;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n");

                  /* jeśli asynchroniczne, sprawdzamy, czy nie wystąpił
                   * przypadkiem jakiś błąd. */
                  if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
                        /* no tak, nie udało się połączyć z proxy. nawet
                         * nie próbujemy dalej. */
                        if (sess->proxy_addr && sess->proxy_port) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res));
                              goto fail_connecting;
                        }
                              
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to hub failed (errno=%d, %s), trying direct connection\n", res, strerror(res));
                        close(sess->fd);

                        if ((sess->fd = gg_connect(&sess->hub_addr, GG_DEFAULT_PORT, sess->async)) == -1) {
                              /* przy asynchronicznych, gg_connect()
                               * zwraca -1 przy błędach socket(),
                               * ioctl(), braku routingu itd. dlatego
                               * nawet nie próbujemy dalej. */
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() direct connection failed (errno=%d, %s), critical\n", errno, strerror(errno));
                              goto fail_connecting;
                        }

                        sess->state = GG_STATE_CONNECTING_GG;
                        sess->check = GG_CHECK_WRITE;
                        sess->timeout = GG_DEFAULT_TIMEOUT;
                        break;
                  }
                  
                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n");

                  if (!(client = gg_urlencode((sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION))) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n");
                        goto fail_connecting;
                  }

                  if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port)
                        host = "http://" GG_APPMSG_HOST;
                  else
                        host = "";

#ifdef __GG_LIBGADU_HAVE_OPENSSL
                  if (sess->ssl)
                        appmsg = "appmsg3.asp";
                  else
#endif
                        appmsg = "appmsg2.asp";

                  auth = gg_proxy_auth();

                  snprintf(buf, sizeof(buf) - 1,
                        "GET %s/appsvc/%s?fmnumber=%u&version=%s&lastmsg=%d HTTP/1.0\r\n"
                        "Host: " GG_APPMSG_HOST "\r\n"
                        "User-Agent: " GG_HTTP_USERAGENT "\r\n"
                        "Pragma: no-cache\r\n"
                        "%s" 
                        "\r\n", host, appmsg, sess->uin, client, sess->last_sysmsg, (auth) ? auth : "");

                  if (auth)
                        free(auth);
                  
                  free(client);

                  /* zwolnij pamięć po wersji klienta. */
                  if (sess->client_version) {
                        free(sess->client_version);
                        sess->client_version = NULL;
                  }

                  gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf);
       
                  /* zapytanie jest krótkie, więc zawsze zmieści się
                   * do bufora gniazda. jeśli write() zwróci mniej,
                   * stało się coś złego. */
                  if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n");

                        e->type = GG_EVENT_CONN_FAILED;
                        e->event.failure = GG_FAILURE_WRITING;
                        sess->state = GG_STATE_IDLE;
                        close(sess->fd);
                        sess->fd = -1;
                        break;
                  }

                  sess->state = GG_STATE_READING_DATA;
                  sess->check = GG_CHECK_READ;
                  sess->timeout = GG_DEFAULT_TIMEOUT;

                  break;
            }

            case GG_STATE_READING_DATA:
            {
                  char buf[1024], *tmp, *host;
                  int port = GG_DEFAULT_PORT;
                  struct in_addr addr;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n");

                  /* czytamy linię z gniazda i obcinamy \r\n. */
                  gg_read_line(sess->fd, buf, sizeof(buf) - 1);
                  gg_chomp(buf);
                  gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf);
      
                  /* sprawdzamy, czy wszystko w porządku. */
                  if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() that's not what we've expected, trying direct connection\n");

                        close(sess->fd);

                        /* jeśli otrzymaliśmy jakieś dziwne informacje,
                         * próbujemy się łączyć z pominięciem huba. */
                        if (sess->proxy_addr && sess->proxy_port) {
                              if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) {
                                    /* trudno. nie wyszło. */
                                    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno));
                                    goto fail_connecting;
                              }

                              sess->state = GG_STATE_CONNECTING_GG;
                              sess->check = GG_CHECK_WRITE;
                              sess->timeout = GG_DEFAULT_TIMEOUT;
                              break;
                        }
                        
                        sess->port = GG_DEFAULT_PORT;

                        /* łączymy się na port 8074 huba. */
                        if ((sess->fd = gg_connect(&sess->hub_addr, sess->port, sess->async)) == -1) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno));

                              sess->port = GG_HTTPS_PORT;
                              
                              /* łączymy się na port 443. */
                              if ((sess->fd = gg_connect(&sess->hub_addr, sess->port, sess->async)) == -1) {
                                    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
                                    goto fail_connecting;
                              }
                        }
                        
                        sess->state = GG_STATE_CONNECTING_GG;
                        sess->check = GG_CHECK_WRITE;
                        sess->timeout = GG_DEFAULT_TIMEOUT;
                        break;
                  }
      
                  /* ignorujemy resztę nagłówka. */
                  while (strcmp(buf, "\r\n") && strcmp(buf, ""))
                        gg_read_line(sess->fd, buf, sizeof(buf) - 1);

                  /* czytamy pierwszą linię danych. */
                  gg_read_line(sess->fd, buf, sizeof(buf) - 1);
                  gg_chomp(buf);
                  
                  /* jeśli pierwsza liczba w linii nie jest równa zeru,
                   * oznacza to, że mamy wiadomość systemową. */
                  if (atoi(buf)) {
                        char tmp[1024], *foo, *sysmsg_buf = NULL;
                        int len = 0;
                        
                        while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) {
                              if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) {
                                    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n");
                                    break;
                              }

                              sysmsg_buf = foo;

                              if (!len)
                                    strcpy(sysmsg_buf, tmp);
                              else
                                    strcat(sysmsg_buf, tmp);
                              
                              len += strlen(tmp);
                        }
                        
                        e->type = GG_EVENT_MSG;
                        e->event.msg.msgclass = atoi(buf);
                        e->event.msg.sender = 0;
                        e->event.msg.message = sysmsg_buf;
                  }
      
                  close(sess->fd);
      
                  gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http data (%s)\n", buf);

                  /* analizujemy otrzymane dane. */
                  tmp = buf;
                  
                  while (*tmp && *tmp != ' ')
                        tmp++;
                  while (*tmp && *tmp == ' ')
                        tmp++;
                  host = tmp;
                  while (*tmp && *tmp != ' ')
                        tmp++;
                  *tmp = 0;

                  if ((tmp = strchr(host, ':'))) {
                        *tmp = 0;
                        port = atoi(tmp+1);
                  }

                  addr.s_addr = inet_addr(host);
                  sess->server_addr = addr.s_addr;

                  if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) {
                        /* jeśli mamy proxy, łączymy się z nim. */
                        if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) {
                              /* nie wyszło? trudno. */
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno));
                              goto fail_connecting;
                        }
                        
                        sess->state = GG_STATE_CONNECTING_GG;
                        sess->check = GG_CHECK_WRITE;
                        sess->timeout = GG_DEFAULT_TIMEOUT;
                        break;
                  }

                  sess->port = port;

                  /* łączymy się z właściwym serwerem. */
                  if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno));

                        sess->port = GG_HTTPS_PORT;

                        /* nie wyszło? próbujemy portu 443. */
                        if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) {
                              /* ostatnia deska ratunku zawiodła?
                               * w takim razie zwijamy manatki. */
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
                              goto fail_connecting;
                        }
                  }

                  sess->state = GG_STATE_CONNECTING_GG;
                  sess->check = GG_CHECK_WRITE;
                  sess->timeout = GG_DEFAULT_TIMEOUT;
            
                  break;
            }

            case GG_STATE_CONNECTING_GG:
            {
                  int res = 0, res_size = sizeof(res);

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n");

                  /* jeśli wystąpił błąd podczas łączenia się... */
                  if (sess->async && (sess->timeout == 0 || getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
                        /* jeśli nie udało się połączenie z proxy,
                         * nie mamy czego próbować więcej. */
                        if (sess->proxy_addr && sess->proxy_port) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res));
                              goto fail_connecting;
                        }

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

#ifdef ETIMEDOUT
                        if (sess->timeout == 0)
                              errno = ETIMEDOUT;
#endif

#ifdef __GG_LIBGADU_HAVE_OPENSSL
                        /* jeśli logujemy się po TLS, nie próbujemy
                         * się łączyć już z niczym innym w przypadku
                         * błędu. nie dość, że nie ma sensu, to i
                         * trzeba by się bawić w tworzenie na nowo
                         * SSL i SSL_CTX. */

                        if (sess->ssl) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res));
                              goto fail_connecting;
                        }
#endif

                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res));

                        sess->port = GG_HTTPS_PORT;

                        /* próbujemy na port 443. */
                        if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
                              goto fail_connecting;
                        }
                  }

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connected\n");
                  
                  if (gg_proxy_http_only)
                        sess->proxy_port = 0;

                  /* jeśli mamy proxy, wyślijmy zapytanie. */
                  if (sess->proxy_addr && sess->proxy_port) {
                        char buf[100], *auth = gg_proxy_auth();

                        snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(*((struct in_addr*) &sess->server_addr)), sess->port);

                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n//   %s", buf);
                        
                        /* wysyłamy zapytanie. jest ono na tyle krótkie,
                         * że musi się zmieścić w buforze gniazda. jeśli
                         * write() zawiedzie, stało się coś złego. */
                        if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
                              goto fail_connecting;
                        }

                        if (auth) {
                              gg_debug(GG_DEBUG_MISC, "//   %s", auth);
                              if (write(sess->fd, auth, strlen(auth)) < (signed)strlen(auth)) {
                                    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
                                    goto fail_connecting;
                              }

                              free(auth);
                        }

                        if (write(sess->fd, "\r\n", 2) < 2) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
                              goto fail_connecting;
                        }
                  }

#ifdef __GG_LIBGADU_HAVE_OPENSSL
                  if (sess->ssl) {
                        SSL_set_fd(sess->ssl, sess->fd);

                        sess->state = GG_STATE_TLS_NEGOTIATION;
                        sess->check = GG_CHECK_WRITE;
                        sess->timeout = GG_DEFAULT_TIMEOUT;

                        break;
                  }
#endif

                  sess->state = GG_STATE_READING_KEY;
                  sess->check = GG_CHECK_READ;
                  sess->timeout = GG_DEFAULT_TIMEOUT;

                  break;
            }

#ifdef __GG_LIBGADU_HAVE_OPENSSL
            case GG_STATE_TLS_NEGOTIATION:
            {
                  int res;
                  X509 *peer;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n");

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

                        if (res == 0) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n");

                              e->type = GG_EVENT_CONN_FAILED;
                              e->event.failure = GG_FAILURE_TLS;
                              sess->state = GG_STATE_IDLE;
                              close(sess->fd);
                              sess->fd = -1;
                              break;
                        }
                        
                        if (err == SSL_ERROR_WANT_READ) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n");

                              sess->state = GG_STATE_TLS_NEGOTIATION;
                              sess->check = GG_CHECK_READ;
                              sess->timeout = GG_DEFAULT_TIMEOUT;

                              break;
                        } else if (err == SSL_ERROR_WANT_WRITE) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n");

                              sess->state = GG_STATE_TLS_NEGOTIATION;
                              sess->check = GG_CHECK_WRITE;
                              sess->timeout = GG_DEFAULT_TIMEOUT;

                              break;
                        } else {
                              char buf[1024];

                              ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));

                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf);
 
                              e->type = GG_EVENT_CONN_FAILED;
                              e->event.failure = GG_FAILURE_TLS;
                              sess->state = GG_STATE_IDLE;
                              close(sess->fd);
                              sess->fd = -1;
                              break;
                        }
                  }

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n//   cipher: %s\n", SSL_get_cipher_name(sess->ssl));

                  peer = SSL_get_peer_certificate(sess->ssl);

                  if (!peer)
                        gg_debug(GG_DEBUG_MISC, "//   WARNING! unable to get peer certificate!\n");
                  else {
                        char buf[1024];

                        X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf));
                        gg_debug(GG_DEBUG_MISC, "//   cert subject: %s\n", buf);

                        X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf));
                        gg_debug(GG_DEBUG_MISC, "//   cert issuer: %s\n", buf);
                  }

                  sess->state = GG_STATE_READING_KEY;
                  sess->check = GG_CHECK_READ;
                  sess->timeout = GG_DEFAULT_TIMEOUT;

                  break;
            }
#endif

            case GG_STATE_READING_KEY:
            {
                  struct gg_header *h;                
                  struct gg_welcome *w;
                  struct gg_login60 l;
                  unsigned int hash;
                  unsigned char *password = sess->password;
                  int ret;
                  
                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n");

                  memset(&l, 0, sizeof(l));
                  l.dunno2 = 0xbe;

                  /* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie
                   * się tekstu wrzucanego przez proxy. */
                  if (sess->proxy_addr && sess->proxy_port) {
                        char buf[100];

                        strcpy(buf, "");
                        gg_read_line(sess->fd, buf, sizeof(buf) - 1);
                        gg_chomp(buf);
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n//   %s\n", buf);
                        
                        while (strcmp(buf, "")) {
                              gg_read_line(sess->fd, buf, sizeof(buf) - 1);
                              gg_chomp(buf);
                              if (strcmp(buf, ""))
                                    gg_debug(GG_DEBUG_MISC, "//   %s\n", buf);
                        }

                        /* XXX niech czeka jeszcze raz w tej samej
                         * fazie. głupio, ale działa. */
                        sess->proxy_port = 0;
                        
                        break;
                  }

                  /* czytaj pierwszy pakiet. */
                  if (!(h = gg_recv_packet(sess))) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno));

                        e->type = GG_EVENT_CONN_FAILED;
                        e->event.failure = GG_FAILURE_READING;
                        sess->state = GG_STATE_IDLE;
                        close(sess->fd);
                        sess->fd = -1;
                        break;
                  }

                  if (h->type != GG_WELCOME) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n");
                        free(h);
                        close(sess->fd);
                        sess->fd = -1;
                        errno = EINVAL;
                        e->type = GG_EVENT_CONN_FAILED;
                        e->event.failure = GG_FAILURE_INVALID;
                        sess->state = GG_STATE_IDLE;
                        break;
                  }
      
                  w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header));
                  w->key = gg_fix32(w->key);

                  hash = gg_login_hash(password, w->key);
      
                  gg_debug(GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> hash %.8x\n", w->key, hash);
      
                  free(h);

                  free(sess->password);
                  sess->password = NULL;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() gg_dcc_ip = %s\n", inet_ntoa(*((struct in_addr*) &gg_dcc_ip)));
                  
                  if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) {
                        struct sockaddr_in sin;
                        int sin_len = sizeof(sin);

                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n");

                        if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr));
                              l.local_ip = sin.sin_addr.s_addr;
                        } else {
                              gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n");
                              l.local_ip = 0;
                        }
                  } else 
                        l.local_ip = gg_dcc_ip;
            
                  l.uin = gg_fix32(sess->uin);
                  l.hash = gg_fix32(hash);
                  l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL);
                  l.version = gg_fix32(sess->protocol_version);
                  l.local_port = gg_fix16(gg_dcc_port);
                  l.image_size = sess->image_size;
                  
                  if (sess->external_addr && sess->external_port > 1023) {
                        l.external_ip = sess->external_addr;
                        l.external_port = sess->external_port;
                  }

                  gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN60 packet\n");
                  ret = gg_send_packet(sess, GG_LOGIN60, &l, sizeof(l), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, NULL);

                  free(sess->initial_descr);
                  sess->initial_descr = NULL;

                  if (ret == -1) {
                        gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno));

                        close(sess->fd);
                        sess->fd = -1;
                        e->type = GG_EVENT_CONN_FAILED;
                        e->event.failure = GG_FAILURE_WRITING;
                        sess->state = GG_STATE_IDLE;
                        break;
                  }
      
                  sess->state = GG_STATE_READING_REPLY;

                  break;
            }

            case GG_STATE_READING_REPLY:
            {
                  struct gg_header *h;

                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n");

                  if (!(h = gg_recv_packet(sess))) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno));
                        e->type = GG_EVENT_CONN_FAILED;
                        e->event.failure = GG_FAILURE_READING;
                        sess->state = GG_STATE_IDLE;
                        close(sess->fd);
                        sess->fd = -1;
                        break;
                  }
      
                  if (h->type == GG_LOGIN_OK) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n");
                        e->type = GG_EVENT_CONN_SUCCESS;
                        sess->state = GG_STATE_CONNECTED;
                        sess->timeout = -1;
                        sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL;
                        free(h);
                        break;
                  }

                  if (h->type == GG_LOGIN_FAILED) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() login failed\n");
                        e->event.failure = GG_FAILURE_PASSWORD;
                        errno = EACCES;
                  } else if (h->type == GG_NEED_EMAIL) {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() email change needed\n");
                        e->event.failure = GG_FAILURE_NEED_EMAIL;
                        errno = EACCES;
                  } else {
                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n");
                        e->event.failure = GG_FAILURE_INVALID;
                        errno = EINVAL;
                  }

                  e->type = GG_EVENT_CONN_FAILED;
                  sess->state = GG_STATE_IDLE;
                  close(sess->fd);
                  sess->fd = -1;
                  free(h);

                  break;
            }

            case GG_STATE_CONNECTED:
            {
                  gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n");

                  sess->last_event = time(NULL);
                  
                  if ((res = gg_watch_fd_connected(sess, e)) == -1) {

                        gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() watch_fd_connected failed (errno=%d, %s)\n", errno, strerror(errno));

                        if (errno == EAGAIN) {
                              e->type = GG_EVENT_NONE;
                              res = 0;
                        } else
                              res = -1;
                  }
                  break;
            }
      }

done:
      if (res == -1) {
            free(e);
            e = NULL;
      }

      return e;
      
fail_connecting:
      if (sess->fd != -1) {
            close(sess->fd);
            sess->fd = -1;
      }
      e->type = GG_EVENT_CONN_FAILED;
      e->event.failure = GG_FAILURE_CONNECTING;
      sess->state = GG_STATE_IDLE;
      goto done;

fail_resolving:
      e->type = GG_EVENT_CONN_FAILED;
      e->event.failure = GG_FAILURE_RESOLVING;
      sess->state = GG_STATE_IDLE;
      goto done;
}

/*
 * 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