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

netdaemon.c

/*
 * src/netdaemon.c - the net daemon
 *
 * Copyright (C) 2004 Novell, Inc.
 *
 * Licensed under the GNU GPL v2.  See COPYING.
 */
#define _GNU_SOURCE
#include <config.h>

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <net/ethernet.h>
#include <glib.h>
#include <iwlib.h>

#include "netcommon.h"

/* SuSE */
#define SUSE_IFCFG_PATH       "/etc/sysconfig/network"
#define SUSE_IFCFG_PREFIX           "ifcfg-"
#define GETCFG_CMD            "/sbin/getcfg"
#define GETCFG_INTERFACE_CMD  "/sbin/getcfg-interface"

/* Gentoo */
#define GENTOO_IFCFG_FILE     "/etc/conf.d/net"
#define GENTOO_IFCFG_PREFIX   "iface_"
#define GENTOO_IFCFG_DELIMS   "_="

/* Debian */
#define DEBIAN_INTERFACE_FILE   "/etc/network/interfaces"

/* Standard Linux commands */
#define IFUP_BIN        "/sbin/ifup"
#define IFDOWN_BIN            "/sbin/ifdown"
#define ROUTE_CMD               "/sbin/route"
#define FORWARD_CMD             SBINDIR "/netapplet-forward"
#define STARTFWD_CMD            SBINDIR "/netapplet-startfwd"
#define STOPFWD_CMD             SBINDIR "/netapplet-stopfwd"
#define MAX_QUAL_OVERRIDE     90    /* for driver's with a broken max */

#define ROUTE_ARGV              { ROUTE_CMD, "-n", NULL }

#define TIMEOUT_INTERVAL        10000

typedef struct {
      GIOChannel *channel;    /* GIOChannel that requested this scan */
      char *interface;  /* network interface we are scanning */
      int fd;                 /* open fd from iw_sockets_open () */
} ScanningInfo;

typedef struct {
      char essid[IW_ESSID_MAX_SIZE + 1];  /* ESSID name */
      int quality;                        /* link quality */
      gboolean is_encrypted;              /* is this AP encrypted? */
} APInfo;

static GHashTable *hash;                  /* hash table for commands */
static GSList *current_connections;       /* list of GIOChannels */

static gboolean skip_poll = FALSE;              /* Skip one round of polling,
                                       for slow network changes */

static char *active_iface = NULL;               /* The last active interface */

int
check_interface_up (const char *interface)
{
      int fd;
      struct ifreq ifr;

      fd = socket (AF_INET, SOCK_DGRAM, 0);
      ifr.ifr_addr.sa_family = AF_INET;
      g_strlcpy (ifr.ifr_name, interface, sizeof (ifr.ifr_name));
      if (ioctl (fd, SIOCGIFFLAGS, &ifr) < 0) {
            return -1;
      }
      if (ifr.ifr_flags & IFF_UP) {
            return 0;
      }
      return -2;
}

static const char *
get_network_type (const char *interface)
{
      int skfd;
      struct iwreq wrq;

      if (strncmp (interface, "ppp", 3) == 0)
            return TYPE_DIALUP;

      skfd = iw_sockets_open ();
      if (skfd < 0)
            return TYPE_ETHERNET;

      strncpy (wrq.ifr_name, interface, IFNAMSIZ);
      if (ioctl (skfd, SIOCGIWNAME, &wrq) < 0) {
            /* no wireless extensions */
            close (skfd);
            return TYPE_ETHERNET;
      }

      close (skfd);

      return TYPE_WIRELESS;
}

static GSList *
get_interfaces_gentoo (void)
{
      GError *err = NULL;
      GSList *iface_list = NULL;

      GIOChannel *gen_confd_net_file;
      gchar *gen_confd_net_filedata;
      gchar **gen_confd_net_ifacedata;

      /* Open gentoo network configuration file */
      gen_confd_net_file = g_io_channel_new_file (GENTOO_IFCFG_FILE, "r", &err);
      if (!gen_confd_net_file) {
            g_warning ("Failed to open %s: %s\n", "GENTOO_IFCFG_FILE", err->message);
            g_error_free (err);
            return NULL;
      }

      /* Read each iface_ line in the gentoo net cfg and add it to iface_list */ 
      while (g_io_channel_read_line (gen_confd_net_file, &gen_confd_net_filedata, NULL, NULL, &err) ==
             G_IO_STATUS_NORMAL) {
            if (g_str_has_prefix (gen_confd_net_filedata, GENTOO_IFCFG_PREFIX)) {
                  gen_confd_net_ifacedata = g_strsplit_set (gen_confd_net_filedata, GENTOO_IFCFG_DELIMS, 0);
                  /* Blacklist the loopback device */
                  if (strcmp(gen_confd_net_ifacedata[1],"lo")) 
                  {
                        if (!g_slist_find_custom(iface_list, gen_confd_net_ifacedata[1],
                                             (GCompareFunc)strcmp)) {
                              /* We haven't seen this interface before */
                              iface_list = g_slist_prepend (iface_list,
                                                        g_strdup(gen_confd_net_ifacedata[1]));
                      }
                  }

                  g_strfreev (gen_confd_net_ifacedata);
            }
            g_free (gen_confd_net_filedata);
      }

      return iface_list;
}

static GSList *
get_interfaces_suse (void)
{
      GError *err = NULL;
      GDir *dir;
      const char *name;
      GSList *iface_list = NULL;

      dir = g_dir_open (SUSE_IFCFG_PATH, 0, &err);
      if (!dir) {
            g_error ("Unable to open "SUSE_IFCFG_PATH": %s",
                   err->message);
            return NULL;
      }

      do {
            char *interface;
            const char *argv[3];

            name = g_dir_read_name (dir);
            if (name == NULL || strncmp (name, SUSE_IFCFG_PREFIX,
                                   sizeof (SUSE_IFCFG_PREFIX) - 1) != 0)
                  continue;

            /* Blacklist the loopback device */
            if (strcmp (name, SUSE_IFCFG_PREFIX"lo") == 0)
                  continue;

            /* 
             * Blacklist "ip6tnl*" and "mip6mnha" interfaces, which
             * the SUSE package miplv6 dumps into the sysconf path.
             */
            if (g_str_has_prefix (name, SUSE_IFCFG_PREFIX"ip6tnl") ||
                g_str_has_prefix (name, SUSE_IFCFG_PREFIX"mipmnha"))
                  continue;

            argv[0] = GETCFG_INTERFACE_CMD;
            argv[1] = (char *) name + sizeof (SUSE_IFCFG_PREFIX) - 1;
            argv[2] = NULL;

            if (!g_spawn_sync (NULL, (char **) argv, NULL, 0,
                           NULL, NULL, &interface, NULL,
                           NULL, &err)) {
                  g_warning ("Unable to execute "
                           GETCFG_INTERFACE_CMD": %s",
                           err->message);
                  g_error_free (err);
                  continue;
            }

            if (interface != NULL && interface[0] != '\0') {
                  /* strip whitespace */
                  interface = g_strstrip (interface);

                  /*
                   * Workaround for some strange behavior in
                   * getcfg-interface.  If you bring up a ppp0
                   * interface, and if you run it (as root), it
                   * might return "no" as the interface name while
                   * it's in the process of connecting.  If that's
                   * the case, then return the name as the
                   * interface instead.
                   */
                  if (strcmp (interface, "no") == 0) {
                        g_free (interface);
                        interface = g_strdup (argv[1]);
                  }

                  iface_list = g_slist_prepend (iface_list, interface);
            } else
                  g_free (interface);

      } while (name);

      g_dir_close (dir);

      return iface_list;
}

static char *
get_config_path (const char *interface)
{
      const char *argv[3];
      char *getcfg;
      char *cfg;
      char *p;
      GError *err = NULL;     
      char *retval;

      argv[0] = GETCFG_CMD;
      argv[1] = interface;
      argv[2] = NULL;

      retval = NULL;

      /* get the configuration information for this interface */
      if (!g_spawn_sync (NULL, (char **) argv, NULL, 0, NULL, NULL,
                     &getcfg, NULL, NULL, &err)) {
                  g_warning ("Unable to execute "GETCFG_CMD": %s",
                           err->message);
                  g_error_free (err);
                  return NULL;
      }

      /* get the ifcfg name for this interface */
      cfg = strstr (getcfg, "HWD_CONFIG_0");
      if (!cfg) {
            g_warning ("Unable to determine configuration file for "
                     "interface %s", interface);
            goto out_free_getcfg;
      }
      cfg = strstr (cfg, "=");
      if (!cfg) {
            g_warning ("Unable to determine configuration file for "
                     "interface %s", interface);
            goto out_free_getcfg;
      }
      cfg++;
      p = strstr (cfg, ";");
      if (!p) {
            g_warning ("Unable to determine configuration file for "
                     "interface %s", interface);
            goto out_free_getcfg;
      }
      p[0] = '\0';
      cfg = g_strdup_printf (SUSE_IFCFG_PATH"/"SUSE_IFCFG_PREFIX"%s", cfg);

      retval = cfg;

 out_free_getcfg:
      g_free (getcfg);
      return retval;
}

static char *
escape_essid (const char *essid)
{
      char **tokens;
      char *escaped;

      tokens = g_strsplit (essid, "'", 0);
      escaped = g_strjoinv ("'\\''", tokens);
      g_strfreev (tokens);

      return escaped;
}

/*
 * Rewrites the /etc/sysconfig/network file for the specified interface.
 *
 * If any field is specified as NULL, then we simply skip that field,
 * and don't overwrite any existing values.
 *
 * interface: interface config to modify (e.g. "eth0")
 * bootproto: "static" for static IP, "dhcp" for dhcp
 * ipaddr: IP address ("18.238.0.1"), or NULL to skip
 * netmask: netmask ("255.255.255.0"), or NULL to skip
 * wireless_mode: Must be either 'Managed' 'Ad-Hoc' or 'Master'
 * essid: essid or NULL to skip
 * key: Must be either a wireless encryption key or "" if no key
 */
static gboolean
modify_interface_config (const char *interface, const char *bootproto,
                   const char *ipaddr, const char *netmask,
                   const char *wireless_mode,
                   const char *wireless_essid,
                   const char *wireless_key)
{
      GString *output;
      char *data = NULL, *cfg;
      GIOChannel *file;
      GError *err = NULL;
      gboolean retval = FALSE;

      cfg = get_config_path (interface);
      if (cfg == NULL)
            return FALSE;

      output = g_string_sized_new (512);  /* start big to prevent resizing */

      file = g_io_channel_new_file (cfg, "r", &err);
      if (!file) {
            g_warning ("Failed to open %s: %s\n", cfg, err->message);
            g_error_free (err);
            return FALSE;
      }

      /* remove lines that we are changing */
      while (g_io_channel_read_line (file, &data, NULL, NULL, &err) ==
                  G_IO_STATUS_NORMAL) {
            if (!(g_str_has_prefix (data, "WIRELESS_ESSID=") && wireless_essid != NULL) &&
                !(g_str_has_prefix (data, "WIRELESS_KEY_0=") && wireless_key != NULL) &&
                !(g_str_has_prefix (data, "WIRELESS_MODE") && wireless_mode != NULL) &&
                !(g_str_has_prefix (data, "IPADDR=") && ipaddr != NULL) &&
                !(g_str_has_prefix (data, "NETMASK=") && netmask != NULL) &&
                !(g_str_has_prefix (data, "BOOTPROTO=") && bootproto != NULL) &&

                /* we always reset these */
                !g_str_has_prefix (data, "WIRELESS_NWID=''") &&
                !g_str_has_prefix (data, "WIRELESS_KEY=''") &&
                !g_str_has_prefix (data, "WIRELESS_KEY_LENGTH=") &&
                !g_str_has_prefix (data, "WIRELESS_AP") &&
                !g_str_has_prefix (data, "WIRELESS_BITRATE") &&
                !g_str_has_prefix (data, "WIRELESS_CHANNEL") &&
                !g_str_has_prefix (data, "WIRELESS_FREQUENCY") &&
                !g_str_has_prefix (data, "WIRELESS_DEFAULT_KEY="))
                  g_string_append (output, data);

            g_free (data);
      }

      if (err) {
            g_warning ("Failed to read %s: %s\n", cfg, err->message);
            g_error_free (err);
            goto out;
      }

      /* write out new network configuration lines */
      if (bootproto != NULL)
            g_string_append_printf (output, "BOOTPROTO='%s'\n", bootproto);

      if (ipaddr != NULL && strlen (ipaddr) > 0)
            g_string_append_printf (output, "IPADDR='%s'\n", ipaddr);

      if (netmask != NULL && strlen (netmask) > 0)
            g_string_append_printf (output, "NETMASK='%s'\n", netmask);

      /* write out wireless configuration lines */
      if (! strcmp (get_network_type (interface), TYPE_WIRELESS) &&
          (wireless_essid != NULL || wireless_mode != NULL || wireless_key != NULL)) {

            if (wireless_essid != NULL) {
                  char *escaped_essid;
                  escaped_essid = escape_essid (wireless_essid);
                  g_string_append_printf (output, "WIRELESS_ESSID='%s'\n",
                                    escaped_essid);
                  g_free (escaped_essid);
            }

            if (wireless_key != NULL && strlen (wireless_key) > 0)
                  g_string_append_printf (output, "WIRELESS_KEY_0='%s'\n", wireless_key);

            /* return the other lines to a nice sane default */
            if (! strcmp (wireless_mode, ""))
                  wireless_mode = "Managed";

            g_string_append_printf (output, "WIRELESS_NWID=''\n");
            g_string_append_printf (output, "WIRELESS_KEY=''\n");
            g_string_append_printf (output, "WIRELESS_DEFAULT_KEY='0'\n");
            g_string_append_printf (output, "WIRELESS_KEY_LENGTH='128'\n");
            g_string_append_printf (output, "WIRELESS_AP=''\n");
            g_string_append_printf (output, "WIRELESS_BITRATE='auto'\n");
            g_string_append_printf (output, "WIRELESS_CHANNEL=''\n");
            g_string_append_printf (output, "WIRELESS_MODE='%s'\n", wireless_mode);
            g_string_append_printf (output, "WIRELESS_FREQUENCY=''");
      }

      /* close, truncate, and reopen the file for writing */
      g_io_channel_unref (file);
      file = g_io_channel_new_file (cfg, "w", &err);
      if (!file) {
            g_warning ("Failed to open %s: %s\n", cfg, err->message);
            g_error_free (err);
            goto out;
      }

      /* write out the new file in one swoop */
      if (g_io_channel_write_chars (file, output->str, -1, NULL, &err) !=
                  G_IO_STATUS_NORMAL) {
            g_warning ("Failed to write to %s: %s\n", cfg, err->message);
            g_error_free (err);
            goto out;
      }

      g_io_channel_flush (file, NULL);
      retval = TRUE;

out:
      g_string_free (output, TRUE);
      g_io_channel_unref (file);
      g_free (cfg);

      return retval;
}

static gboolean
ifup (const char *interface)
{
      const char *argv[3];
      GError *err = NULL;
      int ret;

      argv[0] = IFUP_BIN;
      argv[1] = interface;
      argv[2] = NULL;

      if (!g_spawn_sync (NULL, (char **) argv, NULL,
                     0, NULL, NULL, NULL, NULL, &ret,
                     &err)) {
            g_warning ("Unable to exec " IFUP_BIN ": %s",
                     err->message);
            g_error_free (err);
      }

      if (WEXITSTATUS (ret) == 0 && check_interface_up(argv[1])==0)
            return TRUE;

      return FALSE;
}

static void
ifdown (const char *interface)
{
      const char *argv[3];
      GError *err = NULL;

      argv[0] = IFDOWN_BIN;
      argv[1] = interface;
      argv[2] = NULL;

      if (!g_spawn_sync (NULL, (char **) argv, NULL,
                     0, NULL, NULL, NULL, NULL, NULL,
                     &err)) {
            g_warning ("Unable to exec " IFDOWN_BIN ": %s",
                     err->message);
            g_error_free (err);
      }
}

#if 0
static void
setup_forward (const char *from, const char *to)
{
      char *cmd;

      /* FIXME: exploitable? */
      cmd = g_strdup_printf ("%s %s %s", FORWARD_CMD, from, to);
      g_spawn_command_line_sync (cmd, NULL, NULL, NULL, NULL);
}
#endif

static GSList *
get_interfaces_debian (void)
{
  GError *err = NULL;
  const char *name;
  GSList *iface_list = NULL;
  GIOChannel *file;
  gchar *data;
  gchar **ifacedata = NULL;

  /* First get the list of interfaces that the kernel already knows about */

  file = g_io_channel_new_file ("/proc/net/dev", "r", &err);
  if (!file) {
    g_warning ("Failed to open %s: %s\n",
             "/proc/net/dev", err->message);
    g_error_free (err);
    return NULL;
  }

  while (g_io_channel_read_line (file, &data, NULL, NULL, &err) ==
       G_IO_STATUS_NORMAL) {    
    if (g_strrstr(data,":")) {
      ifacedata = g_strsplit_set (data, ":", 0);
      ifacedata[0] = g_strchug (ifacedata[0]);
      if (strcmp(ifacedata[0],"lo")) {
      if (!g_slist_find_custom(iface_list,ifacedata[0],
                         (GCompareFunc)strcmp)) {
        /* We haven't seen this interface before */
        iface_list = g_slist_prepend (iface_list,
                              g_strdup(ifacedata[0]));
      }
      }
      g_strfreev(ifacedata);
      ifacedata = NULL;
    }
    g_free(data);
  }

  if (err) {
    /* Something has blown up */
    g_error_free (err);
    return NULL;
  }

  g_io_channel_unref(file);
  file=NULL;

  /* Now get the list of interfaces that can come into existence if we ifup
     them */

  file = g_io_channel_new_file ("/etc/network/interfaces", "r", &err);
  if (!file) {
    g_warning ("Failed to open %s: %s\n",
             "/etc/network/interfaces", err->message);
    g_error_free (err);
    return NULL;
  }

  while (g_io_channel_read_line (file, &data, NULL, NULL, &err) ==
       G_IO_STATUS_NORMAL) {
    data = g_strchug (data);
    if (g_str_has_prefix (data, "iface ")) {
      g_strfreev(ifacedata); /* can be NULL */
      ifacedata = g_strsplit_set (data, " ", 0);    
    } else if (g_str_has_prefix (data, "creates") && ifacedata != NULL) {
      if (strcmp(ifacedata[1],"lo")) {
      if (!g_slist_find_custom(iface_list,ifacedata[1],
                         (GCompareFunc)strcmp)) {
        /* We haven't seen this interface before */
        iface_list = g_slist_prepend (iface_list,
                              g_strdup(ifacedata[1]));
        printf("Added %s\n",ifacedata[1]);
      }      
      }
    }
    g_free(data);
  }

  if (err) {
    /* Something has blown up */
    return NULL;
  }

  g_strfreev(ifacedata); /* can be NULL */

  g_io_channel_unref(file);

  return iface_list;
}


static GSList *
get_interfaces (void)
{
      if (strcmp (get_platform (), SUSE_PLATFORM_NAME) == 0)
            return get_interfaces_suse ();
      if (strcmp (get_platform (), GENTOO_PLATFORM_NAME) == 0)
            return get_interfaces_gentoo ();
      if (strcmp (get_platform (), DEBIAN_PLATFORM_NAME) == 0)
              return get_interfaces_debian ();

      return NULL;
}

/*
 * Sanitize the interface.  We cannot trust the networking shell
 * scripts, which have escaping problems out the wazoo.
 */
static const char *
verify_interface (const char *interface)
{
      GSList *iface_list, *iter;

      if (!interface)
            return NULL;

      iface_list = get_interfaces ();
      if (!iface_list)
            return NULL;

      for (iter = iface_list; iter != NULL; iter = iter->next) {
            if (strcmp (iter->data, interface) == 0)
                  return interface;
      }

      return NULL;
}

static void
netdaemon_disconnect_all (void)
{
      GSList *iface_list, *iter;

      iface_list = get_interfaces ();

      for (iter = iface_list; iter != NULL; iter = iter->next)
            ifdown (iter->data);

      g_free (active_iface);
      active_iface = NULL;
      g_slist_foreach (iface_list, (GFunc) g_free, NULL);
      g_slist_free (iface_list);
}

static void
netdaemon_do_disconnect (GIOChannel *channel, char **args G_GNUC_UNUSED)
{
      /* down all interfaces */
      netdaemon_disconnect_all ();

      /* tell the client that we are disconnected */
      netcommon_send_message (channel, "disconnected", NULL);
}

static void
netdaemon_do_change_active (GIOChannel *channel G_GNUC_UNUSED, char **args)
{
      const char *interface;

      /* Is this interface valid ? */
      interface = verify_interface (args[1]);
      if (!interface) {
            g_warning ("invalid interface: %s\n", args[1]);
            return;
      }

      /*
       * To get around various network script bugs, and to have the concept
       * of a single active interface, we down all interfaces first,
       * including the very interface we plan to bring back up.  No trust.
       */
      netdaemon_disconnect_all ();

      /*
       * In case the wireless interface was left in Master mode with
       * a static IP from sharing earlier, reset it to Managed mode with
       * a dynamic IP now.
       */
      if (!strcmp (get_network_type (interface), TYPE_WIRELESS))
            modify_interface_config (interface, "dhcp", NULL, NULL,
                               "Managed", NULL, NULL);

      /* Bring the interface up */
      if (ifup (interface)) {
            netcommon_send_message (channel, "active", interface, NULL);
            g_free (active_iface);
            active_iface = g_strdup (interface);
      }

      /* 
       * Let's skip the next poll so we can wait for these interfaces to
       * come up.
       */
      skip_poll = TRUE;
}

static void
netdaemon_do_list_interfaces (GIOChannel *channel G_GNUC_UNUSED,
                        char **args G_GNUC_UNUSED)
{
      GSList *iface_list, *iter;
      GString *ifaces = NULL;
      gboolean found_active = FALSE;

      iface_list = get_interfaces ();
      if (!iface_list)
            return;

      for (iter = iface_list; iter != NULL; iter = iter->next) {
            const char *interface = iter->data;
            const char *type;

            if (active_iface != NULL &&
                        strcmp (active_iface, interface) == 0)
                  found_active = TRUE;

            type = get_network_type (interface);

            if (ifaces == NULL)
                  ifaces = g_string_new ("");
            else
                  ifaces = g_string_append_c (ifaces, ' ');

            g_string_append_printf (ifaces, "%s %s", interface, type);
      }

      g_slist_foreach (iface_list, (GFunc) g_free, NULL);
      g_slist_free (iface_list);

      netcommon_send_message (channel, "interfaces", ifaces->str, NULL);
      g_string_free (ifaces, TRUE);

      if (!found_active) {
            g_free (active_iface);
            active_iface = NULL;
      }
}

static void
netdaemon_do_get_active (GIOChannel *channel, char **args G_GNUC_UNUSED)
{
      const char *argv[] = ROUTE_ARGV;
      char *output;
      GError *err = NULL;
      char **lines, **l;

      if (!g_spawn_sync (NULL, (char **) argv, NULL, 0, NULL, NULL,
                     &output, NULL, NULL, &err)) {
            g_warning ("Unable to execute "ROUTE_CMD": %s",
                     err->message);
            g_error_free (err);
            return;
      }

      lines = g_strsplit (output, "\n", -1);

      for (l = lines; *l != NULL; l++) {
            const char *iface;

            /*
             * We are only interested in the default route, which
             * has a "Destination" field of "0.0.0.0".
             */
            if (strncmp (*l, "0.0.0.0", 7) != 0)
                  continue;

            /* The interface is the last field */
            iface = strrchr (*l, ' ');
            if (iface != NULL) {
                  iface++; /* move past the space */
                  netcommon_send_message (channel, "active",
                                    iface, NULL);
                  g_free (active_iface);
                  active_iface = g_strdup (iface);
                  goto out;
            }
      }

      /*
       * FIXME: Not totally sure this is right.  We may want to check to
       * make sure the interface is up before unconditionally sending
       * the old active iface, but this is better than nothing.
       */
      if (active_iface != NULL && check_interface_up(active_iface)==0)
            netcommon_send_message (channel, "active", active_iface, NULL);
      else {
            /*
             * we did not find an active interface - tell the client
             * as much
             */
            netcommon_send_message (channel, "disconnected", NULL);
      }

out:
      g_strfreev (lines);
      g_free (output);
}

#if 0
/*
 * turn on IP forwarding in the kernel
 *
 * args[0]: 'start_forward' (ignored)
 */
static void
netdaemon_do_start_forward (GIOChannel *channel G_GNUC_UNUSED, char **args G_GNUC_UNUSED)
{
      g_spawn_command_line_sync (STARTFWD_CMD, NULL, NULL, NULL, NULL);
}

/*
 * turn off IP forwarding in the kernel, and clear iptables
 *
 * args[0]: 'start_forward' (ignored)
 */
static void
netdaemon_do_stop_forward (GIOChannel *channel G_GNUC_UNUSED, char **args G_GNUC_UNUSED)
{
      g_spawn_command_line_sync (STOPFWD_CMD, NULL, NULL, NULL, NULL);
}

/*
 * handler to share one interface to another interface
 *
 * args[0]: 'share_interface' (ignored)
 * args[1]: from_interface
 * args[2]: to_interface
 */
static void
netdaemon_do_share_interface (GIOChannel *channel G_GNUC_UNUSED, char **args)
{
      char *from_ifc = args[1];
      char *to_ifc = args[2];

      printf ("netdaemon_do_share_interface\n");

      ifup (to_ifc);

      setup_forward (from_ifc, to_ifc);
}

/*
 * handler to setup a wireless interface as a wireless access point
 *
 * args[0]: 'setup_wap' (ignored)
 * args[1]: wireless interface
 * args[2]: new IP address to setup
 * args[3]: new netmask to setup
 * args[4]: new ESSID to setup
 * args[5]: new wireless key to setup
 */
static void
netdaemon_do_setup_wap (GIOChannel *channel  G_GNUC_UNUSED, char **args)
{
      const char *interface = args[1];
      const char *ipaddr    = args[2];
      const char *netmask   = args[3];
      const char *essid     = args[4];
      const char *key       = args[5];

      printf ("netdaemon_do_setup_wap\n");
      
      modify_interface_config (interface, "static", ipaddr, netmask, "Master", essid, key);
}
#endif

static APInfo *
parse_scanning_event (struct iw_event *event, APInfo *old_ap)
{
      APInfo *ap;

      if (event->cmd == SIOCGIWAP)
            ap = g_new0 (APInfo, 1);
      else {
            g_assert (old_ap);
            ap = old_ap;
      }

      switch (event->cmd) {
      case SIOCGIWESSID:
            if (event->u.essid.pointer) {
                  memcpy (ap->essid, event->u.essid.pointer,
                        IW_ESSID_MAX_SIZE + 1);
            }
            ap->essid[event->u.essid.length] = '\0';
            break;
      case IWEVQUAL:
            ap->quality = event->u.qual.qual;
            break;
      case SIOCGIWENCODE:
            if (event->u.data.flags & IW_ENCODE_DISABLED)
                  ap->is_encrypted = FALSE;
            else
                  ap->is_encrypted = TRUE;
            break;
      }

      return ap;
}

static void
hash_to_list (gpointer key G_GNUC_UNUSED, gpointer value, gpointer user_data)
{
      GSList **list = (GSList **) user_data;
      *list = g_slist_prepend (*list, value);
}

static int
ap_compare (gconstpointer a, gconstpointer b)
{
      APInfo *ap_a = (APInfo *) a;
      APInfo *ap_b = (APInfo *) b;

      return g_ascii_strcasecmp (ap_a->essid, ap_b->essid);
}

static void
send_scanning_results (GIOChannel *channel, const char *interface, GSList *aps,
                   guint8 max)
{
      GHashTable *ap_hash;
      GString *msg;
      GSList *iter, *list = NULL;

      msg = g_string_sized_new (512);     /* start big to avoid resizes */
      g_string_printf (msg, "accesspoints %s", interface);

      ap_hash = g_hash_table_new_full (g_str_hash, g_str_equal, 
                               NULL, g_free);

      for (iter = aps; iter != NULL; iter = iter->next) {
            APInfo *ap = iter->data, *best;

            /*
             * Scanning returns an entry for each AP on a given ESSID, but
             * we only want to see one entry per ESSID, with the highest
             * quality value of all of the AP's.  We do this cunningly via
             * hash table.
             */
            best = g_hash_table_lookup (ap_hash, ap->essid);
            if (best) {
                  if (ap->quality > best->quality)
                        g_hash_table_replace (ap_hash, ap->essid, ap);
                  else
                        g_free (ap);
            } else
                  g_hash_table_insert (ap_hash, ap->essid, ap);
      }

      g_hash_table_foreach (ap_hash, hash_to_list, &list);
      list = g_slist_sort (list, ap_compare);

      for (iter = list; iter != NULL; iter = iter->next) {
            APInfo *ap = iter->data;
            float val = 0.0;
            char *escaped_essid;

            /*
             * The results of this vary drastically by driver.  Some
             * return 0 for quality, some return 0 for max.
             */
            if (max > 0.0 && ap->quality > 0)
                  val = log ((float) ap->quality) / log ((float) max);
            if (val > 1.0)
                  val = 1.0;

            escaped_essid = netcommon_escape_argument (ap->essid);

            g_string_append_printf (msg, " %s %f %d", escaped_essid, val,
                              ap->is_encrypted);
            g_free (escaped_essid);
      }

      netcommon_send_message (channel, msg->str, NULL);

      g_string_free (msg, TRUE);
      g_slist_free (list);
      g_slist_free (aps);
      g_hash_table_destroy (ap_hash);
}

static gboolean
read_scanning_results (gpointer user_data)
{
      ScanningInfo *si = user_data;
      char buf[IW_SCAN_MAX_DATA];
      struct iw_event event;
      struct iw_range range;
      struct stream_descr stream;
      struct iwreq rq;
      APInfo *old_ap = NULL, *ap;
      GSList *aps = NULL;
      int ret;

      rq.u.data.pointer = buf;
      rq.u.data.length = IW_SCAN_MAX_DATA;
      rq.u.data.flags = 0;
      strncpy (rq.ifr_name, si->interface, IFNAMSIZ);
      if (ioctl (si->fd, SIOCGIWSCAN, &rq) < 0) {
            if (errno == EAGAIN)
                  return TRUE;      /* results not yet ready; requeue */
            else
                  goto out;   /* error */
      }

      if (rq.u.data.length <= 0)
            goto out;

      iw_get_range_info (si->fd, si->interface, &range);

      iw_init_event_stream (&stream, buf, rq.u.data.length); 

      do {
#ifdef IW_EXTRACT_EVENT_STREAM_OLD
            ret = iw_extract_event_stream (&stream, &event);
#else
            ret = iw_extract_event_stream (&stream, &event, 
                                               range.we_version_compiled);
#endif
            if (ret > 0) {
                  ap = parse_scanning_event (&event, old_ap);
                  if (ap != old_ap)
                        aps = g_slist_prepend (aps, ap);
                  old_ap = ap;
            }
      } while (ret > 0);

      send_scanning_results (si->channel, si->interface, aps,
                         range.max_qual.qual);

out:
      close (si->fd);   
      g_free (si->interface);
      g_free (si);

      return FALSE;
}

static gboolean
is_valid_ap_address (const char *ap_addr)
{
      /*
       * Check for an invalid mac address.  This seems to vary
       * from driver to driver, but popular ones include all
       * zeros, all fours, and all F's.
       *
       * The all F's check turns up false positives on crappy Atheros
       * cards.  So we don't check for that.
       */
      if (strcmp (ap_addr, "00:00:00:00:00:00") == 0 ||
          strcmp (ap_addr, "44:44:44:44:44:44") == 0)
            return FALSE;
      else
            return TRUE;
}

static float
netdaemon_get_link_quality (int fd, const char *interface)
{
      struct iw_statistics stats;
      float quality = 0.0;

      if (iw_get_stats (fd, interface, &stats, NULL, 0) >= 0) {
            struct iw_range range;

            if (iw_get_range_info (fd, interface, &range) >= 0 &&
                range.max_qual.qual > 0.0 && stats.qual.qual > 0) {
                  int max, qual;

                  max = range.max_qual.qual;
                  qual = stats.qual.qual;

                  if (max <= 20 && MAX_QUAL_OVERRIDE > qual)
                        max = MAX_QUAL_OVERRIDE;
                  quality = log ((float) qual) / log ((float) max);
                  if (quality > 1.0)
                        quality = 1.0;
            }
      }

      return quality;
}

static void
netdaemon_do_get_accesspoints (GIOChannel *channel, char **args)
{
      const char *interface;
      struct iwreq wrq;
      int fd;

      interface = verify_interface (args[1]);
      if (!interface) {
            g_warning ("invalid interface: %s\n", args[1]);
            return;
      }

      fd = iw_sockets_open ();
      if (fd < 0)
            return;

      strncpy (wrq.ifr_name, interface, IFNAMSIZ);
      if (ioctl (fd, SIOCSIWSCAN, &wrq) >= 0) {
            ScanningInfo *si;

            si = g_new (ScanningInfo, 1);
            si->channel = channel;
            si->interface = g_strdup (interface);
            si->fd = fd;
            g_timeout_add (500, read_scanning_results, si);
            return;
      } else {
            /* Just send info about our current access point */
            struct iwreq wrq;
            char essid[IW_ESSID_MAX_SIZE + 1];
            char key[IW_ENCODING_TOKEN_MAX];
            gboolean is_encrypted;
            float quality;
            char *msg = NULL;
            gboolean error = FALSE;

            strncpy (wrq.ifr_name, interface, IFNAMSIZ);
            if (ioctl (fd, SIOCGIWNAME, &wrq) < 0) {
                  error = TRUE;
                  goto out;
            }

            /* AP Ethernet address */
            strncpy (wrq.ifr_name, interface, IFNAMSIZ);
            if (ioctl (fd, SIOCGIWAP, &wrq) >= 0) {
                  char ap_addr[128];

                  memset (ap_addr, 0, 128);
                  iw_ether_ntop ( (const struct ether_addr *) wrq.u.ap_addr.sa_data, ap_addr);

                  if (!is_valid_ap_address (ap_addr)) {
                        error = TRUE;
                        goto out;
                  }
            }

            /* ESSID */
            memset (essid, '\0', IW_ESSID_MAX_SIZE + 1);
            wrq.u.essid.pointer = (caddr_t) essid;
            wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
            wrq.u.essid.flags = 0;
            strncpy (wrq.ifr_name, interface, IFNAMSIZ);
            if (ioctl (fd, SIOCGIWESSID, &wrq) < 0) {
                  error = TRUE;
                  goto out;
            }
            essid[wrq.u.essid.length] = '\0';

            /* Crypto */
            memset (key, 0, IW_ENCODING_TOKEN_MAX);
            wrq.u.data.pointer = (caddr_t) key;
            wrq.u.data.length = IW_ENCODING_TOKEN_MAX;
            wrq.u.data.flags = 0;

            strncpy (wrq.ifr_name, interface, IFNAMSIZ);
            if (ioctl (fd, SIOCGIWENCODE, &wrq) < 0 ||
                wrq.u.data.length == 0 ||
                wrq.u.data.flags & IW_ENCODE_DISABLED)
                  is_encrypted = FALSE;
            else
                  is_encrypted = TRUE;

            quality = netdaemon_get_link_quality (fd, interface);

out:
            if (error)
                  msg = g_strdup_printf ("accesspoints %s", interface);
            else {
                  char *escaped_essid;

                  escaped_essid = netcommon_escape_argument (essid);

                  msg = g_strdup_printf ("accesspoints %s %s %f %d",
                                     interface, escaped_essid,
                                     quality, is_encrypted);

                  g_free (escaped_essid);
            }

            netcommon_send_message (channel, msg, NULL);

            close (fd);
            g_free (msg);
      }
}

static void
netdaemon_do_change_essid_debian (GIOChannel *channel, char **args)
{
  const char *argv[7];
  GError *err = NULL;

  argv[0] = g_strdup("/sbin/iwconfig");
  argv[1] = args[1];
  argv[2] = g_strdup("essid");
  argv[3] = args[2];
  if (strcmp(args[3],"")) {
    argv[4] = g_strdup("key");
    argv[5] = args[3];
    argv[6] = NULL;
  } else {
    argv[4] = NULL;
    argv[5] = NULL;
    argv[6] = NULL;
  }

  if (!g_spawn_sync (NULL, (char **) argv, NULL, 0, NULL, NULL,
                 NULL, NULL, NULL, &err)) {
    g_warning("Unable to execute iwconfig: %s",
            err->message);
    g_error_free(err);
  }

  return;
}


static void
netdaemon_do_change_essid_suse (GIOChannel *channel, char **args)
{
      const char *interface, *essid, *key;

      interface = verify_interface (args[1]);
      if (!interface) {
            g_warning ("invalid interface: %s\n", args[1]);
            return;
      }
      essid = netcommon_verify_string (args[2]);
      if (!essid) {
            g_warning ("invalid essid: %s\n", args[2]);
            return;
      }
      key = netcommon_verify_string (args[3]);
      if (!key) {
            g_warning ("invalid key: %s\n", args[3]);
            return;
      }

      if (modify_interface_config (interface,   /* interface */
                             "dhcp",      /* bootproto */
                             NULL,  /* ip address */
                             NULL,  /* netmask */
                             "Managed",   /* Wireless mode */
                             essid, /* essid */
                             key))  /* key */
            netdaemon_do_change_active (channel, args);
}

/*
 * handler to change the current wireless interface's ESSID
 *
 * args[0] is 'essid' (ignored)
 * args[1] is the current interface
 * args[2] is the new ESSID
 * args[3] is the new ESSID's wireless encryption key ("" if no key)
 */

static void
netdaemon_do_change_essid (GIOChannel *channel, char **args)
{
      if (strcmp (get_platform (), SUSE_PLATFORM_NAME) == 0)
            return netdaemon_do_change_essid_suse (channel, args);
      if (strcmp (get_platform (), GENTOO_PLATFORM_NAME) == 0)
            return netdaemon_do_change_essid_suse (channel, args);
      if (strcmp (get_platform (), DEBIAN_PLATFORM_NAME) == 0)
            return netdaemon_do_change_essid_debian (channel, args);

      return;
}


static void
netdaemon_do_get_wireless (GIOChannel *channel, char **args)
{
      const char *interface;
      int skfd;
      struct iwreq wrq;
      char essid[IW_ESSID_MAX_SIZE + 1];
      char key[IW_ENCODING_TOKEN_MAX];
      gboolean encrypted;
      char *escaped_essid, *msg;
      float quality;

      interface = verify_interface (args[1]);
      if (!interface) {
            g_warning ("invalid interface: %s\n", args[1]);
            return;
      }

      skfd = iw_sockets_open ();
      if (skfd < 0)
            return;

      strncpy (wrq.ifr_name, interface, IFNAMSIZ);
      if (ioctl (skfd, SIOCGIWNAME, &wrq) < 0)
            goto out;

      /* ESSID */
      memset (essid, 0, IW_ESSID_MAX_SIZE + 1);
      wrq.u.essid.pointer = (caddr_t) essid;
      wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
      wrq.u.essid.flags = 0;
      strncpy (wrq.ifr_name, interface, IFNAMSIZ);
      if (ioctl (skfd, SIOCGIWESSID, &wrq) < 0 || *essid == '\0')
            goto out;

      /* AP Ethernet address */
      strncpy (wrq.ifr_name, interface, IFNAMSIZ);
      if (ioctl (skfd, SIOCGIWAP, &wrq) >= 0) {
            char ap_addr[128];

            memset (ap_addr, 0, 128);
            iw_ether_ntop ( (const struct ether_addr *) wrq.u.ap_addr.sa_data, ap_addr);

            if (!is_valid_ap_address (ap_addr)) {
                  /* This is kind of ugly... */
                  char *argv[] = { "essid", (char *) interface,
                               "any", "", NULL };

                  netdaemon_do_change_essid (channel, argv);
                  goto out;
            }
      }

      /* Crypto */
      memset (key, 0, IW_ENCODING_TOKEN_MAX);
      wrq.u.data.pointer = (caddr_t) key;
      wrq.u.data.length = IW_ENCODING_TOKEN_MAX;
      wrq.u.data.flags = 0;
      strncpy (wrq.ifr_name, interface, IFNAMSIZ);
      if (ioctl (skfd, SIOCGIWENCODE, &wrq) < 0 ||
          wrq.u.data.length == 0 ||
          wrq.u.data.flags & IW_ENCODE_DISABLED)
            encrypted = FALSE;
      else
            encrypted = TRUE;

      escaped_essid = netcommon_escape_argument (essid);
      quality = netdaemon_get_link_quality (skfd, interface);
      msg = g_strdup_printf ("wireless %s %d %f", escaped_essid,
                         encrypted, quality);

      netcommon_send_message (channel, msg, NULL);
      g_free (escaped_essid);
      g_free (msg);

out:
      close (skfd);
}

static gboolean
connection_hup (GIOChannel *channel, GIOCondition cond G_GNUC_UNUSED,
            gpointer user_data G_GNUC_UNUSED)
{
      current_connections = g_slist_remove (current_connections, channel);

      return FALSE;
}

static gboolean
netdaemon_accept_client (GIOChannel *source, GIOCondition condition,
                   gpointer data G_GNUC_UNUSED)
{
      if (condition & G_IO_IN) {
            GIOChannel *gio;
            int fd;
            int flags;

            fd = accept (g_io_channel_unix_get_fd (source), NULL, NULL);
            if (fd < 0)
                  g_error ("Accept failed: %s\n", g_strerror (errno));

            flags = fcntl (fd, F_GETFL, 0);
            fcntl (fd, F_SETFL, flags | O_NONBLOCK);

            gio = g_io_channel_unix_new (fd);
            if (!gio)
                  g_error ("Cannot create new GIOChannel!\n");

            g_io_channel_set_encoding (gio, NULL, NULL);

            netcommon_watch_channel (gio, hash);

            /* Maintain connection lifecycle */
            current_connections = g_slist_prepend (current_connections,
                                           gio);
            g_io_add_watch (gio, G_IO_HUP, connection_hup, NULL);
            g_io_channel_unref(gio);
      }

      /* our listener socket hung up - we are dead */
      if (condition & G_IO_HUP)
            g_error ("Server listening socket died!\n");

      return TRUE;
}

static void
sig_handler (int signo G_GNUC_UNUSED)
{
      unlink (NETDAEMON_SOCKET);
      exit (0);
}

static void
netdaemon_init_socket (void)
{
      struct sockaddr_un addr;
      GIOChannel *gio;
      int fd;

      /* ignore SIGPIPE */
      signal (SIGPIPE, SIG_IGN);

      fd = socket (PF_UNIX, SOCK_STREAM, 0);
      if (fd < 0)
            g_error ("Cannot create socket!");

      unlink (NETDAEMON_SOCKET);

      memset (&addr, 0, sizeof (addr));
      addr.sun_family = AF_UNIX;
      snprintf (addr.sun_path, sizeof (addr.sun_path), NETDAEMON_SOCKET);

      if (bind (fd, (struct sockaddr *) &addr, sizeof (addr)))
            g_error ("Bind on socket failed: %s\n", g_strerror (errno));

      if (listen (fd, 5))
            g_error ("Listen on socket failed: %s\n", g_strerror (errno));

      chown (NETDAEMON_SOCKET, 0, 0);
      chmod (NETDAEMON_SOCKET, 0666);

      gio = g_io_channel_unix_new (fd);
      if (!gio)
            g_error ("Cannot create new GIOChannel!\n");

      g_io_channel_set_encoding (gio, NULL, NULL);
      g_io_channel_set_buffered (gio, FALSE);
      g_io_channel_set_close_on_unref (gio, TRUE);

      if (!g_io_add_watch (gio, G_IO_IN |G_IO_HUP, netdaemon_accept_client,
                       NULL))
            g_error ("Cannot add watch on GIOChannel\n");

      /*
       * the watch increments the ref count, so if we decrement it here and
       * set close-on-unref, the channel will automatically shutdown when the
       * watch returns FALSE
       */
      g_io_channel_set_close_on_unref (gio, TRUE);
      g_io_channel_unref (gio);
}

static void
netdaemon_init_hash (void)
{
      hash = g_hash_table_new (g_str_hash, g_str_equal);
      if (!hash)
            g_error ("Failed to initialize the hash table\n");

      g_hash_table_insert (hash, "change", netdaemon_do_change_active);
      g_hash_table_insert (hash, "disconnect", netdaemon_do_disconnect);
      g_hash_table_insert (hash, "list_interfaces",
                       netdaemon_do_list_interfaces);
      g_hash_table_insert (hash, "get_active", netdaemon_do_get_active);
      g_hash_table_insert (hash, "wireless", netdaemon_do_get_wireless);
      g_hash_table_insert (hash, "accesspoints",
                       netdaemon_do_get_accesspoints);
      g_hash_table_insert (hash, "essid", netdaemon_do_change_essid);

#if 0
      g_hash_table_insert (hash, "setup_wap", netdaemon_do_setup_wap);
      g_hash_table_insert (hash, "share_interface", netdaemon_do_share_interface);
      g_hash_table_insert (hash, "start_forward", netdaemon_do_start_forward);
      g_hash_table_insert (hash, "stop_forward", netdaemon_do_stop_forward);
#endif
}

static gboolean
poll_interfaces (gpointer user_data G_GNUC_UNUSED)
{
      GSList *iface_list;
      GSList *iter;

      /*
       * Skip this round of polling since we just activated an interface
       * and don't want to be overzealous in case the interface doesn't
       * come up right away.
       */
      if (skip_poll) {
            skip_poll = FALSE;
            return TRUE;
      }

      iface_list = get_interfaces ();

      for (iter = current_connections; iter != NULL; iter = iter->next) {
            GIOChannel *channel = iter->data;

            netdaemon_do_list_interfaces (channel, NULL);
            netdaemon_do_get_active (channel, NULL);
      }

      g_slist_foreach (iface_list, (GFunc) g_free, NULL);
      g_slist_free (iface_list);

      return TRUE;
}

static void
netdaemon_init_timeouts (void)
{
      g_timeout_add (TIMEOUT_INTERVAL, poll_interfaces, NULL);
}

int
main (void)
{
      GMainLoop *loop;
      gboolean daemonize = TRUE;

      /* FIXME: add cmdline option parsing here, e.g. for daemonize */

      if (daemonize) {
            int fd;
            pid_t child;

            if (chdir ("/") < 0)
                  g_error ("Cannot chdir to /: %s", g_strerror (errno));

            child = fork ();
            switch (child) {
            case 0:           /* child */
                  fd = open ("/dev/null", O_RDWR);
                  if (fd > 0) {
                        dup2 (fd, 0);
                        dup2 (fd, 1);
                        dup2 (fd, 2);
                  }
                  umask (022);
                  setsid ();
                  break;
            case -1:    /* error */
                  g_error ("Cannot fork(): %s", g_strerror (errno));
            default:    /* parent */
                  return 0;
            }
      }

      signal (SIGINT, sig_handler);

      loop = g_main_loop_new (NULL, FALSE);
      netdaemon_init_socket ();
      netdaemon_init_hash ();
      netdaemon_init_timeouts ();
      g_main_loop_run (loop);

      return 0;
}

Generated by  Doxygen 1.6.0   Back to index