/xconvers-0.8.3/src/net.c
# · C · 458 lines · 314 code · 64 blank · 80 comment · 43 complexity · 0462d0b0a6b9966d4b99ca11b7ddca4e MD5 · raw file
- /*
- * 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);
- }
- }