From 474651014faab2205231ef12a74f125a785f7619 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Fri, 19 Jun 2009 20:02:07 +0100 Subject: [PATCH 2/2] Add Skyhook provider Based on the reverse-engineered Loki.com service. --- configure.ac | 13 + providers/Makefile.am | 3 +- providers/skyhook/Makefile.am | 33 ++ providers/skyhook/geoclue-skyhook.c | 321 ++++++++++++++++++++ providers/skyhook/geoclue-skyhook.provider | 8 + ...reedesktop.Geoclue.Providers.Skyhook.service.in | 3 + 6 files changed, 380 insertions(+), 1 deletions(-) create mode 100644 providers/skyhook/Makefile.am create mode 100644 providers/skyhook/geoclue-skyhook.c create mode 100644 providers/skyhook/geoclue-skyhook.provider create mode 100644 providers/skyhook/org.freedesktop.Geoclue.Providers.Skyhook.service.in diff --git a/configure.ac b/configure.ac index 3109048..f107b53 100644 --- a/configure.ac +++ b/configure.ac @@ -125,7 +125,19 @@ fi AC_SUBST(GPSD_LIBS) AC_SUBST(GPSD_CFLAGS) +PKG_CHECK_MODULES(SKYHOOK, [ + libsoup-gnome-2.4 +], HAVE_SKYHOOK=yes, HAVE_SKYHOOK=no) +if test "x$HAVE_SKYHOOK" = "xyes"; then + PROVIDER_SUBDIRS="$PROVIDER_SUBDIRS skyhook" +else + NO_BUILD_PROVIDERS="$NO_BUILD_PROVIDERS skyhook" +fi +AC_SUBST(SKYHOOK_LIBS) +AC_SUBST(SKYHOOK_CFLAGS) + AC_SUBST(PROVIDER_SUBDIRS) +AC_SUBST(NO_BUILD_PROVIDERS) AC_CONFIG_FILES([ geoclue.pc @@ -148,6 +160,7 @@ providers/plazes/Makefile providers/localnet/Makefile providers/yahoo/Makefile providers/gsmloc/Makefile +providers/skyhook/Makefile src/Makefile ]) diff --git a/providers/Makefile.am b/providers/Makefile.am index af11cad..7a966e3 100644 --- a/providers/Makefile.am +++ b/providers/Makefile.am @@ -1,2 +1,3 @@ -SUBDIRS = @PROVIDER_SUBDIRS@ +SUBDIRS = $(PROVIDER_SUBDIRS) +DIST_SUBDIRS = $(PROVIDER_SUBDIRS) $(NO_BUILD_PROVIDERS) diff --git a/providers/skyhook/Makefile.am b/providers/skyhook/Makefile.am new file mode 100644 index 0000000..6353d1b --- /dev/null +++ b/providers/skyhook/Makefile.am @@ -0,0 +1,33 @@ +libexec_PROGRAMS = \ + geoclue-skyhook + +geoclue_skyhook_SOURCES = \ + geoclue-skyhook.c + +geoclue_skyhook_CFLAGS = \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + $(GEOCLUE_CFLAGS) \ + $(SKYHOOK_CFLAGS) + +geoclue_skyhook_LDADD = \ + $(GEOCLUE_LIBS) \ + $(SKYHOOK_LIBS) \ + $(top_builddir)/geoclue/libgeoclue.la + +providersdir = $(datadir)/geoclue-providers +providers_DATA = geoclue-skyhook.provider + +servicedir = $(DBUS_SERVICES_DIR) +service_in_files = org.freedesktop.Geoclue.Providers.Skyhook.service.in +service_DATA = $(service_in_files:.service.in=.service) + +$(service_DATA): $(service_in_files) Makefile + @sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@ + +EXTRA_DIST = \ + $(service_in_files) \ + $(providers_DATA) + +DISTCLEANFILES = \ + $(service_DATA) diff --git a/providers/skyhook/geoclue-skyhook.c b/providers/skyhook/geoclue-skyhook.c new file mode 100644 index 0000000..71fa2a9 --- /dev/null +++ b/providers/skyhook/geoclue-skyhook.c @@ -0,0 +1,321 @@ +/* + * Geoclue + * geoclue-skyhook.c - A skyhook.com-based Address/Position provider + * + * Author: Bastien Nocera + * Copyright 2009 Bastien Nocera + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define GEOCLUE_DBUS_SERVICE_SKYHOOK "org.freedesktop.Geoclue.Providers.Skyhook" +#define GEOCLUE_DBUS_PATH_SKYHOOK "/org/freedesktop/Geoclue/Providers/Skyhook" +#define SKYHOOK_URL "https://api.skyhookwireless.com/wps2/location" +#define SKYHOOK_LAT_XPATH "//prefix:latitude" +#define SKYHOOK_LON_XPATH "//prefix:longitude" +#define USER_AGENT "Geoclue "VERSION + +#define GEOCLUE_TYPE_SKYHOOK (geoclue_skyhook_get_type ()) +#define GEOCLUE_SKYHOOK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEOCLUE_TYPE_SKYHOOK, GeoclueSkyhook)) + +#define QUERY "betajs.loki.com%s-50" + +typedef struct _GeoclueSkyhook { + GcProvider parent; + GMainLoop *loop; + SoupSession *session; +} GeoclueSkyhook; + +typedef struct _GeoclueSkyhookClass { + GcProviderClass parent_class; +} GeoclueSkyhookClass; + + +static void geoclue_skyhook_init (GeoclueSkyhook *skyhook); +static void geoclue_skyhook_position_init (GcIfacePositionClass *iface); + +G_DEFINE_TYPE_WITH_CODE (GeoclueSkyhook, geoclue_skyhook, GC_TYPE_PROVIDER, + G_IMPLEMENT_INTERFACE (GC_TYPE_IFACE_POSITION, + geoclue_skyhook_position_init)) + + +/* Geoclue interface implementation */ +static gboolean +geoclue_skyhook_get_status (GcIfaceGeoclue *iface, + GeoclueStatus *status, + GError **error) +{ + /* Assume available so long as all the requirements are satisfied + ie: Network is available */ + *status = GEOCLUE_STATUS_AVAILABLE; + return TRUE; +} + +static void +_shutdown (GcProvider *provider) +{ + GeoclueSkyhook *skyhook = GEOCLUE_SKYHOOK (provider); + g_main_loop_quit (skyhook->loop); +} + +#define MAC_LEN 18 + +static char * +get_mac_address (void) + { + /* this is an ugly hack, but it seems there is no easy + * ioctl-based way to get the mac address of the router. This + * implementation expects the system to have netstat, grep and awk + * */ + + FILE *in; + char mac[MAC_LEN]; + int i; + + /*for some reason netstat or /proc/net/arp isn't always ready + * when a connection is already up... Try a couple of times */ + for (i=0; i<10; i++) { + if (!(in = popen ("ROUTER_IP=`netstat -rn | grep '^0.0.0.0 ' | awk '{ print $2 }'` && grep \"^$ROUTER_IP \" /proc/net/arp | awk '{print $4}'", "r"))) { + g_warning ("popen failed"); + return NULL; + } + + if (!(fgets (mac, MAC_LEN, in))) { + if (errno != ENOENT && errno != EAGAIN) { + g_debug ("error %d", errno); + return NULL; + } + /* try again */ + pclose (in); + g_debug ("trying again..."); + g_usleep (200); + continue; + } + pclose (in); + return g_strdup (mac); + } + return NULL; +} + +static char * +create_post_query (void) +{ + char *mac, *query; + char **split; + + mac = get_mac_address (); + if (mac == NULL) + return NULL; + /* Remove the ":" */ + split = g_strsplit (mac, ":", -1); + g_free (mac); + if (split == NULL) + return NULL; + mac = g_strjoinv ("", split); + g_strfreev (split); + + query = g_strdup_printf (QUERY, mac); + g_free (mac); + + return query; +} + +static gboolean +get_double (xmlXPathContext *xpath_ctx, char *xpath, gdouble *value) +{ + xmlXPathObject *obj = NULL; + + obj = xmlXPathEvalExpression (BAD_CAST (xpath), xpath_ctx); + if (obj && + (!obj->nodesetval || xmlXPathNodeSetIsEmpty (obj->nodesetval))) { + xmlXPathFreeObject (obj); + return FALSE; + } + *value = xmlXPathCastNodeSetToNumber (obj->nodesetval); + xmlXPathFreeObject (obj); + return TRUE; +} + +static gboolean +parse_response (const char *body, gdouble *latitude, gdouble *longitude) +{ + xmlDocPtr doc; + xmlXPathContext *xpath_ctx; + gboolean ret = TRUE; + + doc = xmlParseDoc (BAD_CAST (body)); + if (!doc) + return FALSE; + + xpath_ctx = xmlXPathNewContext(doc); + if (!xpath_ctx) { + xmlFreeDoc (doc); + return FALSE; + } + xmlXPathRegisterNs (xpath_ctx, BAD_CAST ("prefix"), BAD_CAST("http://skyhookwireless.com/wps/2005")); + if (get_double (xpath_ctx, SKYHOOK_LAT_XPATH, latitude) == FALSE) { + ret = FALSE; + } else if (get_double (xpath_ctx, SKYHOOK_LON_XPATH, longitude) == FALSE) { + ret = FALSE; + } + xmlXPathFreeContext (xpath_ctx); + xmlFreeDoc (doc); + + return ret; +} + +/* Position interface implementation */ + +static gboolean +geoclue_skyhook_get_position (GcIfacePosition *iface, + GeocluePositionFields *fields, + int *timestamp, + double *latitude, + double *longitude, + double *altitude, + GeoclueAccuracy **accuracy, + GError **error) +{ + GeoclueSkyhook *skyhook; + char *query; + SoupMessage *msg; + + skyhook = (GEOCLUE_SKYHOOK (iface)); + + *fields = GEOCLUE_POSITION_FIELDS_NONE; + if (timestamp) + *timestamp = time (NULL); + + query = create_post_query (); + if (query == NULL) { + g_set_error (error, GEOCLUE_ERROR, + GEOCLUE_ERROR_NOT_AVAILABLE, + "Router mac address query failed"); + /* TODO: set status == error ? */ + return FALSE; + } + + msg = soup_message_new ("POST", SKYHOOK_URL); + soup_message_headers_append (msg->request_headers, "User-Agent", USER_AGENT); + soup_message_set_request (msg, + "text/xml", + SOUP_MEMORY_TAKE, + query, + strlen (query)); + soup_session_send_message (skyhook->session, msg); + if (msg->response_body == NULL || msg->response_body->data == NULL) { + g_object_unref (msg); + g_set_error (error, GEOCLUE_ERROR, + GEOCLUE_ERROR_NOT_AVAILABLE, + "Failed to query web service"); + return FALSE; + } + + if (strstr (msg->response_body->data, "") != NULL) { + g_object_unref (msg); + g_set_error (error, GEOCLUE_ERROR, + GEOCLUE_ERROR_NOT_AVAILABLE, + "Web service returned an error"); + return FALSE; + } + + if (parse_response (msg->response_body->data, latitude, longitude) == FALSE) { + g_object_unref (msg); + g_set_error (error, GEOCLUE_ERROR, + GEOCLUE_ERROR_NOT_AVAILABLE, + "Couldn't parse response from web service"); + return FALSE; + } + + if (latitude) + *fields |= GEOCLUE_POSITION_FIELDS_LATITUDE; + if (longitude) + *fields |= GEOCLUE_POSITION_FIELDS_LONGITUDE; + + if (accuracy) { + if (*fields == GEOCLUE_POSITION_FIELDS_NONE) { + *accuracy = geoclue_accuracy_new (GEOCLUE_ACCURACY_LEVEL_NONE, + 0, 0); + } else { + /* Educated guess. Skyhook are typically hand pointed on + * a map, or geocoded from address, so should be fairly + * accurate */ + *accuracy = geoclue_accuracy_new (GEOCLUE_ACCURACY_LEVEL_STREET, + 0, 0); + } + } + + return TRUE; +} + +static void +geoclue_skyhook_finalize (GObject *obj) +{ + GeoclueSkyhook *skyhook = GEOCLUE_SKYHOOK (obj); + + g_object_unref (skyhook->session); + + ((GObjectClass *) geoclue_skyhook_parent_class)->finalize (obj); +} + + +/* Initialization */ + +static void +geoclue_skyhook_class_init (GeoclueSkyhookClass *klass) +{ + GcProviderClass *p_class = (GcProviderClass *)klass; + GObjectClass *o_class = (GObjectClass *)klass; + + p_class->shutdown = _shutdown; + p_class->get_status = geoclue_skyhook_get_status; + + o_class->finalize = geoclue_skyhook_finalize; +} + +static void +geoclue_skyhook_init (GeoclueSkyhook *skyhook) +{ + gc_provider_set_details (GC_PROVIDER (skyhook), + GEOCLUE_DBUS_SERVICE_SKYHOOK, + GEOCLUE_DBUS_PATH_SKYHOOK, + "Skyhook", "Skyhook.com based provider, uses gateway mac address to locate"); + skyhook->session = soup_session_sync_new (); +} + +static void +geoclue_skyhook_position_init (GcIfacePositionClass *iface) +{ + iface->get_position = geoclue_skyhook_get_position; +} + +int +main() +{ + g_type_init(); + g_thread_init (NULL); + + GeoclueSkyhook *o = g_object_new (GEOCLUE_TYPE_SKYHOOK, NULL); + o->loop = g_main_loop_new (NULL, TRUE); + + g_main_loop_run (o->loop); + + g_main_loop_unref (o->loop); + g_object_unref (o); + + return 0; +} diff --git a/providers/skyhook/geoclue-skyhook.provider b/providers/skyhook/geoclue-skyhook.provider new file mode 100644 index 0000000..6790ea9 --- /dev/null +++ b/providers/skyhook/geoclue-skyhook.provider @@ -0,0 +1,8 @@ +[Geoclue Provider] +Name=Plazes +Service=org.freedesktop.Geoclue.Providers.Plazes +Path=/org/freedesktop/Geoclue/Providers/Plazes +Accuracy=Street +Requires=RequiresNetwork +Provides=ProvidesCacheableOnConnection +Interfaces=org.freedesktop.Geoclue.Position;org.freedesktop.Geoclue.Address diff --git a/providers/skyhook/org.freedesktop.Geoclue.Providers.Skyhook.service.in b/providers/skyhook/org.freedesktop.Geoclue.Providers.Skyhook.service.in new file mode 100644 index 0000000..e65e672 --- /dev/null +++ b/providers/skyhook/org.freedesktop.Geoclue.Providers.Skyhook.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.Geoclue.Providers.Plazes +Exec=@libexecdir@/geoclue-plazes -- 1.6.2.2