Logo Search packages:      
Sourcecode: xconvers version File versions  Download package

net.c

/*
 * xconvers - GTK+ convers client for amateur radio
 * Copyright (C) 2000-2003 Joop Stakenborg <pg4i@amsat.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * net.c - private functions for sending and receiving data, connecting and 
 * disconnecting.
 */

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <resolv.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include "support.h"
#include "net.h"
#include "types.h"
#include "utils.h"

gint sockethandle;
gboolean connecttimeout = FALSE;
extern GHashTable *callsigns;
extern GtkWidget *mainwindow;
extern gpointer cvhost, cvport;
extern gchar *preferencesdir, *logfilename;
extern gint rxmonitor, usercolor;
extern gboolean connected, locked;
extern FILE *logfp;
extern preferencestype preferences;
static gint resolvepipe[2], resolvepid, resolvemonitor = 0, connectmonitor,
            progresstimer;

/*
 * Handler for connection timeout, set to 90 seconds.
 */
 
static void connectalarmhandler()
{
  GString *msg = g_string_new(_("Time-out during connection setup"));
  GtkWidget *menuopen, *menuclose;
  
  /* ignore pending alarms and cleanup */
  signal(SIGALRM, SIG_IGN);
  alarm(0);

  updatestatusbar(msg);
  g_string_free(msg, TRUE);
  gtk_timeout_remove(progresstimer);

  connecttimeout = TRUE;
  close(sockethandle);

  menuclose = lookup_widget(mainwindow, "close");
  menuopen = lookup_widget(mainwindow, "open");
  gtk_widget_set_sensitive(menuopen, 1);
  gtk_widget_set_sensitive(menuclose, 0);

  return;
}

/* 
 * Free the child if it is a zombie. A returned FALSE value will stop the
 * timer which calls this function every second.
 */

static gboolean childcheck() {
  gint status, childpid;

  childpid = waitpid(-1, &status, WNOHANG);
  return (WIFEXITED(status) == 0);
}

/*
 * Read result from the child through the pipe.
 */

static void resolvereadpipe()
{
  gint error = 1, ret, len;
  gchar *resultstr = NULL;
  GString *msg = g_string_new("");
  GtkWidget *menuclose, *menuopen;

  /* resolving has returned a result, we can stop the monitor */
  gdk_input_remove(resolvemonitor);
  /* read result of connection from the pipe */
  ret = read(resolvepipe[0], &error, sizeof(error));

  if (error != 1)
  {
    /* we first receive length of string, than the string itself */
    ret = read(resolvepipe[0], &len, sizeof(len));
    resultstr = g_malloc0(len);
    ret = read(resolvepipe[0], resultstr, len);
    ret = close(resolvepipe[0]);
    ret = close(resolvepipe[1]);
    if (error == -1)
    {
      g_string_sprintf(msg, _("Resolver failed: %s"), resultstr);
      updatestatusbar(msg);
      menuclose = lookup_widget(mainwindow, "close");
      menuopen = lookup_widget(mainwindow, "open");
      gtk_widget_set_sensitive(menuopen, 1);
      gtk_widget_set_sensitive(menuclose, 0);
      gtk_timeout_remove(progresstimer);
    }
    else
    {
      cvconnect(resultstr);
    }
    g_string_free(msg, TRUE);
    g_free(resultstr);
  }
}

/*
 * Move the progressbar by adding 1.
 */

static gint progress_timeout(gpointer data)
{
  gfloat new_val;
  GtkAdjustment *adj;

  new_val = gtk_progress_get_value(GTK_PROGRESS(data)) + 1;

  adj = GTK_PROGRESS(data)->adjustment;
  if (new_val > adj->upper) new_val = adj->lower;

  gtk_progress_set_value (GTK_PROGRESS (data), new_val);

  return(TRUE);
}

/*
 * Resolving of the hostname trough a child. The result is sent over a pipe.
 * This way we don't block any dialogs.
 */

void cvresolve(void)
{
  gint error;
  GString *msg = g_string_new("");
  GtkWidget *progressbar;

  error = pipe(resolvepipe);
  if (error != 0)
  {
    g_string_sprintf(msg, _("Pipe failed: %s"), g_strerror(errno));
    updatestatusbar(msg);
    g_string_free(msg, TRUE);
    return;
  }

  g_string_sprintf(msg, _("Resolving %s..."), (gchar *)cvhost);
  updatestatusbar(msg);
  g_string_free(msg, TRUE);

  progressbar = lookup_widget(mainwindow, "progressbar");
  progresstimer =  gtk_timeout_add (50, progress_timeout, progressbar);
  resolvepid = resolvenonblock();
  if (resolvepid == -1) {
    g_string_sprintf(msg, _("Fork failed: %s"), g_strerror(errno));
    updatestatusbar(msg);
    g_string_free(msg, TRUE);
    return;
  }

  /* monitor one end of the pipe for the result */
  resolvemonitor = gdk_input_add(resolvepipe[0], GDK_INPUT_READ, GTK_SIGNAL_FUNC(resolvereadpipe), NULL);
  /* check every second if the child has finished until childcheck returns FALSE, so the timer will be stopped */
  (void)gtk_timeout_add(1000, childcheck, NULL);
}

/*
 * Here the actual child is created. Both resolving errors and results are
 * written through the pipe.
 */

gint resolvenonblock(void)
{
  gint pid, len, ret, error = 0;
  struct hostent *cvhostent;

  /* create fork */
  pid = fork();

  /* use child for resolving */
  if (pid == 0)
  {
    cvhostent = gethostbyname(cvhost);
    if (cvhostent == NULL)
    { /* resolving failed, write result to pipe */
      error = -1;
      ret = write(resolvepipe[1], &error, sizeof(error));
      len = strlen(hstrerror(h_errno)) + 1;
      ret = write(resolvepipe[1], &len, sizeof(len));
      ret = write(resolvepipe[1], hstrerror(h_errno), len);
    }
    else
    { /* send IP-address over the pipe */
      ret = write(resolvepipe[1], &error, sizeof(error));
      len = cvhostent->h_length;
      ret = write(resolvepipe[1], &len, sizeof(len));
      ret = write(resolvepipe[1], cvhostent->h_addr, len);
    }
    _exit(0);
  }
  return(pid); /* parent pid */
}

/*
 * Something has happened on the socket. We now have to check if a connection
 * has succeeded. Do autologin if enabled and setup a monitor for incoming 
 * packets.
 */
static void socket_connected(void)
{
  GtkWidget *menuopen, *menuclose;
  GString *msg = g_string_new(""), *sendstring;
  gchar **sendsplit = NULL;
  gint i = 0, res, option, size = sizeof(gint);

  gdk_input_remove(connectmonitor);
  /* Disable alarm handler */
  signal(SIGALRM, SIG_IGN);
  alarm(0);

  res = getsockopt(sockethandle, SOL_SOCKET, SO_ERROR, &option, &size);

  if (option != 0)
  {
    if (!connecttimeout) 
    {
      msg = g_string_new(strerror(option));
      updatestatusbar(msg);
      g_string_free(msg, TRUE);
      menuclose = lookup_widget(mainwindow, "close");
      menuopen = lookup_widget(mainwindow, "open");
      gtk_widget_set_sensitive(menuopen, 1);
      gtk_widget_set_sensitive(menuclose, 0);
      gtk_timeout_remove(progresstimer);
    }
    return;
  }

  g_string_sprintf(msg, _("Connected to %s"), (gchar *)cvhost);
  updatestatusbar(msg);
  g_string_free(msg, TRUE);
  
  menuclose = lookup_widget(mainwindow, "close");
  menuopen = lookup_widget(mainwindow, "open");
  gtk_widget_set_sensitive(menuopen, 0);
  gtk_widget_set_sensitive(menuclose, 1);
  if (preferences.logging) 
  {
    logfilename = g_strconcat(preferencesdir, "/", cvhost, ".log", NULL);
    if ((logfp = fopen(logfilename, "w")) == NULL) 
      g_error(_("Creating %s for writing."), logfilename);
  }
  if (preferences.autologin && preferences.name)
  {
    sendstring = g_string_new("/n ");
    g_string_append(sendstring, preferences.name->str);
    if (preferences.channel)
    {
      g_string_append(sendstring, " ");
      g_string_append(sendstring, preferences.channel->str);
    }
    tx(sendstring);
    g_string_free(sendstring, TRUE);
  }
  if (preferences.autologin && preferences.commands)
  {
    sendsplit = g_strsplit(preferences.commands->str, ";", 0);
    while (sendsplit[i])
    {
      sendstring = g_string_new(sendsplit[i]);
      tx(sendstring);
      g_string_free(sendstring, TRUE);
      i++;
    }
    g_strfreev(sendsplit);
  }

  gtk_timeout_remove(progresstimer);
  rxmonitor = gdk_input_add(sockethandle, GDK_INPUT_READ, GTK_SIGNAL_FUNC(rx), NULL);
  callsigns = g_hash_table_new(g_str_hash, g_str_equal);
  connected = TRUE;
}

/*
 * Connect routine. Create the socket and recover from possible errors,
 * which will be displayed in the statusbar. Also, set up a monitor which
 * checks if the connection is created.
 */

void cvconnect(gchar *where)
{
  GString *msg = g_string_new("");
  gint opt, ret;
  struct sockaddr_in cvaddress;

  g_string_sprintf(msg, _("Connecting to %s..."), inet_ntoa(*((struct in_addr *)where)));
  updatestatusbar(msg);
  g_string_free(msg, TRUE);

  if ((sockethandle = socket (AF_INET, SOCK_STREAM, 0)) == -1)
  {
    msg = g_string_new(strerror(errno));
    updatestatusbar(msg);
    g_string_free(msg, TRUE); 
    return;
  }

  /* send keepalive probes */
  setsockopt(sockethandle, SOL_SOCKET, SO_KEEPALIVE, (gchar *)&opt, sizeof(opt));
  /* make connection setup non-blocking */
  fcntl(sockethandle, F_SETFL, O_NONBLOCK);

  cvaddress.sin_family = AF_INET;
  cvaddress.sin_port = htons(atoi(cvport));
  cvaddress.sin_addr = *((struct in_addr *)where);
  bzero (&(cvaddress.sin_zero), 8);

  connecttimeout = FALSE;
  signal(SIGALRM, connectalarmhandler);
  alarm(90);

  ret = connect(sockethandle, (struct sockaddr *)&cvaddress, sizeof(struct sockaddr));

  if (ret == -1 && errno != EINPROGRESS)
  {
    if (!connecttimeout) 
    {
      msg = g_string_new(strerror(errno));
      updatestatusbar(msg);
      g_string_free(msg, TRUE);
    }
    return;
  }

  /* connection is non-blocking, so we set up a monitor to see if connection is succesful */
  connectmonitor = gdk_input_add(sockethandle, GDK_INPUT_WRITE, GTK_SIGNAL_FUNC(socket_connected), NULL);
}

/*
 * Disconnect routine. The socket and logfile are closed, the lockfile for
 * logging is removed, statusbar updated and hash tabled freed. 
 * Also, menu items are adjusted, so only 'connect' will be active. 
 */

void cvdisconnect(void)
{
  GString *msg = g_string_new("");
  GtkWidget *menuopen, *menuclose;

  close(sockethandle);
  if (preferences.logging) fclose(logfp);
  if (rxmonitor) gdk_input_remove(rxmonitor);

  g_string_sprintf(msg, _("Connection closed"));
  updatestatusbar(msg);
  g_string_free(msg, TRUE);

  menuclose = lookup_widget(mainwindow, "close");
  menuopen = lookup_widget(mainwindow, "open");
  gtk_widget_set_sensitive(menuopen, 1);
  gtk_widget_set_sensitive(menuclose, 0);
  if (callsigns) 
  {
    g_hash_table_foreach_remove(callsigns, (GHRFunc)remove_calls, NULL);
    g_hash_table_destroy(callsigns);
  }
  usercolor = 0;
  connected = FALSE;
}

/*
 * A message is received here. We create a buffer where the incoming message is
 * stored. Then we check for possible problems (displayed in the statusbar). If 
 * all is OK, the message gets displayed and is written to the log if this is
 * opened.
 */

void rx(gpointer data, gint source, GdkInputCondition condition)
{
  gchar cvbuf[8192];
  gint numbytes;
  GString *msg = g_string_new("");

  memset(cvbuf, 0, 8192*sizeof(gchar));
  numbytes = read(sockethandle, cvbuf, 4096);

  if (numbytes == - 1 ) /* an error has occured */
  {
    msg = g_string_new(strerror(errno));
    updatestatusbar(msg);
    g_string_free(msg, TRUE); 
    cvdisconnect();
    return;
  }

  if ( numbytes == 0 ) /* remote end has closed connection */
  {
    cvdisconnect();
    return;
  }

  maintext_add(cvbuf, MESSAGE_RX);

  if (preferences.logging)
  {
    fprintf(logfp, "%s", cvbuf);
    fflush(logfp);
  }
}

/*
 * Save messages to history, send out to the socket, echo to the main window,
 * and write to logfile.
 */

void tx(GString *message)
{
  tx_save(message);
  message = g_string_append(message, "\n");
  write(sockethandle, message->str, message->len);
  maintext_add(message->str, MESSAGE_TX);
  if (preferences.logging && connected)
  {
    fprintf(logfp, "%s", message->str);
    fflush(logfp);
  }
}

Generated by  Doxygen 1.6.0   Back to index