RSSyl: Allow use of .netrc by libcurl. Bug/enhancement #3309, by Vincent Pelletier
[claws.git] / src / plugins / geolocation / geolocation_plugin.c
1 /* GeoLocation plugin for Claws-Mail
2  * Copyright (C) 2009 Holger Berndt
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #  include "config.h"
20 #  include "claws-features.h"
21 #endif
22
23 #include <glib.h>
24 #include <glib/gi18n.h>
25
26 #include <gtk/gtk.h>
27 #include <gdk-pixbuf/gdk-pixbuf.h>
28
29 #include "pixmap_earth.h"
30
31 #include "common/plugin.h"
32 #include "common/version.h"
33 #include "common/utils.h"
34 #include "common/hooks.h"
35 #include "prefs_common.h"
36 #include "mimeview.h"
37 #include "procheader.h"
38 #include "main.h"
39
40 #include <libsoup/soup.h>
41
42 #include <champlain/champlain.h>
43 #include <champlain-gtk/champlain-gtk.h>
44 #include <clutter-gtk/clutter-gtk.h>
45
46 #include <string.h>
47
48
49 /* For now, make extracting the ip address string from the Received header
50  * as simple as possible. This should be made more accurate, some day.
51  * Logic:
52  * Find four 1-3 digit numbers, separated by dots, encosed in word boundaries,
53  * where the word "from" must be ahead, and the word "by" must follow, but must
54  * not be between the "from" and the digits.
55  * The result will be in backreference 2, the individual numbers in 3 to 6 */
56 #define EXTRACT_IP_FROM_RECEIVED_HEADER_REGEX "\\bfrom\\b((?!\\bby\\b).)*(\\b(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\b).*\\bby\\b"
57
58 #define EXTRACT_LAT_LON_FROM_HOSTIP_RESPONSE_REGEX "<gml:coordinates>(-?[\\d.]+),(-?[\\d.]+)</gml:coordinates>"
59
60 #define GEOLOCATION_CONTAINER_NAME "cm_geolocation_container"
61 #define GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON "togglebutton"
62 #define GEOLOCATION_CONTAINER_DATA_LABEL "label"
63
64 #define GEOLOCATION_TOGGLE_BUTTON_DATA_CB_ID "toggled_cb_id"
65 #define GEOLOCATION_TOGGLE_BUTTON_DATA_IP "ip"
66
67 #define GEOLOCATION_NOTEBOOK_DATA_MAPWIDGET "mapwidget"
68
69 #define GEOLOCATION_MAP_DATA_VIEW "view"
70 #define GEOLOCATION_MAP_DATA_ORIGIN_MARKER "originmarker"
71
72 #define BUFFSIZE 8192
73
74 #define HOSTIP_URL "http://api.hostip.info/"
75
76 #define COUNTRY_RESOLVER_SUFFIX "zz.countries.nerd.dk"
77
78
79 static gboolean my_messageview_show_hook(gpointer, gpointer);
80 static guint hook_messageview_show;
81
82 static GRegex *ip_from_received_header_regex = NULL;
83 static GRegex *lat_lon_from_hostip_response_regex = NULL;
84
85 static GSList *container_list = NULL;
86
87 static GHashTable *iso_country_hash = NULL;
88
89 static gchar* get_ip_from_received_header(gchar *received)
90 {
91   GMatchInfo *match_info;
92   gchar *ip;
93   gchar *n1;
94
95   g_regex_match(ip_from_received_header_regex, received, 0, &match_info);
96   ip = g_match_info_fetch(match_info, 2);
97
98   if (ip) {
99     /* check for loopback and local subnetworks */
100     if(!strcmp(ip, "127.0.0.1")) {
101       g_free(ip);
102       ip = NULL;
103     }
104     else {
105       n1 = g_match_info_fetch(match_info, 3);
106       if(n1) {
107         /* 10.x.x.x */
108         if(!strcmp(n1, "10")) {
109           g_free(ip);
110           ip = NULL;
111         }
112         /* 192.168.x.x */
113         else if(!strcmp(n1, "192")) {
114           gchar *n2;
115           n2 = g_match_info_fetch(match_info, 4);
116           if(n2 && !strcmp(n2, "168")) {
117             g_free(ip);
118             ip = NULL;
119           }
120         }
121         /* 172.16.xx to 172.31.x.x */
122         else if(!strcmp(n1, "172")) {
123           gchar *n2;
124           int i2;
125           n2 = g_match_info_fetch(match_info, 4);
126           if(n2) {
127             i2 = atoi(n2);
128             if((i2 >= 16) && (i2 <= 31)) {
129               g_free(ip);
130               ip = NULL;
131             }
132           }
133         }
134       }
135     }
136   }
137
138   g_match_info_free(match_info);
139   return ip;
140 }
141
142 enum {
143   H_RECEIVED = 0
144 };
145
146 static HeaderEntry hentry[] = {
147     {"Received:", NULL, TRUE},
148     {NULL, NULL, FALSE}};
149
150 static gchar* get_ip_from_msginfo(MsgInfo *msginfo)
151 {
152   gchar *file;
153   struct stat ss;
154   FILE *fp;
155   gchar buf[BUFFSIZE];
156   gint hnum;
157   gchar *hp;
158   gchar *ip;
159   gchar *new_ip;
160
161   file = procmsg_get_message_file_path(msginfo);
162   g_return_val_if_fail(file, NULL);
163
164   if(g_stat(file, &ss) < 0) {
165     FILE_OP_ERROR(file, "stat");
166     return NULL;
167   }
168   if(!S_ISREG(ss.st_mode)) {
169     g_warning("mail file is not a regular file\n");
170     return NULL;
171   }
172
173   if((fp = g_fopen(file, "rb")) == NULL) {
174     FILE_OP_ERROR(file, "fopen");
175     return NULL;
176   }
177
178   ip = NULL;
179   new_ip = NULL;
180   while((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) != -1) {
181     switch(hnum) {
182     case H_RECEIVED:
183       hp = buf + strlen(hentry[hnum].name);
184       while(*hp == ' ' || *hp == '\t') hp++;
185       new_ip = get_ip_from_received_header(hp);
186       if(new_ip) {
187         g_free(ip);
188         ip = new_ip;
189       }
190       break;
191     default:
192       break;
193     }
194   }
195
196   fclose(fp);
197
198   g_free(file);
199   return ip;
200 }
201
202 /* inspired by GeoClue */
203 static gboolean get_lat_lon_from_ip(const gchar *ip, double *lat, double *lon)
204 {
205   gchar *url;
206   GMatchInfo *match_info;
207   gchar *slat;
208   gchar *slon;
209   SoupSession *session;
210   SoupMessage *msg;
211
212   /* fetch data from hostip.info */
213   url = g_strdup_printf("%s?ip=%s", HOSTIP_URL, ip);
214   session = soup_session_async_new();
215   msg = soup_message_new(SOUP_METHOD_GET, url);
216   g_free(url);
217
218   soup_session_send_message(session, msg);
219   g_object_unref(session);
220   if(!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code) || !msg->response_body->data)
221     return FALSE;
222
223   /* extract longitude and latitude from */
224   g_regex_match(lat_lon_from_hostip_response_regex, msg->response_body->data, 0, &match_info);
225   g_object_unref(msg);
226   slon = g_match_info_fetch(match_info, 1);
227   slat = g_match_info_fetch(match_info, 2);
228   g_match_info_free(match_info);
229
230   if(!slat || !slon)
231     return FALSE;
232
233   *lat = g_ascii_strtod(slat, NULL);
234   *lon = g_ascii_strtod(slon, NULL);
235
236   return TRUE;
237 }
238
239 /* try to look up country */
240 static const gchar* get_country_from_ip(const gchar *ip)
241 {
242   gchar **tok;
243   gchar *uri;
244   SoupAddress *addr;
245   const gchar *val = NULL;
246
247   tok = g_strsplit_set(ip, ".", 4);
248   if(!(tok[0] && tok[1] && tok[2] && tok[3] && !tok[4])) {
249     g_strfreev(tok);
250     debug_print("GeoLocation plugin: Error parsing ip address '%s'\n", ip);
251     return NULL;
252   }
253   uri = g_strjoin(".", tok[3], tok[2], tok[1], tok[0], COUNTRY_RESOLVER_SUFFIX, NULL);
254   g_strfreev(tok);
255
256   addr = soup_address_new(uri, SOUP_ADDRESS_ANY_PORT);
257   g_free(uri);
258
259   if(soup_address_resolve_sync(addr, NULL) == SOUP_STATUS_OK) {
260     const gchar *physical = soup_address_get_physical(addr);
261     val = g_hash_table_lookup(iso_country_hash, physical);
262   }
263
264   g_object_unref(addr);
265   return val;
266 }
267
268 static GtkWidget* create_map_widget()
269 {
270   GtkWidget *map;
271   ChamplainLayer *layer;
272   ChamplainView *view;
273   ClutterActor *origin_marker;
274   ClutterColor orange = { 0xf3, 0x94, 0x07, 0xbb };
275
276   /* create map widget */
277   map = gtk_champlain_embed_new();
278   view = gtk_champlain_embed_get_view(GTK_CHAMPLAIN_EMBED(map));
279   g_object_set(G_OBJECT(view), "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC, "zoom-level", 8, NULL);
280
281   /* mail origin marker */
282   layer = champlain_layer_new();
283   origin_marker = champlain_marker_new_with_text("mail origin", "Serif 14", NULL, NULL);
284   champlain_marker_set_color(CHAMPLAIN_MARKER(origin_marker), &orange);
285   clutter_container_add(CLUTTER_CONTAINER(layer), origin_marker, NULL);
286   champlain_view_add_layer(view, layer);
287
288   clutter_actor_show(CLUTTER_ACTOR(view));
289   clutter_actor_show(CLUTTER_ACTOR(layer));
290   clutter_actor_show(origin_marker);
291
292   g_object_set_data(G_OBJECT(map), GEOLOCATION_MAP_DATA_VIEW, view);
293   g_object_set_data(G_OBJECT(map), GEOLOCATION_MAP_DATA_ORIGIN_MARKER, origin_marker);
294
295   return map;
296 }
297
298 static int get_notebook_map_page_num(GtkWidget *notebook)
299 {
300   GtkWidget *wid;
301   int page_num = 0;
302
303   wid = g_object_get_data(G_OBJECT(notebook), GEOLOCATION_NOTEBOOK_DATA_MAPWIDGET);
304
305   if(!wid) {
306     /* create page */
307     GtkWidget *map;
308     map = create_map_widget();
309     page_num = gtk_notebook_append_page(GTK_NOTEBOOK(notebook), map, NULL);
310     gtk_widget_show_all(map);
311     if(page_num == -1)
312       page_num = 0;
313     else
314       g_object_set_data(G_OBJECT(notebook), GEOLOCATION_NOTEBOOK_DATA_MAPWIDGET, map);
315   }
316   else {
317     /* find page */
318     page_num = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), wid);
319     if(page_num == -1)
320       page_num = 0;
321   }
322
323   return page_num;
324 }
325
326 static void geolocation_button_toggled_cb(GtkToggleButton *button, gpointer data)
327 {
328   if(gtk_toggle_button_get_active(button)) {
329     gchar *ip;
330     double lat, lon;
331     gchar *text = NULL;
332     GtkWidget *container, *label;
333
334     ip = g_object_get_data(G_OBJECT(button), GEOLOCATION_TOGGLE_BUTTON_DATA_IP);
335     container = gtk_widget_get_parent(GTK_WIDGET(button));
336     g_return_if_fail(container);
337     label = g_object_get_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_LABEL);
338     g_return_if_fail(label);
339
340     if(ip && get_lat_lon_from_ip(ip, &lat, &lon)) {
341       GtkWidget *notebook;
342       GtkWidget *map;
343       MessageView *messageview;
344       ClutterActor *view;
345       ClutterActor *origin_marker;
346       gint page_num;
347
348       /* TRANSLATORS: The two numbers are latitude and longitude coordinates */
349       text = g_strdup_printf(_("Found location: (%.2f,%.2f)"), lat, lon);
350       gtk_label_set_text(GTK_LABEL(label), text);
351       debug_print("%s\n", text);
352
353       /* switch to map widget, and center on found location */
354       messageview = data;
355       notebook = messageview->mimeview->mime_notebook;
356       page_num = get_notebook_map_page_num(notebook);
357       map = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), page_num);
358       view = g_object_get_data(G_OBJECT(map), GEOLOCATION_MAP_DATA_VIEW);
359       origin_marker = g_object_get_data(G_OBJECT(map), GEOLOCATION_MAP_DATA_ORIGIN_MARKER);
360       champlain_view_center_on(CHAMPLAIN_VIEW(view), lat, lon);
361       champlain_base_marker_set_position(CHAMPLAIN_BASE_MARKER(origin_marker), lat, lon);
362       gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), page_num);
363     }
364     else {
365       const char *country;
366
367       if(ip && ((country = get_country_from_ip(ip)) != NULL)) {
368         /* TRANSLATORS: The country name is appended to the string */
369         text = g_strconcat(_("Alleged country of origin: "), country, NULL);
370       }
371       else {
372         /* TRANSLATORS: The IP address is appended to the string */
373         text = g_strconcat(_("Could not resolve location of IP address "), ip, NULL);
374       }
375       gtk_label_set_text(GTK_LABEL(label), text);
376       debug_print("%s\n", text);
377       gtk_toggle_button_set_active(button, FALSE);
378     }
379     g_free(text);
380   }
381   else {
382     /* swich to first notebook page */
383     MessageView *messageview;
384     messageview = data;
385     gtk_notebook_set_current_page(GTK_NOTEBOOK(messageview->mimeview->mime_notebook), 0);
386   }
387 }
388
389 static void container_finalize_cb(gpointer data, GObject *where_the_object_was)
390 {
391   container_list = g_slist_remove(container_list, where_the_object_was);
392 }
393
394 static GtkWidget* get_geolocation_container_from_messageview(MessageView *messageview)
395 {
396   GtkWidget *container = NULL;
397   GtkWidget *vbox;
398   GList *children, *walk;
399   GtkWidget *button;
400   gulong *cb_id;
401
402   g_return_val_if_fail(messageview && messageview->mimeview && messageview->mimeview->paned, NULL);
403
404   vbox = gtk_paned_get_child2(GTK_PANED(messageview->mimeview->paned));
405
406   /* check if there is already a geolocation container */
407   children = gtk_container_get_children(GTK_CONTAINER(vbox));
408   for(walk = children; walk; walk = walk->next) {
409     GtkWidget *child = walk->data;
410     if(!strcmp(gtk_widget_get_name(child), GEOLOCATION_CONTAINER_NAME)) {
411       container = child;
412       break;
413     }
414   }
415   g_list_free(children);
416
417   /* if not, create one */
418   if(!container) {
419     GtkWidget *label;
420     GtkWidget *image;
421     GdkPixbuf *pixbuf, *scaled_pixbuf;
422
423     container = gtk_hbox_new(FALSE, 0);
424     gtk_widget_set_name(container, GEOLOCATION_CONTAINER_NAME);
425
426     /* button */
427     pixbuf = gdk_pixbuf_new_from_inline(-1, pixmap_earth, FALSE, NULL);
428     scaled_pixbuf = gdk_pixbuf_scale_simple(pixbuf, 16, 16, GDK_INTERP_BILINEAR);
429     g_object_unref(pixbuf);
430     image = gtk_image_new_from_pixbuf(scaled_pixbuf);
431     gtk_widget_show(image);
432     g_object_unref(scaled_pixbuf);
433     button = gtk_toggle_button_new();
434     gtk_widget_show(button);
435     gtk_container_add(GTK_CONTAINER(button), image);
436     gtk_box_pack_start(GTK_BOX(container), button, FALSE, FALSE, 0);
437     g_object_set_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON, button);
438
439     /* label */
440     label = gtk_label_new("");
441     gtk_widget_show(label);
442     gtk_box_pack_start(GTK_BOX(container), label, FALSE, FALSE, 2);
443     g_object_set_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_LABEL, label);
444
445     gtk_box_pack_start(GTK_BOX(vbox), container, FALSE, FALSE, 0);
446
447     /* add new container to list */
448     container_list = g_slist_prepend(container_list, container);
449     g_object_weak_ref(G_OBJECT(container), container_finalize_cb, NULL);
450   }
451   /* if it existed, changed the button callback data */
452   else {
453     /* disconnect old signal handler from button */
454     gulong *id;
455     button = g_object_get_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON);
456     g_return_val_if_fail(button, NULL);
457     id = g_object_get_data(G_OBJECT(button), GEOLOCATION_TOGGLE_BUTTON_DATA_CB_ID);
458     g_signal_handler_disconnect(button, *id);
459   }
460
461   /* connect new signal handler to button, and store its id */
462   cb_id = g_new(gulong, 1);
463   *cb_id = g_signal_connect(button, "toggled", G_CALLBACK(geolocation_button_toggled_cb), messageview);
464   g_object_set_data_full(G_OBJECT(button), GEOLOCATION_TOGGLE_BUTTON_DATA_CB_ID, cb_id, g_free);
465
466   return container;
467 }
468
469 static gboolean my_messageview_show_hook(gpointer source, gpointer data)
470 {
471   MessageView *messageview;
472   GtkWidget *container;
473   GtkWidget *button;
474   gchar *ip;
475
476   messageview = source;
477
478   g_return_val_if_fail(messageview, FALSE);
479
480   container = get_geolocation_container_from_messageview(messageview);
481   g_return_val_if_fail(container, FALSE);
482
483   /* make sure toggle button is turned off */
484   button = g_object_get_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON);
485   g_return_val_if_fail(button, FALSE);
486   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
487     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
488   }
489
490   /* do nothing except hiding the button if claws mail is in offline mode */
491   if(prefs_common.work_offline) {
492     gtk_widget_hide(container);
493     return FALSE;
494   }
495
496   /* try to get ip address from message */
497   if(!messageview->msginfo) {
498     debug_print("Warning: Messageview does not have a MsgInfo\n");
499     return FALSE;
500   }
501   ip = get_ip_from_msginfo(messageview->msginfo);
502   if(!ip) {
503     debug_print("Could not get IP address from message\n");
504     gtk_widget_hide(container);
505   }
506   else {
507     GtkWidget *label;
508     debug_print("Found sender ip address: %s\n", ip);
509     label = g_object_get_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_LABEL);
510     if(label) {
511       gchar *text;
512       text = g_strconcat(_("Try to locate sender"), NULL);
513       gtk_label_set_text(GTK_LABEL(label), text);
514       g_free(text);
515     }
516     /* button takes ownership of ip string */
517     g_object_set_data_full(G_OBJECT(button), GEOLOCATION_TOGGLE_BUTTON_DATA_IP, ip, g_free);
518     gtk_widget_show(container);
519   }
520
521   return FALSE;
522 }
523
524 #define ISO_IN(k,v) g_hash_table_insert(iso_country_hash, (k), (v))
525 static void fill_iso_country_hash(void)
526 {
527   iso_country_hash = g_hash_table_new(g_str_hash, g_str_equal);
528   ISO_IN("127.0.0.20", _("Andorra"));
529   ISO_IN("127.0.3.16", _("United Arab Emirates"));
530   ISO_IN("127.0.0.4", _("Afghanistan"));
531   ISO_IN("127.0.0.28", _("Antigua And Barbuda"));
532   ISO_IN("127.0.2.148", _("Anguilla"));
533   ISO_IN("127.0.0.8", _("Albania"));
534   ISO_IN("127.0.0.51", _("Armenia"));
535   ISO_IN("127.0.2.18", _("Netherlands Antilles"));
536   ISO_IN("127.0.0.24", _("Angola"));
537   ISO_IN("127.0.0.10", _("Antarctica"));
538   ISO_IN("127.0.0.32", _("Argentina"));
539   ISO_IN("127.0.0.16", _("American Samoa"));
540   ISO_IN("127.0.0.40", _("Austria"));
541   ISO_IN("127.0.0.36", _("Australia"));
542   ISO_IN("127.0.2.21", _("Aruba"));
543   ISO_IN("127.0.0.31", _("Azerbaijan"));
544   ISO_IN("127.0.0.70", _("Bosnia And Herzegovina"));
545   ISO_IN("127.0.0.52", _("Barbados"));
546   ISO_IN("127.0.0.50", _("Bangladesh"));
547   ISO_IN("127.0.0.56", _("Belgium"));
548   ISO_IN("127.0.3.86", _("Burkina Faso"));
549   ISO_IN("127.0.0.100", _("Bulgaria"));
550   ISO_IN("127.0.0.48", _("Bahrain"));
551   ISO_IN("127.0.0.108", _("Burundi"));
552   ISO_IN("127.0.0.204", _("Benin"));
553   ISO_IN("127.0.0.60", _("Bermuda"));
554   ISO_IN("127.0.0.96", _("Brunei Darussalam"));
555   ISO_IN("127.0.0.68", _("Bolivia"));
556   ISO_IN("127.0.0.76", _("Brazil"));
557   ISO_IN("127.0.0.44", _("Bahamas"));
558   ISO_IN("127.0.0.64", _("Bhutan"));
559   ISO_IN("127.0.0.74", _("Bouvet Island"));
560   ISO_IN("127.0.0.72", _("Botswana"));
561   ISO_IN("127.0.0.112", _("Belarus"));
562   ISO_IN("127.0.0.84", _("Belize"));
563   ISO_IN("127.0.0.124", _("Canada"));
564   ISO_IN("127.0.0.166", _("Cocos (Keeling) Islands"));
565   ISO_IN("127.0.0.140", _("Central African Republic"));
566   ISO_IN("127.0.0.178", _("Congo"));
567   ISO_IN("127.0.2.244", _("Switzerland"));
568   ISO_IN("127.0.1.128", _("Cote D'Ivoire"));
569   ISO_IN("127.0.0.184", _("Cook Islands"));
570   ISO_IN("127.0.0.152", _("Chile"));
571   ISO_IN("127.0.0.120", _("Cameroon"));
572   ISO_IN("127.0.0.156", _("China"));
573   ISO_IN("127.0.0.170", _("Colombia"));
574   ISO_IN("127.0.0.188", _("Costa Rica"));
575   ISO_IN("127.0.0.192", _("Cuba"));
576   ISO_IN("127.0.0.132", _("Cape Verde"));
577   ISO_IN("127.0.0.162", _("Christmas Island"));
578   ISO_IN("127.0.0.196", _("Cyprus"));
579   ISO_IN("127.0.0.203", _("Czech Republic"));
580   ISO_IN("127.0.1.20", _("Germany"));
581   ISO_IN("127.0.1.6", _("Djibouti"));
582   ISO_IN("127.0.0.208", _("Denmark"));
583   ISO_IN("127.0.0.212", _("Dominica"));
584   ISO_IN("127.0.0.214", _("Dominican Republic"));
585   ISO_IN("127.0.0.12", _("Algeria"));
586   ISO_IN("127.0.0.218", _("Ecuador"));
587   ISO_IN("127.0.0.233", _("Estonia"));
588   ISO_IN("127.0.3.50", _("Egypt"));
589   ISO_IN("127.0.2.220", _("Western Sahara"));
590   ISO_IN("127.0.0.232", _("Eritrea"));
591   ISO_IN("127.0.2.212", _("Spain"));
592   ISO_IN("127.0.0.231", _("Ethiopia"));
593   ISO_IN("127.0.0.246", _("Finland"));
594   ISO_IN("127.0.0.242", _("Fiji"));
595   ISO_IN("127.0.0.238", _("Falkland Islands (Malvinas)"));
596   ISO_IN("127.0.2.71", _("Micronesia, Federated States Of"));
597   ISO_IN("127.0.0.234", _("Faroe Islands"));
598   ISO_IN("127.0.0.250", _("France"));
599   ISO_IN("127.0.0.249", _("France, Metropolitan"));
600   ISO_IN("127.0.1.10", _("Gabon"));
601   ISO_IN("127.0.3.58", _("United Kingdom"));
602   ISO_IN("127.0.1.52", _("Grenada"));
603   ISO_IN("127.0.1.12", _("Georgia"));
604   ISO_IN("127.0.0.254", _("French Guiana"));
605   ISO_IN("127.0.1.32", _("Ghana"));
606   ISO_IN("127.0.1.36", _("Gibraltar"));
607   ISO_IN("127.0.1.48", _("Greenland"));
608   ISO_IN("127.0.1.14", _("Gambia"));
609   ISO_IN("127.0.1.68", _("Guinea"));
610   ISO_IN("127.0.1.56", _("Guadeloupe"));
611   ISO_IN("127.0.0.226", _("Equatorial Guinea"));
612   ISO_IN("127.0.1.44", _("Greece"));
613   ISO_IN("127.0.0.239", _("South Georgia And The South Sandwich Islands"));
614   ISO_IN("127.0.1.64", _("Guatemala"));
615   ISO_IN("127.0.1.60", _("Guam"));
616   ISO_IN("127.0.2.112", _("Guinea-Bissau"));
617   ISO_IN("127.0.1.72", _("Guyana"));
618   ISO_IN("127.0.1.88", _("Hong Kong"));
619   ISO_IN("127.0.1.78", _("Heard Island And Mcdonald Islands"));
620   ISO_IN("127.0.1.84", _("Honduras"));
621   ISO_IN("127.0.0.191", _("Croatia"));
622   ISO_IN("127.0.1.76", _("Haiti"));
623   ISO_IN("127.0.1.92", _("Hungary"));
624   ISO_IN("127.0.1.104", _("Indonesia"));
625   ISO_IN("127.0.1.116", _("Ireland"));
626   ISO_IN("127.0.1.120", _("Israel"));
627   ISO_IN("127.0.1.100", _("India"));
628   ISO_IN("127.0.0.86", _("British Indian Ocean Territory"));
629   ISO_IN("127.0.1.112", _("Iraq"));
630   ISO_IN("127.0.1.108", _("Iran, Islamic Republic Of"));
631   ISO_IN("127.0.1.96", _("Iceland"));
632   ISO_IN("127.0.1.124", _("Italy"));
633   ISO_IN("127.0.1.132", _("Jamaica"));
634   ISO_IN("127.0.1.144", _("Jordan"));
635   ISO_IN("127.0.1.136", _("Japan"));
636   ISO_IN("127.0.1.148", _("Kenya"));
637   ISO_IN("127.0.1.161", _("Kyrgyzstan"));
638   ISO_IN("127.0.0.116", _("Cambodia"));
639   ISO_IN("127.0.1.40", _("Kiribati"));
640   ISO_IN("127.0.0.174", _("Comoros"));
641   ISO_IN("127.0.2.147", _("Saint Kitts And Nevis"));
642   ISO_IN("127.0.1.152", _("Korea, Democratic People'S Republic Of"));
643   ISO_IN("127.0.1.154", _("Korea, Republic Of"));
644   ISO_IN("127.0.1.158", _("Kuwait"));
645   ISO_IN("127.0.0.136", _("Cayman Islands"));
646   ISO_IN("127.0.1.142", _("Kazakhstan"));
647   ISO_IN("127.0.1.162", _("Lao People'S Democratic Republic"));
648   ISO_IN("127.0.1.166", _("Lebanon"));
649   ISO_IN("127.0.2.150", _("Saint Lucia"));
650   ISO_IN("127.0.1.182", _("Liechtenstein"));
651   ISO_IN("127.0.0.144", _("Sri Lanka"));
652   ISO_IN("127.0.1.174", _("Liberia"));
653   ISO_IN("127.0.1.170", _("Lesotho"));
654   ISO_IN("127.0.1.184", _("Lithuania"));
655   ISO_IN("127.0.1.186", _("Luxembourg"));
656   ISO_IN("127.0.1.172", _("Latvia"));
657   ISO_IN("127.0.1.178", _("Libyan Arab Jamahiriya"));
658   ISO_IN("127.0.1.248", _("Morocco"));
659   ISO_IN("127.0.1.236", _("Monaco"));
660   ISO_IN("127.0.1.242", _("Moldova, Republic Of"));
661   ISO_IN("127.0.1.194", _("Madagascar"));
662   ISO_IN("127.0.2.72", _("Marshall Islands"));
663   ISO_IN("127.0.3.39", _("Macedonia, The Former Yugoslav Republic Of"));
664   ISO_IN("127.0.1.210", _("Mali"));
665   ISO_IN("127.0.0.104", _("Myanmar"));
666   ISO_IN("127.0.1.240", _("Mongolia"));
667   ISO_IN("127.0.1.190", _("Macao"));
668   ISO_IN("127.0.2.68", _("Northern Mariana Islands"));
669   ISO_IN("127.0.1.218", _("Martinique"));
670   ISO_IN("127.0.1.222", _("Mauritania"));
671   ISO_IN("127.0.1.244", _("Montserrat"));
672   ISO_IN("127.0.1.214", _("Malta"));
673   ISO_IN("127.0.1.224", _("Mauritius"));
674   ISO_IN("127.0.1.206", _("Maldives"));
675   ISO_IN("127.0.1.198", _("Malawi"));
676   ISO_IN("127.0.1.228", _("Mexico"));
677   ISO_IN("127.0.1.202", _("Malaysia"));
678   ISO_IN("127.0.1.252", _("Mozambique"));
679   ISO_IN("127.0.2.4", _("Namibia"));
680   ISO_IN("127.0.2.28", _("New Caledonia"));
681   ISO_IN("127.0.2.50", _("Niger"));
682   ISO_IN("127.0.2.62", _("Norfolk Island"));
683   ISO_IN("127.0.2.54", _("Nigeria"));
684   ISO_IN("127.0.2.46", _("Nicaragua"));
685   ISO_IN("127.0.2.16", _("Netherlands"));
686   ISO_IN("127.0.2.66", _("Norway"));
687   ISO_IN("127.0.2.12", _("Nepal"));
688   ISO_IN("127.0.2.8", _("Nauru"));
689   ISO_IN("127.0.2.58", _("Niue"));
690   ISO_IN("127.0.2.42", _("New Zealand"));
691   ISO_IN("127.0.2.0", _("Oman"));
692   ISO_IN("127.0.2.79", _("Panama"));
693   ISO_IN("127.0.2.92", _("Peru"));
694   ISO_IN("127.0.1.2", _("French Polynesia"));
695   ISO_IN("127.0.2.86", _("Papua New Guinea"));
696   ISO_IN("127.0.2.96", _("Philippines"));
697   ISO_IN("127.0.2.74", _("Pakistan"));
698   ISO_IN("127.0.2.104", _("Poland"));
699   ISO_IN("127.0.2.154", _("Saint Pierre And Miquelon"));
700   ISO_IN("127.0.2.100", _("Pitcairn"));
701   ISO_IN("127.0.2.118", _("Puerto Rico"));
702   ISO_IN("127.0.2.108", _("Portugal"));
703   ISO_IN("127.0.2.73", _("Palau"));
704   ISO_IN("127.0.2.88", _("Paraguay"));
705   ISO_IN("127.0.2.122", _("Qatar"));
706   ISO_IN("127.0.2.126", _("Reunion"));
707   ISO_IN("127.0.2.130", _("Romania"));
708   ISO_IN("127.0.2.131", _("Russian Federation"));
709   ISO_IN("127.0.2.134", _("Rwanda"));
710   ISO_IN("127.0.2.170", _("Saudi Arabia"));
711   ISO_IN("127.0.0.90", _("Solomon Islands"));
712   ISO_IN("127.0.2.178", _("Seychelles"));
713   ISO_IN("127.0.2.224", _("Sudan"));
714   ISO_IN("127.0.2.240", _("Sweden"));
715   ISO_IN("127.0.2.190", _("Singapore"));
716   ISO_IN("127.0.2.142", _("Saint Helena"));
717   ISO_IN("127.0.2.193", _("Slovenia"));
718   ISO_IN("127.0.2.232", _("Svalbard And Jan Mayen"));
719   ISO_IN("127.0.2.191", _("Slovakia"));
720   ISO_IN("127.0.2.182", _("Sierra Leone"));
721   ISO_IN("127.0.2.162", _("San Marino"));
722   ISO_IN("127.0.2.174", _("Senegal"));
723   ISO_IN("127.0.2.194", _("Somalia"));
724   ISO_IN("127.0.2.228", _("Suriname"));
725   ISO_IN("127.0.2.166", _("Sao Tome And Principe"));
726   ISO_IN("127.0.0.222", _("El Salvador"));
727   ISO_IN("127.0.2.248", _("Syrian Arab Republic"));
728   ISO_IN("127.0.2.236", _("Swaziland"));
729   ISO_IN("127.0.3.28", _("Turks And Caicos Islands"));
730   ISO_IN("127.0.0.148", _("Chad"));
731   ISO_IN("127.0.1.4", _("French Southern Territories"));
732   ISO_IN("127.0.3.0", _("Togo"));
733   ISO_IN("127.0.2.252", _("Thailand"));
734   ISO_IN("127.0.2.250", _("Tajikistan"));
735   ISO_IN("127.0.3.4", _("Tokelau"));
736   ISO_IN("127.0.3.27", _("Turkmenistan"));
737   ISO_IN("127.0.3.20", _("Tunisia"));
738   ISO_IN("127.0.3.8", _("Tonga"));
739   ISO_IN("127.0.2.114", _("East Timor"));
740   ISO_IN("127.0.3.24", _("Turkey"));
741   ISO_IN("127.0.3.12", _("Trinidad And Tobago"));
742   ISO_IN("127.0.3.30", _("Tuvalu"));
743   ISO_IN("127.0.0.158", _("Taiwan, Province Of China"));
744   ISO_IN("127.0.3.66", _("Tanzania, United Republic Of"));
745   ISO_IN("127.0.3.36", _("Ukraine"));
746   ISO_IN("127.0.3.32", _("Uganda"));
747   ISO_IN("127.0.3.58", _("United Kingdom"));
748   ISO_IN("127.0.2.69", _("United States Minor Outlying Islands"));
749   ISO_IN("127.0.3.72", _("United States"));
750   ISO_IN("127.0.3.90", _("Uruguay"));
751   ISO_IN("127.0.3.92", _("Uzbekistan"));
752   ISO_IN("127.0.1.80", _("Holy See (Vatican City State)"));
753   ISO_IN("127.0.2.158", _("Saint Vincent And The Grenadines"));
754   ISO_IN("127.0.3.94", _("Venezuela"));
755   ISO_IN("127.0.0.92", _("Virgin Islands, British"));
756   ISO_IN("127.0.3.82", _("Virgin Islands, U.S."));
757   ISO_IN("127.0.2.192", _("Viet Nam"));
758   ISO_IN("127.0.2.36", _("Vanuatu"));
759   ISO_IN("127.0.3.108", _("Wallis And Futuna"));
760   ISO_IN("127.0.3.114", _("Samoa"));
761   ISO_IN("127.0.3.119", _("Yemen"));
762   ISO_IN("127.0.0.175", _("Mayotte"));
763   ISO_IN("127.0.3.123", _("Serbia And Montenegro"));
764   ISO_IN("127.0.2.198", _("South Africa"));
765   ISO_IN("127.0.3.126", _("Zambia"));
766   ISO_IN("127.0.0.180", _("Democratic Republic Of The Congo"));
767   ISO_IN("127.0.2.204", _("Zimbabwe"));
768 }
769 #undef ISO_IN
770
771 gint plugin_init(gchar **error)
772 {
773   GError *err = NULL;
774
775   /* Version check */
776   if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,1,55),
777                            VERSION_NUMERIC, _("GeoLocation"), error))
778     return -1;
779
780   if(gtk_clutter_init(0, NULL) != CLUTTER_INIT_SUCCESS)
781   {
782     *error = g_strdup_printf(_("Could not initialize clutter"));
783     return -1;
784   }
785
786   ip_from_received_header_regex = g_regex_new(EXTRACT_IP_FROM_RECEIVED_HEADER_REGEX, 0, 0, &err);
787   if(err) {
788     *error = g_strdup_printf(_("Could not create regular expression: %s\n"), err->message);
789     g_error_free(err);
790     return -1;
791   }
792
793   lat_lon_from_hostip_response_regex = g_regex_new(EXTRACT_LAT_LON_FROM_HOSTIP_RESPONSE_REGEX, 0, 0, &err);
794   if(err) {
795     *error = g_strdup_printf(_("Could not create regular expression: %s\n"), err->message);
796     g_error_free(err);
797     g_regex_unref(ip_from_received_header_regex);
798     return -1;
799   }
800
801   hook_messageview_show = hooks_register_hook(MESSAGE_VIEW_SHOW_DONE_HOOKLIST, my_messageview_show_hook, NULL);
802   if(hook_messageview_show == (guint) -1) {
803     *error = g_strdup(_("Failed to register messageview_show hook in the GeoLocation plugin"));
804     g_regex_unref(ip_from_received_header_regex);
805     g_regex_unref(lat_lon_from_hostip_response_regex);
806     return -1;
807   }
808
809   fill_iso_country_hash();
810
811   debug_print("GeoLocation plugin loaded\n");
812
813   return 0;
814 }
815
816 gboolean plugin_done(void)
817 {
818
819   hooks_unregister_hook(MESSAGE_VIEW_SHOW_DONE_HOOKLIST, hook_messageview_show);
820
821   if(!claws_is_exiting()) {
822     GSList *copy, *walk;
823
824     copy = g_slist_copy(container_list);
825     for(walk = copy; walk; walk = walk->next) {
826       GtkWidget *wid = walk->data;
827       GtkWidget *button = g_object_get_data(G_OBJECT(wid), GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON);
828       if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
829         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
830       gtk_widget_destroy(GTK_WIDGET(wid));
831     }
832     g_slist_free(copy);
833     if(container_list) {
834       g_slist_free(container_list);
835       container_list = NULL;
836     }
837
838     if(ip_from_received_header_regex) {
839       g_regex_unref(ip_from_received_header_regex);
840       ip_from_received_header_regex = NULL;
841     }
842
843     if(lat_lon_from_hostip_response_regex) {
844       g_regex_unref(lat_lon_from_hostip_response_regex);
845       lat_lon_from_hostip_response_regex = NULL;
846     }
847
848     if(iso_country_hash) {
849       g_hash_table_destroy(iso_country_hash);
850       iso_country_hash = NULL;
851     }
852   }
853
854   debug_print("GeoLocation plugin unloaded\n");
855
856   /* returning FALSE because dependant libraries may not be unload-safe. */
857   return FALSE;
858 }
859
860 const gchar *plugin_name(void)
861 {
862   return _("GeoLocation");
863 }
864
865 const gchar *plugin_desc(void)
866 {
867   return _("This plugin provides GeoLocation functionality "
868            "for Claws Mail.\n\n"
869            "Warning: It is technically impossible to derive the geographic "
870            "location of senders from their E-Mails with any amount of certainty. "
871            "The results presented by "
872            "this plugin are only rough estimates. In particular, mailing list managers "
873            "often strip sender information from the mails, so mails from mailing lists "
874            "may be assigned to the location of the mailing list server instead of the "
875            "mail sender.\n"
876            "When in doubt, don't trust the results of this plugin, and don't rely on this "
877            "information to divorce your spouse.\n"
878            "\nFeedback to <berndth@gmx.de> is welcome "
879            "(but only if it's not about marital quarrels).");
880 }
881
882 const gchar *plugin_type(void)
883 {
884   return "GTK2";
885 }
886
887 const gchar *plugin_licence(void)
888 {
889   return "GPL3+";
890 }
891
892 const gchar *plugin_version(void)
893 {
894   return VERSION;
895 }
896
897 struct PluginFeature *plugin_provides(void)
898 {
899   static struct PluginFeature features[] =
900     { {PLUGIN_UTILITY, N_("GeoLocation integration")},
901       {PLUGIN_NOTHING, NULL}};
902   return features;
903 }