From 2fc4078bc842efa253f84398fad8e655a3714568 Mon Sep 17 00:00:00 2001 From: Guido Günther Date: Wed, 8 Feb 2012 15:53:42 +0100 Subject: Initial commit --- .gitignore | 33 ++ AUTHORS | 1 + COPYING | 351 +++++++++++++++++ Makefile.am | 33 ++ auth-dialog/Makefile.am | 30 ++ auth-dialog/main.c | 246 ++++++++++++ auth-dialog/vpn-password-dialog.c | 461 +++++++++++++++++++++++ auth-dialog/vpn-password-dialog.h | 79 ++++ autogen.sh | 21 ++ configure.ac | 92 +++++ m4/compiler_warnings.m4 | 31 ++ network-manager-iodine.doap | 23 ++ nm-iodine-service.conf | 14 + nm-iodine-service.name.in | 8 + po/LINGUAS | 2 + po/POTFILES.in | 7 + properties/Makefile.am | 34 ++ properties/nm-iodine-dialog.ui | 291 +++++++++++++++ properties/nm-iodine.c | 768 ++++++++++++++++++++++++++++++++++++++ properties/nm-iodine.h | 83 ++++ src/Makefile.am | 30 ++ src/nm-iodine-service.c | 672 +++++++++++++++++++++++++++++++++ src/nm-iodine-service.h | 57 +++ 23 files changed, 3367 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 auth-dialog/Makefile.am create mode 100644 auth-dialog/main.c create mode 100644 auth-dialog/vpn-password-dialog.c create mode 100644 auth-dialog/vpn-password-dialog.h create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 m4/compiler_warnings.m4 create mode 100644 network-manager-iodine.doap create mode 100644 nm-iodine-service.conf create mode 100644 nm-iodine-service.name.in create mode 100644 po/LINGUAS create mode 100644 po/POTFILES.in create mode 100644 properties/Makefile.am create mode 100644 properties/nm-iodine-dialog.ui create mode 100644 properties/nm-iodine.c create mode 100644 properties/nm-iodine.h create mode 100644 src/Makefile.am create mode 100644 src/nm-iodine-service.c create mode 100644 src/nm-iodine-service.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1dc5c5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +*~ +*.o +*.lo +*.la +Makefile +Makefile.in* +configure +compile +config.* +aclocal.m4 +depcomp +install-sh +libtool +ltmain.sh +missing +mkinstalldirs +POTFILES +stamp-* +.deps +.libs +autom4te.cache +intltool-* +po/*.gmo +po/.intltool-merge-cache +m4/gtk-doc.m4 +m4/intltool.m4 +m4/libtool.m4 +m4/lt*.m4 + +nm-iodine-service.name +src/nm-iodine-service +auth-dialog/nm-iodine-auth-dialog + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..080f177 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Guido Günther diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..fde38aa --- /dev/null +++ b/COPYING @@ -0,0 +1,351 @@ +Unless a COPYING file in a subdirectory or file-specific license headers +specify a different license, the following applies to all files in this +directory and all subdirectories. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..794cc01 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,33 @@ +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = src + +if WITH_GNOME +SUBDIRS += properties po + +if WITH_AUTHDLG +SUBDIRS += auth-dialog +endif +endif + +dbusservicedir = $(sysconfdir)/dbus-1/system.d +dbusservice_DATA = nm-iodine-service.conf + +nmvpnservicedir = $(sysconfdir)/NetworkManager/VPN +nmvpnservice_DATA = nm-iodine-service.name + +nm-iodine-service.name: $(srcdir)/nm-iodine-service.name.in + sed -e 's|[@]LIBEXECDIR[@]|$(libexecdir)|g' $< >$@ + +EXTRA_DIST = nm-iodine-service.name.in \ + $(dbusservice_DATA) \ + $(desktop_in_files) \ + $(icon_DATA) \ + intltool-extract.in \ + intltool-merge.in \ + intltool-update.in + +CLEANFILES = $(nmvpnservice_DATA) $(desktop_DATA) *~ +DISTCLEANFILES = intltool-extract intltool-merge intltool-update + +ACLOCAL_AMFLAGS = -I m4 diff --git a/auth-dialog/Makefile.am b/auth-dialog/Makefile.am new file mode 100644 index 0000000..fe45a82 --- /dev/null +++ b/auth-dialog/Makefile.am @@ -0,0 +1,30 @@ +INCLUDES = -I${top_srcdir} + +libexec_PROGRAMS = nm-iodine-auth-dialog + +nm_iodine_auth_dialog_CPPFLAGS = \ + $(NM_CFLAGS) \ + $(GTHREAD_CFLAGS) \ + $(GTK_CFLAGS) \ + $(GNOMEKEYRING_CFLAGS) \ + -DICONDIR=\""$(datadir)/pixmaps"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DG_DISABLE_DEPRECATED \ + -DGDK_DISABLE_DEPRECATED \ + -DGNOME_DISABLE_DEPRECATED \ + -DGNOMELOCALEDIR=\"$(datadir)/locale\" \ + -DVERSION=\"$(VERSION)\" + +nm_iodine_auth_dialog_SOURCES = \ + main.c \ + vpn-password-dialog.c \ + vpn-password-dialog.h \ + $(NULL) + +nm_iodine_auth_dialog_LDADD = \ + $(GTK_LIBS) \ + $(NM_LIBS) \ + $(GNOMEKEYRING_LIBS) + +CLEANFILES = *~ + diff --git a/auth-dialog/main.c b/auth-dialog/main.c new file mode 100644 index 0000000..a7f127b --- /dev/null +++ b/auth-dialog/main.c @@ -0,0 +1,246 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * Authentication dialog for NetworkManager iodine VPN connections + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright © 2012 Guido Günther + * + * Heavily based on network-manager-pptp by Dan Williams + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "src/nm-iodine-service.h" + +#include "vpn-password-dialog.h" + +#define KEYRING_UUID_TAG "connection-uuid" +#define KEYRING_SN_TAG "setting-name" +#define KEYRING_SK_TAG "setting-key" + +static char * +keyring_lookup_secret (const char *uuid, const char *secret_name) +{ + GList *found_list = NULL; + GnomeKeyringResult ret; + GnomeKeyringFound *found; + char *secret = NULL; + + ret = gnome_keyring_find_itemsv_sync (GNOME_KEYRING_ITEM_GENERIC_SECRET, + &found_list, + KEYRING_UUID_TAG, + GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + uuid, + KEYRING_SN_TAG, + GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + NM_SETTING_VPN_SETTING_NAME, + KEYRING_SK_TAG, + GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + secret_name, + NULL); + if (ret == GNOME_KEYRING_RESULT_OK && found_list) { + found = g_list_nth_data (found_list, 0); + secret = gnome_keyring_memory_strdup (found->secret); + } + + gnome_keyring_found_list_free (found_list); + return secret; +} + +static gboolean +get_secrets (const char *vpn_uuid, + const char *vpn_name, + gboolean retry, + gboolean allow_interaction, + const char *in_pw, + char **out_pw, + NMSettingSecretFlags pw_flags) +{ + VpnPasswordDialog *dialog; + char *prompt, *pw = NULL; + const char *new_password = NULL; + + g_return_val_if_fail (vpn_uuid != NULL, FALSE); + g_return_val_if_fail (vpn_name != NULL, FALSE); + g_return_val_if_fail (out_pw != NULL, FALSE); + g_return_val_if_fail (*out_pw == NULL, FALSE); + + /* Get the existing secret, if any */ + if ( !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + if (in_pw) + pw = gnome_keyring_memory_strdup (in_pw); + else + pw = keyring_lookup_secret (vpn_uuid, NM_IODINE_KEY_PASSWORD); + } + + /* Don't ask if the passwords is unused */ + if (pw_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) { + gnome_keyring_memory_free (pw); + return TRUE; + } + + if (!retry) { + /* Don't ask the user if we don't need a new password (ie, !retry), + * we have an existing PW, and the password is saved. + */ + if (pw && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)) { + *out_pw = pw; + return TRUE; + } + } + + /* If interaction isn't allowed, just return existing secrets */ + if (allow_interaction == FALSE) { + *out_pw = pw; + return TRUE; + } + + /* Otherwise, we have no saved password, or the password flags indicated + * that the password should never be saved. + */ + prompt = g_strdup_printf (_("You need to authenticate to access the " + "Virtual Private Network '%s'."), vpn_name); + dialog = (VpnPasswordDialog *) \ + vpn_password_dialog_new (_("Authenticate VPN"), prompt, NULL); + g_free (prompt); + + vpn_password_dialog_set_show_password_secondary (dialog, FALSE); + + /* pre-fill dialog with the password */ + if (pw && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)) + vpn_password_dialog_set_password (dialog, pw); + + gtk_widget_show (GTK_WIDGET (dialog)); + + if (vpn_password_dialog_run_and_block (dialog)) { + + new_password = vpn_password_dialog_get_password (dialog); + if (new_password) + *out_pw = gnome_keyring_memory_strdup (new_password); + } + + gtk_widget_hide (GTK_WIDGET (dialog)); + gtk_widget_destroy (GTK_WIDGET (dialog)); + + return TRUE; +} + + +static void +wait_for_quit (void) +{ + GString *str; + char c; + ssize_t n; + time_t start; + + str = g_string_sized_new (10); + start = time (NULL); + do { + errno = 0; + n = read (0, &c, 1); + if (n == 0 || (n < 0 && errno == EAGAIN)) + g_usleep (G_USEC_PER_SEC / 10); + else if (n == 1) { + g_string_append_c (str, c); + if (strstr (str->str, "QUIT") || (str->len > 10)) + break; + } else + break; + } while (time (NULL) < start + 20); + g_string_free (str, TRUE); +} + +int +main (int argc, char *argv[]) +{ + gboolean retry = FALSE, allow_interaction = FALSE; + char *vpn_name = NULL, *vpn_uuid = NULL, *vpn_service = NULL; + char *password = NULL; + GHashTable *data = NULL, *secrets = NULL; + NMSettingSecretFlags pw_flags = NM_SETTING_SECRET_FLAG_NONE; + GOptionContext *context; + GOptionEntry entries[] = { + { "reprompt", 'r', 0, G_OPTION_ARG_NONE, &retry, + "Reprompt for passwords", NULL}, + { "uuid", 'u', 0, G_OPTION_ARG_STRING, &vpn_uuid, + "UUID of VPN connection", NULL}, + { "name", 'n', 0, G_OPTION_ARG_STRING, &vpn_name, + "Name of VPN connection", NULL}, + { "service", 's', 0, G_OPTION_ARG_STRING, &vpn_service, + "VPN service type", NULL}, + { "allow-interaction", 'i', 0, G_OPTION_ARG_NONE, + &allow_interaction, "Allow user interaction", NULL}, + { NULL } + }; + + bindtextdomain (GETTEXT_PACKAGE, NULL); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gtk_init (&argc, &argv); + + context = g_option_context_new ("- iodine auth dialog"); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + g_option_context_parse (context, &argc, &argv, NULL); + g_option_context_free (context); + + if (!nm_vpn_plugin_utils_read_vpn_details (0, &data, &secrets)) { + fprintf (stderr, "Failed to read '%s' (%s) data and secrets from stdin.\n", + vpn_name, vpn_uuid); + return 1; + } + + nm_vpn_plugin_utils_get_secret_flags (secrets, NM_IODINE_KEY_PASSWORD, &pw_flags); + + if (!get_secrets (vpn_uuid, vpn_name, retry, allow_interaction, + g_hash_table_lookup (secrets, NM_IODINE_KEY_PASSWORD), + &password, + pw_flags)) + return 1; + + /* dump the passwords to stdout */ + if (password) + printf ("%s\n%s\n", NM_IODINE_KEY_PASSWORD, password); + printf ("\n\n"); + + /* for good measure, flush stdout since Kansas is going Bye-Bye */ + fflush (stdout); + + /* Wait for quit signal */ + wait_for_quit (); + + if (data) + g_hash_table_unref (data); + if (secrets) + g_hash_table_unref (secrets); + return 0; +} diff --git a/auth-dialog/vpn-password-dialog.c b/auth-dialog/vpn-password-dialog.c new file mode 100644 index 0000000..6c976a3 --- /dev/null +++ b/auth-dialog/vpn-password-dialog.c @@ -0,0 +1,461 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* vpn-password-dialog.c - A use password prompting dialog widget. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the ree Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * Copyright (C) 2011 Red Hat, Inc. + * + * Authors: Ramiro Estrugo + * Dan Williams + */ + +#include +#include +#include +#include + +#include "vpn-password-dialog.h" + +G_DEFINE_TYPE (VpnPasswordDialog, vpn_password_dialog, GTK_TYPE_DIALOG) + +#define VPN_PASSWORD_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + VPN_TYPE_PASSWORD_DIALOG, \ + VpnPasswordDialogPrivate)) + +typedef struct { + /* Attributes */ + gboolean show_password; + gboolean show_password_secondary; + + /* Internal widgetry and flags */ + GtkWidget *password_entry; + GtkWidget *password_entry_secondary; + GtkWidget *show_passwords_checkbox; + + GtkWidget *table_alignment; + GtkWidget *table; + GtkSizeGroup *group; + + char *primary_password_label; + char *secondary_password_label; +} VpnPasswordDialogPrivate; + +/* VpnPasswordDialogClass methods */ +static void vpn_password_dialog_class_init (VpnPasswordDialogClass *password_dialog_class); +static void vpn_password_dialog_init (VpnPasswordDialog *password_dialog); + +/* GtkDialog callbacks */ +static void dialog_show_callback (GtkWidget *widget, gpointer callback_data); +static void dialog_close_callback (GtkWidget *widget, gpointer callback_data); + +static void +finalize (GObject *object) +{ + VpnPasswordDialogPrivate *priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (object); + + g_object_unref (priv->password_entry); + g_object_unref (priv->password_entry_secondary); + g_object_unref (priv->group); + + g_free (priv->primary_password_label); + g_free (priv->secondary_password_label); + + G_OBJECT_CLASS (vpn_password_dialog_parent_class)->finalize (object); +} + +static void +vpn_password_dialog_class_init (VpnPasswordDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (VpnPasswordDialogPrivate)); + + object_class->finalize = finalize; +} + +static void +vpn_password_dialog_init (VpnPasswordDialog *dialog) +{ + VpnPasswordDialogPrivate *priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + + priv->show_password = TRUE; + priv->primary_password_label = g_strdup ( _("_Password:") ); + priv->show_password_secondary = TRUE; + priv->secondary_password_label = g_strdup ( _("_Secondary Password:") ); +} + +/* GtkDialog callbacks */ +static void +dialog_show_callback (GtkWidget *widget, gpointer callback_data) +{ + VpnPasswordDialog *dialog = VPN_PASSWORD_DIALOG (callback_data); + VpnPasswordDialogPrivate *priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + + if (gtk_widget_get_visible (priv->password_entry)) + gtk_widget_grab_focus (priv->password_entry); + else if (gtk_widget_get_visible (priv->password_entry_secondary)) + gtk_widget_grab_focus (priv->password_entry_secondary); +} + +static void +dialog_close_callback (GtkWidget *widget, gpointer callback_data) +{ + gtk_widget_hide (widget); +} + +static void +add_row (GtkWidget *table, int row, const char *label_text, GtkWidget *entry) +{ + GtkWidget *label; + + label = gtk_label_new_with_mnemonic (label_text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, row, row + 1); + gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, row, row + 1); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); +} + +static void +remove_child (GtkWidget *child, GtkWidget *table) +{ + gtk_container_remove (GTK_CONTAINER (table), child); +} + +static void +add_table_rows (VpnPasswordDialog *dialog) +{ + VpnPasswordDialogPrivate *priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + int row; + int offset = 0; + + gtk_alignment_set_padding (GTK_ALIGNMENT (priv->table_alignment), 0, 0, offset, 0); + + /* This will not kill the entries, since they are ref:ed */ + gtk_container_foreach (GTK_CONTAINER (priv->table), (GtkCallback) remove_child, priv->table); + + row = 0; + if (priv->show_password) + add_row (priv->table, row++, priv->primary_password_label, priv->password_entry); + if (priv->show_password_secondary) + add_row (priv->table, row++, priv->secondary_password_label, priv->password_entry_secondary); + + gtk_table_attach_defaults (GTK_TABLE (priv->table), priv->show_passwords_checkbox, 1, 2, row, row + 1); + + gtk_widget_show_all (priv->table); +} + +static void +show_passwords_toggled_cb (GtkWidget *widget, gpointer user_data) +{ + VpnPasswordDialog *dialog = VPN_PASSWORD_DIALOG (user_data); + VpnPasswordDialogPrivate *priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + gboolean visible; + + visible = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + gtk_entry_set_visibility (GTK_ENTRY (priv->password_entry), visible); + gtk_entry_set_visibility (GTK_ENTRY (priv->password_entry_secondary), visible); +} + +/* Public VpnPasswordDialog methods */ +GtkWidget * +vpn_password_dialog_new (const char *title, + const char *message, + const char *password) +{ + GtkWidget *dialog; + VpnPasswordDialogPrivate *priv; + GtkLabel *message_label; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *main_vbox; + GtkWidget *dialog_icon; + GtkBox *content, *action_area; + + dialog = gtk_widget_new (VPN_TYPE_PASSWORD_DIALOG, NULL); + if (!dialog) + return NULL; + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + + gtk_window_set_title (GTK_WINDOW (dialog), title); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + content = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))); + action_area = GTK_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dialog))); + + /* Setup the dialog */ +#if !GTK_CHECK_VERSION (2,22,0) + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); +#endif + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_box_set_spacing (content, 2); /* 2 * 5 + 2 = 12 */ + gtk_container_set_border_width (GTK_CONTAINER (action_area), 5); + gtk_box_set_spacing (action_area, 6); + + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + g_signal_connect (dialog, "show", + G_CALLBACK (dialog_show_callback), + dialog); + g_signal_connect (dialog, "close", + G_CALLBACK (dialog_close_callback), + dialog); + + /* The table that holds the captions */ + priv->table_alignment = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); + + priv->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + priv->table = gtk_table_new (4, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (priv->table), 12); + gtk_table_set_row_spacings (GTK_TABLE (priv->table), 6); + gtk_container_add (GTK_CONTAINER (priv->table_alignment), priv->table); + + priv->password_entry = gtk_entry_new (); + priv->password_entry_secondary = gtk_entry_new (); + + priv->show_passwords_checkbox = gtk_check_button_new_with_mnemonic (_("Sh_ow passwords")); + + /* We want to hold on to these during the table rearrangement */ + g_object_ref_sink (priv->password_entry); + g_object_ref_sink (priv->password_entry_secondary); + g_object_ref_sink (priv->show_passwords_checkbox); + + gtk_entry_set_visibility (GTK_ENTRY (priv->password_entry), FALSE); + gtk_entry_set_visibility (GTK_ENTRY (priv->password_entry_secondary), FALSE); + + g_signal_connect_swapped (priv->password_entry, "activate", + G_CALLBACK (gtk_window_activate_default), + dialog); + g_signal_connect_swapped (priv->password_entry_secondary, "activate", + G_CALLBACK (gtk_window_activate_default), + dialog); + + g_signal_connect (priv->show_passwords_checkbox, "toggled", + G_CALLBACK (show_passwords_toggled_cb), + dialog); + + add_table_rows (VPN_PASSWORD_DIALOG (dialog)); + + /* Adds some eye-candy to the dialog */ +#if GTK_CHECK_VERSION (3,1,6) + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); +#else + hbox = gtk_hbox_new (FALSE, 12); +#endif + gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); + dialog_icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION, GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (dialog_icon), 0.5, 0.0); + gtk_box_pack_start (GTK_BOX (hbox), dialog_icon, FALSE, FALSE, 0); + + /* Fills the vbox */ +#if GTK_CHECK_VERSION (3,1,6) + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 18); +#else + main_vbox = gtk_vbox_new (FALSE, 18); +#endif + + if (message) { + message_label = GTK_LABEL (gtk_label_new (message)); + gtk_label_set_justify (message_label, GTK_JUSTIFY_LEFT); + gtk_label_set_line_wrap (message_label, TRUE); + gtk_label_set_max_width_chars (message_label, 35); + gtk_size_group_add_widget (priv->group, GTK_WIDGET (message_label)); + gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (message_label), FALSE, FALSE, 0); + gtk_size_group_add_widget (priv->group, priv->table_alignment); + } + +#if GTK_CHECK_VERSION (3,1,6) + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); +#else + vbox = gtk_vbox_new (FALSE, 6); +#endif + gtk_box_pack_start (GTK_BOX (main_vbox), vbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), priv->table_alignment, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), main_vbox, FALSE, FALSE, 0); + gtk_box_pack_start (content, hbox, FALSE, FALSE, 0); + gtk_widget_show_all (GTK_WIDGET (content)); + + vpn_password_dialog_set_password (VPN_PASSWORD_DIALOG (dialog), password); + + return GTK_WIDGET (dialog); +} + +gboolean +vpn_password_dialog_run_and_block (VpnPasswordDialog *dialog) +{ + gint button_clicked; + + g_return_val_if_fail (dialog != NULL, FALSE); + g_return_val_if_fail (VPN_IS_PASSWORD_DIALOG (dialog), FALSE); + + button_clicked = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_hide (GTK_WIDGET (dialog)); + + return button_clicked == GTK_RESPONSE_OK; +} + +void +vpn_password_dialog_set_password (VpnPasswordDialog *dialog, + const char *password) +{ + VpnPasswordDialogPrivate *priv; + + g_return_if_fail (VPN_IS_PASSWORD_DIALOG (dialog)); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + gtk_entry_set_text (GTK_ENTRY (priv->password_entry), password ? password : ""); +} + +void +vpn_password_dialog_set_password_secondary (VpnPasswordDialog *dialog, + const char *password_secondary) +{ + VpnPasswordDialogPrivate *priv; + + g_return_if_fail (VPN_IS_PASSWORD_DIALOG (dialog)); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + gtk_entry_set_text (GTK_ENTRY (priv->password_entry_secondary), + password_secondary ? password_secondary : ""); +} + +void +vpn_password_dialog_set_show_password (VpnPasswordDialog *dialog, gboolean show) +{ + VpnPasswordDialogPrivate *priv; + + g_return_if_fail (dialog != NULL); + g_return_if_fail (VPN_IS_PASSWORD_DIALOG (dialog)); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + + show = !!show; + if (priv->show_password != show) { + priv->show_password = show; + add_table_rows (dialog); + } +} + +void +vpn_password_dialog_set_show_password_secondary (VpnPasswordDialog *dialog, + gboolean show) +{ + VpnPasswordDialogPrivate *priv; + + g_return_if_fail (dialog != NULL); + g_return_if_fail (VPN_IS_PASSWORD_DIALOG (dialog)); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + + show = !!show; + if (priv->show_password_secondary != show) { + priv->show_password_secondary = show; + add_table_rows (dialog); + } +} + +void +vpn_password_dialog_focus_password (VpnPasswordDialog *dialog) +{ + VpnPasswordDialogPrivate *priv; + + g_return_if_fail (dialog != NULL); + g_return_if_fail (VPN_IS_PASSWORD_DIALOG (dialog)); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + if (priv->show_password) + gtk_widget_grab_focus (priv->password_entry); +} + +void +vpn_password_dialog_focus_password_secondary (VpnPasswordDialog *dialog) +{ + VpnPasswordDialogPrivate *priv; + + g_return_if_fail (dialog != NULL); + g_return_if_fail (VPN_IS_PASSWORD_DIALOG (dialog)); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + if (priv->show_password_secondary) + gtk_widget_grab_focus (priv->password_entry_secondary); +} + +const char * +vpn_password_dialog_get_password (VpnPasswordDialog *dialog) +{ + VpnPasswordDialogPrivate *priv; + + g_return_val_if_fail (VPN_IS_PASSWORD_DIALOG (dialog), NULL); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + return gtk_entry_get_text (GTK_ENTRY (priv->password_entry)); +} + +const char * +vpn_password_dialog_get_password_secondary (VpnPasswordDialog *dialog) +{ + VpnPasswordDialogPrivate *priv; + + g_return_val_if_fail (VPN_IS_PASSWORD_DIALOG (dialog), NULL); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + return gtk_entry_get_text (GTK_ENTRY (priv->password_entry_secondary)); +} + +void vpn_password_dialog_set_password_label (VpnPasswordDialog *dialog, + const char *label) +{ + VpnPasswordDialogPrivate *priv; + + g_return_if_fail (dialog != NULL); + g_return_if_fail (VPN_IS_PASSWORD_DIALOG (dialog)); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + + g_free (priv->primary_password_label); + priv->primary_password_label = g_strdup (label); + + if (priv->show_password) + add_table_rows (dialog); +} + +void vpn_password_dialog_set_password_secondary_label (VpnPasswordDialog *dialog, + const char *label) +{ + VpnPasswordDialogPrivate *priv; + + g_return_if_fail (dialog != NULL); + g_return_if_fail (VPN_IS_PASSWORD_DIALOG (dialog)); + + priv = VPN_PASSWORD_DIALOG_GET_PRIVATE (dialog); + + g_free (priv->secondary_password_label); + priv->secondary_password_label = g_strdup (label); + + if (priv->show_password_secondary) + add_table_rows (dialog); +} + diff --git a/auth-dialog/vpn-password-dialog.h b/auth-dialog/vpn-password-dialog.h new file mode 100644 index 0000000..7d69320 --- /dev/null +++ b/auth-dialog/vpn-password-dialog.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* vpn-password-dialog.c - A use password prompting dialog widget. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the ree Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * Copyright (C) 2011 Red Hat, Inc. + * + * Authors: Ramiro Estrugo + * Dan Williams + */ + +#ifndef VPN_PASSWORD_DIALOG_H +#define VPN_PASSWORD_DIALOG_H + +#include + +G_BEGIN_DECLS + +#define VPN_TYPE_PASSWORD_DIALOG (vpn_password_dialog_get_type ()) +#define VPN_PASSWORD_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), VPN_TYPE_PASSWORD_DIALOG, VpnPasswordDialog)) +#define VPN_PASSWORD_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), VPN_TYPE_PASSWORD_DIALOG, VpnPasswordDialogClass)) +#define VPN_IS_PASSWORD_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), VPN_TYPE_PASSWORD_DIALOG)) +#define VPN_IS_PASSWORD_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), VPN_TYPE_PASSWORD_DIALOG)) + +typedef struct VpnPasswordDialog VpnPasswordDialog; +typedef struct VpnPasswordDialogClass VpnPasswordDialogClass; + +struct VpnPasswordDialog { + GtkDialog parent; +}; + +struct VpnPasswordDialogClass { + GtkDialogClass parent_class; +}; + +GType vpn_password_dialog_get_type (void); +GtkWidget* vpn_password_dialog_new (const char *title, + const char *message, + const char *password); + +gboolean vpn_password_dialog_run_and_block (VpnPasswordDialog *dialog); + +/* Attribute mutators */ +void vpn_password_dialog_set_show_password (VpnPasswordDialog *dialog, + gboolean show); +void vpn_password_dialog_focus_password (VpnPasswordDialog *dialog); +void vpn_password_dialog_set_password (VpnPasswordDialog *dialog, + const char *password); +void vpn_password_dialog_set_password_label (VpnPasswordDialog *dialog, + const char *label); + +void vpn_password_dialog_set_show_password_secondary (VpnPasswordDialog *dialog, + gboolean show); +void vpn_password_dialog_focus_password_secondary (VpnPasswordDialog *dialog); +void vpn_password_dialog_set_password_secondary (VpnPasswordDialog *dialog, + const char *password_secondary); +void vpn_password_dialog_set_password_secondary_label (VpnPasswordDialog *dialog, + const char *label); +/* Attribute accessors */ +const char *vpn_password_dialog_get_password (VpnPasswordDialog *dialog); + +const char *vpn_password_dialog_get_password_secondary (VpnPasswordDialog *dialog); + +G_END_DECLS + +#endif /* VPN_PASSWORD_DIALOG_H */ diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..1a112a6 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. +REQUIRED_AUTOMAKE_VERSION=1.7 +PKG_NAME=NetworkManager-iodine + +(test -f $srcdir/configure.ac) || { + echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" + echo " top-level $PKG_NAME directory" + exit 1 +} + +(cd $srcdir; + autoreconf --install && + intltoolize --force && + autoreconf && + ./configure --enable-maintainer-mode $@ +) + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..0961d0b --- /dev/null +++ b/configure.ac @@ -0,0 +1,92 @@ +AC_PREREQ(2.52) + +AC_INIT(NetworkManager-iodine, 0.0.1, agx@sigxcpu.org, NetworkManager-iodine) +AM_INIT_AUTOMAKE([subdir-objects no-dist-gzip dist-xz]) +AM_MAINTAINER_MODE + +AC_CONFIG_MACRO_DIR([m4]) + +AC_CONFIG_HEADERS([config.h]) + +dnl +dnl Require programs +dnl +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_INSTALL +AC_PROG_LIBTOOL + +dnl +dnl Required headers +dnl +AC_HEADER_STDC +AC_CHECK_HEADERS(fcntl.h paths.h sys/ioctl.h sys/time.h syslog.h unistd.h) + +dnl +dnl Checks for typedefs, structures, and compiler characteristics. +dnl +AC_TYPE_MODE_T +AC_TYPE_PID_T +AC_HEADER_TIME + +dnl +dnl Checks for library functions. +dnl +AC_PROG_GCC_TRADITIONAL +AC_FUNC_MEMCMP +AC_CHECK_FUNCS(select socket uname) + +dnl +dnl GNOME support +dnl +AC_ARG_WITH(gnome, AS_HELP_STRING([--without-gnome], [Build NetworkManager-iodine without GNOME support, e.g. vpn service only])) +AM_CONDITIONAL(WITH_GNOME, test x"$with_gnome" != xno) + +AC_ARG_WITH(authdlg, AS_HELP_STRING([--without-authdlg], [Build NetworkManager-iodine without authentication dialog])) +AM_CONDITIONAL(WITH_AUTHDLG, test x"$with_authdlg" != xno) + +GETTEXT_PACKAGE=NetworkManager-iodine +AC_SUBST(GETTEXT_PACKAGE) +AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package]) + +IT_PROG_INTLTOOL([0.35]) +AM_GLIB_GNU_GETTEXT + +PKG_CHECK_MODULES(GTHREAD, gthread-2.0) +AC_SUBST(GTHREAD_CFLAGS) +AC_SUBST(GTHREAD_LIBS) + +PKG_CHECK_MODULES(DBUS, dbus-glib-1 >= 0.74) +AC_SUBST(DBUS_CFLAGS) +AC_SUBST(DBUS_LIBS) + +if test x"$with_gnome" != xno; then + gtk3_req=2.91.4 + PKG_CHECK_MODULES(GTK, gtk+-3.0 > $gtk3_req, , + [PKG_CHECK_MODULES(GTK, gtk+-2.0 > $gtk2_req)]) + AC_SUBST(GTK_CFLAGS) + AC_SUBST(GTK_LIBS) + + PKG_CHECK_MODULES(GNOMEKEYRING, gnome-keyring-1) + AC_SUBST(GNOMEKEYRING_CFLAGS) + AC_SUBST(GNOMEKEYRING_LIBS) +fi + +PKG_CHECK_MODULES(NM, + NetworkManager >= 0.9.2 + libnm-util >= 0.9.2 + libnm-glib >= 0.9.2 + libnm-glib-vpn >= 0.9.2) +AC_SUBST(NM_CFLAGS) +AC_SUBST(NM_LIBS) + +NM_COMPILER_WARNINGS + +AC_CONFIG_FILES([ +Makefile +src/Makefile +auth-dialog/Makefile +properties/Makefile +po/Makefile.in +]) +AC_OUTPUT diff --git a/m4/compiler_warnings.m4 b/m4/compiler_warnings.m4 new file mode 100644 index 0000000..6cea2f7 --- /dev/null +++ b/m4/compiler_warnings.m4 @@ -0,0 +1,31 @@ +AC_DEFUN([NM_COMPILER_WARNINGS], +[AC_ARG_ENABLE(more-warnings, + AS_HELP_STRING([--enable-more-warnings], [Maximum compiler warnings]), + set_more_warnings="$enableval",set_more_warnings=yes) +AC_MSG_CHECKING(for more warnings, including -Werror) +if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then + AC_MSG_RESULT(yes) + CFLAGS="-Wall -Werror -std=gnu89 $CFLAGS" + + for option in -Wshadow -Wmissing-declarations -Wmissing-prototypes \ + -Wdeclaration-after-statement -Wstrict-prototypes \ + -Wfloat-equal -Wno-unused-parameter -Wno-sign-compare \ + -fno-strict-aliasing; do + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $option" + AC_MSG_CHECKING([whether gcc understands $option]) + AC_TRY_COMPILE([], [], + has_option=yes, + has_option=no,) + if test $has_option = no; then + CFLAGS="$SAVE_CFLAGS" + fi + AC_MSG_RESULT($has_option) + unset has_option + unset SAVE_CFLAGS + done + unset option +else + AC_MSG_RESULT(no) +fi +]) diff --git a/network-manager-iodine.doap b/network-manager-iodine.doap new file mode 100644 index 0000000..a24d8b9 --- /dev/null +++ b/network-manager-iodine.doap @@ -0,0 +1,23 @@ + + + + network-manager-iodine + Iodine support for NetworkManager + + + + + + + Guido Günther + + guidog + + + + + diff --git a/nm-iodine-service.conf b/nm-iodine-service.conf new file mode 100644 index 0000000..8c00cc1 --- /dev/null +++ b/nm-iodine-service.conf @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/nm-iodine-service.name.in b/nm-iodine-service.name.in new file mode 100644 index 0000000..4a3d471 --- /dev/null +++ b/nm-iodine-service.name.in @@ -0,0 +1,8 @@ +[VPN Connection] +name=iodine +service=org.freedesktop.NetworkManager.iodine +program=@LIBEXECDIR@/nm-iodine-service + +[GNOME] +auth-dialog=nm-iodine-auth-dialog +properties=libnm-iodine-properties diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..bc8cbb0 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1,2 @@ +# please keep this list sorted alphabetically +# diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..2aad63d --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,7 @@ +# List of source files containing translatable strings. +# Please keep this file sorted alphabetically. +auth-dialog/main.c +auth-dialog/vpn-password-dialog.c +properties/nm-iodine.c +[type: gettext/glade]properties/nm-iodine-dialog.ui +src/nm-iodine-service.c diff --git a/properties/Makefile.am b/properties/Makefile.am new file mode 100644 index 0000000..30e7529 --- /dev/null +++ b/properties/Makefile.am @@ -0,0 +1,34 @@ +plugindir = $(libdir)/NetworkManager +plugin_LTLIBRARIES = libnm-iodine-properties.la + +libnm_iodine_properties_la_SOURCES = \ + nm-iodine.c \ + nm-iodine.h + +uidir = $(datadir)/gnome-vpn-properties/iodine +ui_DATA = nm-iodine-dialog.ui + +libnm_iodine_properties_la_CFLAGS = \ + $(GTK_CFLAGS) \ + $(GCONF_CFLAGS) \ + $(NM_CFLAGS) \ + -DICONDIR=\""$(datadir)/pixmaps"\" \ + -DUIDIR=\""$(uidir)"\" \ + -DG_DISABLE_DEPRECATED \ + -DGDK_DISABLE_DEPRECATED \ + -DGNOME_DISABLE_DEPRECATED \ + -DGNOMELOCALEDIR=\"$(datadir)/locale\" \ + -DVERSION=\"$(VERSION)\" + +libnm_iodine_properties_la_LIBADD = \ + $(GTK_LIBS) \ + $(GCONF_LIBS) \ + $(NM_LIBS) + +libnm_iodine_properties_la_LDFLAGS = \ + -avoid-version + +CLEANFILES = *.bak *~ + +EXTRA_DIST = \ + $(ui_DATA) diff --git a/properties/nm-iodine-dialog.ui b/properties/nm-iodine-dialog.ui new file mode 100644 index 0000000..f362c6c --- /dev/null +++ b/properties/nm-iodine-dialog.ui @@ -0,0 +1,291 @@ + + + + + + + + + + + Saved + + + Always ask + + + Not required + + + + + True + False + 12 + 16 + + + True + False + 6 + + + True + False + 0 + <b>General</b> + True + + + False + False + 0 + + + + + True + False + 12 + + + True + False + 2 + 6 + 6 + + + True + False + 1 + 0 + + + True + True + True + + + + + + 1 + 2 + + + + + + True + False + 0 + _Toplevel Domain: + True + topdomain_entry + + + + + + + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + vertical + + + True + False + 0 + <b>Optional</b> + True + + + False + False + 0 + + + + + True + False + 12 + + + True + False + 6 + 6 + + + True + False + 0 + _Nameserver: + True + nameserver_entry + + + 0 + 0 + 1 + 1 + + + + + True + False + 0 + _Password: + True + password_entry + + + 0 + 1 + 1 + 1 + + + + + True + True + False + + True + + + 1 + 1 + 1 + 1 + + + + + True + False + 0 + _Fragment Size: + True + fragsize_entry + + + 0 + 3 + 1 + 1 + + + + + True + True + + True + + + 1 + 3 + 1 + 1 + + + + + True + True + + True + + + 1 + 0 + 1 + 1 + + + + + Show password + False + True + True + False + False + 0 + True + + + 1 + 2 + 1 + 1 + + + + + True + False + pass_type_model + + + + 0 + + + + + 2 + 1 + 1 + 1 + + + + + + + + + + + + + + + + False + True + 1 + + + + + True + True + 1 + + + + diff --git a/properties/nm-iodine.c b/properties/nm-iodine.c new file mode 100644 index 0000000..b1e7537 --- /dev/null +++ b/properties/nm-iodine.c @@ -0,0 +1,768 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * GNOME UI dialogs for configuring iodine VPN connections + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright © 2012 Guido Günther + * + * Based on network-manager-{openconnect,pptp} + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#define NM_VPN_API_SUBJECT_TO_CHANGE + +#include +#include +#include +#include + +#include "../src/nm-iodine-service.h" +#include "nm-iodine.h" + +#define IODINE_PLUGIN_NAME _("Iodine DNS Tunnel") +#define IODINE_PLUGIN_DESC _("Tunnel connections via DNS.") +#define IODINE_PLUGIN_SERVICE NM_DBUS_SERVICE_IODINE + +#define PW_TYPE_SAVE 0 +#define PW_TYPE_ASK 1 +#define PW_TYPE_UNUSED 2 + +/************** plugin class **************/ + +static void iodine_plugin_ui_interface_init (NMVpnPluginUiInterface + *iface_class); + +G_DEFINE_TYPE_EXTENDED (IodinePluginUi, iodine_plugin_ui, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_VPN_PLUGIN_UI_INTERFACE, + iodine_plugin_ui_interface_init)) + +/************** UI widget class **************/ + +static void iodine_plugin_ui_widget_interface_init (NMVpnPluginUiWidgetInterface + *iface_class); + +G_DEFINE_TYPE_EXTENDED (IodinePluginUiWidget, iodine_plugin_ui_widget,G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_VPN_PLUGIN_UI_WIDGET_INTERFACE, + iodine_plugin_ui_widget_interface_init)) + +#define IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IODINE_TYPE_PLUGIN_UI_WIDGET, IodinePluginUiWidgetPrivate)) + +typedef struct { + GtkBuilder *builder; + GtkWidget *widget; + GtkSizeGroup *group; + gboolean window_added; +} IodinePluginUiWidgetPrivate; + +static NMConnection * +import (NMVpnPluginUiInterface *iface, const char *path, GError **error) +{ + NMConnection *connection; + NMSettingConnection *s_con; + NMSettingVPN *s_vpn; + NMSettingIP4Config *s_ip4; + GKeyFile *keyfile; + GKeyFileFlags flags; + const char *buf; + + keyfile = g_key_file_new (); + flags = G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS; + + if (!g_key_file_load_from_file (keyfile, path, flags, error)) { + g_set_error (error, + 0, + 0, + "does not look like a %s VPN connection (parse failed)", + IODINE_PLUGIN_NAME); + return NULL; + } + + connection = nm_connection_new (); + s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + s_vpn = NM_SETTING_VPN (nm_setting_vpn_new ()); + g_object_set (s_vpn, + NM_SETTING_VPN_SERVICE_TYPE, + NM_DBUS_SERVICE_IODINE, + NULL); + nm_connection_add_setting (connection, NM_SETTING (s_vpn)); + + s_ip4 = NM_SETTING_IP4_CONFIG (nm_setting_ip4_config_new ()); + nm_connection_add_setting (connection, NM_SETTING (s_ip4)); + + /* top level domain */ + buf = g_key_file_get_string (keyfile, "iodine", "topdomain", NULL); + if (buf) { + nm_setting_vpn_add_data_item (s_vpn, NM_IODINE_KEY_TOPDOMAIN, buf); + } else { + g_set_error (error, + 0, + 0, + "does not look like a %s VPN connection " + "(no top level domain)", + IODINE_PLUGIN_NAME); + g_object_unref (connection); + return NULL; + } + + /* Optional Settings */ + /* Description */ + buf = g_key_file_get_string (keyfile, "iodine", "Description", NULL); + if (buf) + g_object_set (s_con, NM_SETTING_CONNECTION_ID, buf, NULL); + + /* Name server */ + buf = g_key_file_get_string (keyfile, "iodine", "Nameserver", NULL); + if (buf) + nm_setting_vpn_add_data_item (s_vpn, NM_IODINE_KEY_NAMESERVER, buf); + + /* Fragment size */ + buf = g_key_file_get_string (keyfile, "iodine", "Fragsize", NULL); + if (buf) + nm_setting_vpn_add_data_item (s_vpn, NM_IODINE_KEY_FRAGSIZE, "yes"); + + return connection; +} + +static gboolean +export (NMVpnPluginUiInterface *iface, + const char *path, + NMConnection *connection, + GError **error) +{ + NMSettingConnection *s_con; + NMSettingVPN *s_vpn; + const char *value; + const char *topdomain = NULL; + const char *nameserver = NULL; + const char *fragsize = NULL; + gboolean success = FALSE; + FILE *f; + + f = fopen (path, "w"); + if (!f) { + g_set_error (error, 0, 0, "could not open file for writing"); + return FALSE; + } + + s_con = NM_SETTING_CONNECTION (nm_connection_get_setting + (connection, NM_TYPE_SETTING_CONNECTION)); + + s_vpn = (NMSettingVPN *) nm_connection_get_setting (connection, + NM_TYPE_SETTING_VPN); + + value = nm_setting_vpn_get_data_item (s_vpn, NM_IODINE_KEY_TOPDOMAIN); + if (value && strlen (value)) + topdomain = value; + else { + g_set_error (error, 0, 0, + "connection was incomplete (missing top level domain)"); + goto done; + } + + value = nm_setting_vpn_get_data_item (s_vpn, NM_IODINE_KEY_NAMESERVER); + if (value && strlen (value)) + nameserver = value; + + value = nm_setting_vpn_get_data_item (s_vpn, NM_IODINE_KEY_FRAGSIZE); + if (value && strlen (value)) + fragsize = value; + + fprintf (f, + "[iodine]\n" + "Description=%s\n" + "Topdomain=%s\n" + "Nameserver=%s\n" + "Fragsize=%s\n", + /* Description */ nm_setting_connection_get_id (s_con), + /* Topdomain */ topdomain, + /* Nameserver */ nameserver, + /* Fragsize */ fragsize); + + success = TRUE; + +done: + fclose (f); + return success; +} + +GQuark +iodine_plugin_ui_error_quark (void) +{ + static GQuark error_quark = 0; + + if (G_UNLIKELY (error_quark == 0)) + error_quark = g_quark_from_static_string ("iodine-plugin-ui-error-quark"); + + return error_quark; +} + +/* This should really be standard. */ +#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } + +GType +iodine_plugin_ui_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + /* Unknown error. */ + ENUM_ENTRY (IODINE_PLUGIN_UI_ERROR_UNKNOWN, + "UnknownError"), + /* The specified property was invalid. */ + ENUM_ENTRY (IODINE_PLUGIN_UI_ERROR_INVALID_PROPERTY, + "InvalidProperty"), + /* The specified property was missing and is required. */ + ENUM_ENTRY (IODINE_PLUGIN_UI_ERROR_MISSING_PROPERTY, + "MissingProperty"), + { 0, 0, 0 } + }; + etype = g_enum_register_static ("IodinePluginUiError", values); + } + return etype; +} + +static gboolean +check_validity (IodinePluginUiWidget *self, GError **error) +{ + IodinePluginUiWidgetPrivate *priv = \ + IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (self); + GtkWidget *widget; + const char *str; + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "topdomain_entry")); + str = gtk_entry_get_text (GTK_ENTRY (widget)); + if (!str || !strlen (str)) { + g_set_error (error, + IODINE_PLUGIN_UI_ERROR, + IODINE_PLUGIN_UI_ERROR_INVALID_PROPERTY, + NM_IODINE_KEY_TOPDOMAIN); + return FALSE; + } + + return TRUE; +} + +static void +stuff_changed_cb (GtkWidget *widget, gpointer user_data) +{ + g_signal_emit_by_name (IODINE_PLUGIN_UI_WIDGET (user_data), "changed"); +} + +static void +setup_password_widget (IodinePluginUiWidget *self, + const char *entry_name, + NMSettingVPN *s_vpn, + const char *secret_name) +{ + IodinePluginUiWidgetPrivate *priv = \ + IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (self); + + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + GtkWidget *widget; + const char *value; + + /* Default to agent-owned for new connections */ + if (s_vpn == NULL) + secret_flags = NM_SETTING_SECRET_FLAG_AGENT_OWNED; + + widget = (GtkWidget *) gtk_builder_get_object (priv->builder, entry_name); + g_assert (widget); + gtk_size_group_add_widget (priv->group, widget); + + if (s_vpn) { + value = nm_setting_vpn_get_secret (s_vpn, secret_name); + gtk_entry_set_text (GTK_ENTRY (widget), value ? value : ""); + nm_setting_get_secret_flags (NM_SETTING (s_vpn), + secret_name, + &secret_flags, + NULL); + } + + secret_flags &= ~(NM_SETTING_SECRET_FLAG_NOT_SAVED | + NM_SETTING_SECRET_FLAG_NOT_REQUIRED); + g_object_set_data (G_OBJECT (widget), + "flags", + GUINT_TO_POINTER (secret_flags)); + + g_signal_connect (widget, "changed", G_CALLBACK (stuff_changed_cb), self); +} + +static void +show_toggled_cb (GtkCheckButton *button, IodinePluginUiWidget *self) +{ + IodinePluginUiWidgetPrivate *priv = \ + IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (self); + GtkWidget *widget; + gboolean visible; + + visible = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "password_entry")); + g_assert (widget); + gtk_entry_set_visibility (GTK_ENTRY (widget), visible); +} + +static void +pw_type_combo_changed_cb (GtkWidget *combo, gpointer user_data) +{ + IodinePluginUiWidget *self = IODINE_PLUGIN_UI_WIDGET (user_data); + IodinePluginUiWidgetPrivate *priv = \ + IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (self); + GtkWidget *entry; + + entry = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "password_entry")); + g_assert (entry); + + /* If the user chose "Not required", desensitize and clear the correct + * password entry. + */ + switch (gtk_combo_box_get_active (GTK_COMBO_BOX (combo))) { + case PW_TYPE_ASK: + case PW_TYPE_UNUSED: + gtk_entry_set_text (GTK_ENTRY (entry), ""); + gtk_widget_set_sensitive (entry, FALSE); + break; + default: + gtk_widget_set_sensitive (entry, TRUE); + break; + } + + stuff_changed_cb (combo, self); +} + +static void +init_one_pw_combo (IodinePluginUiWidget *self, + NMSettingVPN *s_vpn, + const char *combo_name, + const char *secret_key, + const char *entry_name) +{ + IodinePluginUiWidgetPrivate *priv = \ + IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (self); + int active = -1; + GtkWidget *widget; + GtkListStore *store; + const char *value = NULL; + guint32 default_idx = 1; + NMSettingSecretFlags pw_flags = NM_SETTING_SECRET_FLAG_NONE; + + /* If there's already a password and the password type can't be found in + * the VPN settings, default to saving it. Otherwise, always ask for it. + */ + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, entry_name)); + g_assert (widget); + value = gtk_entry_get_text (GTK_ENTRY (widget)); + if (value && strlen (value)) + default_idx = 0; + + store = GTK_LIST_STORE(gtk_builder_get_object (priv->builder, "pass_type_model")); + g_assert (store); + + if (s_vpn) + nm_setting_get_secret_flags (NM_SETTING (s_vpn), + secret_key, + &pw_flags, + NULL); + if ((active < 0) + && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + active = PW_TYPE_SAVE; + } + + if ((active < 0) && (pw_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)) + active = PW_TYPE_ASK; + + if ((active < 0) && (pw_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) + active = PW_TYPE_UNUSED; + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, combo_name)); + g_assert (widget); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), + active < 0 ? default_idx : active); + + pw_type_combo_changed_cb (widget, self); + g_signal_connect (G_OBJECT (widget), + "changed", + G_CALLBACK (pw_type_combo_changed_cb), self); +} + +static gboolean +init_plugin_ui (IodinePluginUiWidget *self, + NMConnection *connection, + GError **error) +{ + IodinePluginUiWidgetPrivate *priv = \ + IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (self); + NMSettingVPN *s_vpn; + GtkWidget *widget; + const char *value; + + s_vpn = (NMSettingVPN *) nm_connection_get_setting (connection, + NM_TYPE_SETTING_VPN); + + priv->group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "topdomain_entry")); + if (!widget) + return FALSE; + gtk_size_group_add_widget (priv->group, widget); + if (s_vpn) { + value = nm_setting_vpn_get_data_item (s_vpn, NM_IODINE_KEY_TOPDOMAIN); + if (value) + gtk_entry_set_text (GTK_ENTRY (widget), value); + } + g_signal_connect (G_OBJECT (widget), + "changed", + G_CALLBACK (stuff_changed_cb), self); + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "nameserver_entry")); + if (!widget) + return FALSE; + gtk_size_group_add_widget (priv->group, widget); + if (s_vpn) { + value = nm_setting_vpn_get_data_item (s_vpn, NM_IODINE_KEY_NAMESERVER); + if (value) + gtk_entry_set_text (GTK_ENTRY (widget), value); + } + g_signal_connect (G_OBJECT (widget), + "changed", + G_CALLBACK (stuff_changed_cb), self); + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "fragsize_entry")); + if (!widget) + return FALSE; + if (s_vpn) { + value = nm_setting_vpn_get_data_item (s_vpn, NM_IODINE_KEY_FRAGSIZE); + if (value) + gtk_entry_set_text (GTK_ENTRY (widget), value); + } + g_signal_connect (G_OBJECT (widget), + "changed", + G_CALLBACK (stuff_changed_cb), self); + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "show_passwords_checkbutton")); + g_signal_connect (G_OBJECT (widget), "toggled", + (GCallback) show_toggled_cb, + self); + + setup_password_widget (self, + "password_entry", + s_vpn, + NM_IODINE_KEY_PASSWORD); + + init_one_pw_combo (self, + s_vpn, + "pass_type_combo", + NM_IODINE_KEY_PASSWORD, + "password_entry"); + return TRUE; +} + +static GObject * +get_widget (NMVpnPluginUiWidgetInterface *iface) +{ + IodinePluginUiWidget *self = IODINE_PLUGIN_UI_WIDGET (iface); + IodinePluginUiWidgetPrivate *priv = IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (self); + + return G_OBJECT (priv->widget); +} + +static void +save_password_and_flags (NMSettingVPN *s_vpn, + GtkBuilder *builder, + const char *entry_name, + const char *combo_name, + const char *secret_key) +{ + NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE; + const char *password; + GtkWidget *entry; + GtkWidget *combo; + + /* Grab original password flags */ + entry = GTK_WIDGET (gtk_builder_get_object (builder, entry_name)); + flags = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (entry), "flags")); + + /* And set new ones based on the type combo */ + combo = GTK_WIDGET (gtk_builder_get_object (builder, combo_name)); + + switch (gtk_combo_box_get_active (GTK_COMBO_BOX (combo))) { + case PW_TYPE_SAVE: + password = gtk_entry_get_text (GTK_ENTRY (entry)); + if (password && strlen (password)) + nm_setting_vpn_add_secret (s_vpn, secret_key, password); + break; + case PW_TYPE_UNUSED: + flags |= NM_SETTING_SECRET_FLAG_NOT_REQUIRED; + break; + case PW_TYPE_ASK: + default: + flags |= NM_SETTING_SECRET_FLAG_NOT_SAVED; + break; + } + + /* Set new secret flags */ + nm_setting_set_secret_flags (NM_SETTING (s_vpn), secret_key, flags, NULL); +} + +static gboolean +update_connection (NMVpnPluginUiWidgetInterface *iface, + NMConnection *connection, + GError **error) +{ + IodinePluginUiWidget *self = IODINE_PLUGIN_UI_WIDGET (iface); + IodinePluginUiWidgetPrivate *priv = \ + IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (self); + NMSettingVPN *s_vpn; + GtkWidget *widget; + char *str; + + if (!check_validity (self, error)) + return FALSE; + + s_vpn = NM_SETTING_VPN (nm_setting_vpn_new ()); + g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, + NM_DBUS_SERVICE_IODINE, + NULL); + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "topdomain_entry")); + g_assert(widget); + str = (char *) gtk_entry_get_text (GTK_ENTRY (widget)); + if (str && strlen (str)) + nm_setting_vpn_add_data_item (s_vpn, NM_IODINE_KEY_TOPDOMAIN, str); + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "nameserver_entry")); + g_assert(widget); + str = (char *) gtk_entry_get_text (GTK_ENTRY (widget)); + if (str && strlen (str)) + nm_setting_vpn_add_data_item (s_vpn, NM_IODINE_KEY_NAMESERVER, str); + + widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "fragsize_entry")); + g_assert(widget); + str = (char *) gtk_entry_get_text (GTK_ENTRY (widget)); + if (str && strlen (str)) + nm_setting_vpn_add_data_item (s_vpn, NM_IODINE_KEY_FRAGSIZE, str); + + /* User password and flags */ + save_password_and_flags (s_vpn, + priv->builder, + "password_entry", + "pass_type_combo", + NM_IODINE_KEY_PASSWORD); + + nm_connection_add_setting (connection, NM_SETTING (s_vpn)); + return TRUE; +} + +static NMVpnPluginUiWidgetInterface * +nm_vpn_plugin_ui_widget_interface_new (NMConnection *connection, GError **error) +{ + NMVpnPluginUiWidgetInterface *object; + IodinePluginUiWidgetPrivate *priv; + char *ui_file; + + if (error) + g_return_val_if_fail (*error == NULL, NULL); + + object = NM_VPN_PLUGIN_UI_WIDGET_INTERFACE \ + (g_object_new (IODINE_TYPE_PLUGIN_UI_WIDGET, NULL)); + + if (!object) { + g_set_error (error, IODINE_PLUGIN_UI_ERROR, 0, + "could not create iodine object"); + return NULL; + } + + priv = IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (object); + ui_file = g_strdup_printf ("%s/%s", UIDIR, "nm-iodine-dialog.ui"); + priv->builder = gtk_builder_new (); + + gtk_builder_set_translation_domain (priv->builder, GETTEXT_PACKAGE); + if (!gtk_builder_add_from_file (priv->builder, ui_file, error)) { + g_warning ("Couldn't load builder file: %s", + error && *error ? (*error)->message : "(unknown)"); + g_clear_error (error); + g_set_error (error, IODINE_PLUGIN_UI_ERROR, 0, + "could not load required resources at %s", ui_file); + g_free (ui_file); + g_object_unref (object); + return NULL; + } + g_free (ui_file); + + priv->widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, + "iodine-vbox")); + if (!priv->widget) { + g_set_error (error, IODINE_PLUGIN_UI_ERROR, 0, + "could not load UI widget"); + g_object_unref (object); + return NULL; + } + g_object_ref_sink (priv->widget); + + if (!init_plugin_ui (IODINE_PLUGIN_UI_WIDGET (object), connection, error)) { + g_object_unref (object); + return NULL; + } + + return object; +} + +static void +dispose (GObject *object) +{ + IodinePluginUiWidget *plugin = IODINE_PLUGIN_UI_WIDGET (object); + IodinePluginUiWidgetPrivate *priv = \ + IODINE_PLUGIN_UI_WIDGET_GET_PRIVATE (plugin); + + if (priv->group) + g_object_unref (priv->group); + + if (priv->widget) + g_object_unref (priv->widget); + + if (priv->builder) + g_object_unref (priv->builder); + + G_OBJECT_CLASS (iodine_plugin_ui_widget_parent_class)->dispose (object); +} + +static void +iodine_plugin_ui_widget_class_init (IodinePluginUiWidgetClass *req_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (req_class); + + g_type_class_add_private (req_class, sizeof (IodinePluginUiWidgetPrivate)); + + object_class->dispose = dispose; +} + +static void +iodine_plugin_ui_widget_init (IodinePluginUiWidget *plugin) +{ +} + +static void +iodine_plugin_ui_widget_interface_init (NMVpnPluginUiWidgetInterface *iface_class) +{ + /* interface implementation */ + iface_class->get_widget = get_widget; + iface_class->update_connection = update_connection; +} + +static guint32 +get_capabilities (NMVpnPluginUiInterface *iface) +{ + return (NM_VPN_PLUGIN_UI_CAPABILITY_IMPORT | + NM_VPN_PLUGIN_UI_CAPABILITY_EXPORT); +} + +static NMVpnPluginUiWidgetInterface * +ui_factory (NMVpnPluginUiInterface *iface, + NMConnection *connection, + GError **error) +{ + return nm_vpn_plugin_ui_widget_interface_new (connection, error); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case NM_VPN_PLUGIN_UI_INTERFACE_PROP_NAME: + g_value_set_string (value, IODINE_PLUGIN_NAME); + break; + case NM_VPN_PLUGIN_UI_INTERFACE_PROP_DESC: + g_value_set_string (value, IODINE_PLUGIN_DESC); + break; + case NM_VPN_PLUGIN_UI_INTERFACE_PROP_SERVICE: + g_value_set_string (value, IODINE_PLUGIN_SERVICE); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +iodine_plugin_ui_class_init (IodinePluginUiClass *req_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (req_class); + + object_class->get_property = get_property; + + g_object_class_override_property (object_class, + NM_VPN_PLUGIN_UI_INTERFACE_PROP_NAME, + NM_VPN_PLUGIN_UI_INTERFACE_NAME); + + g_object_class_override_property (object_class, + NM_VPN_PLUGIN_UI_INTERFACE_PROP_DESC, + NM_VPN_PLUGIN_UI_INTERFACE_DESC); + + g_object_class_override_property (object_class, + NM_VPN_PLUGIN_UI_INTERFACE_PROP_SERVICE, + NM_VPN_PLUGIN_UI_INTERFACE_SERVICE); +} + +static void +iodine_plugin_ui_init (IodinePluginUi *plugin) +{ +} + +static void +iodine_plugin_ui_interface_init (NMVpnPluginUiInterface *iface_class) +{ + /* interface implementation */ + iface_class->ui_factory = ui_factory; + iface_class->get_capabilities = get_capabilities; + iface_class->import_from_file = import; + iface_class->export_to_file = export; +} + +G_MODULE_EXPORT NMVpnPluginUiInterface * +nm_vpn_plugin_ui_factory (GError **error) +{ + if (error) + g_return_val_if_fail (*error == NULL, NULL); + + return NM_VPN_PLUGIN_UI_INTERFACE (g_object_new (IODINE_TYPE_PLUGIN_UI, + NULL)); +} diff --git a/properties/nm-iodine.h b/properties/nm-iodine.h new file mode 100644 index 0000000..1769fff --- /dev/null +++ b/properties/nm-iodine.h @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * GNOME UI dialogs for configuring iodine VPN connections + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright © 2012 Guido Günther + */ + +#ifndef _NM_IODINE_H_ +#define _NM_IODINE_H_ + +#include + +typedef enum +{ + IODINE_PLUGIN_UI_ERROR_UNKNOWN = 0, + IODINE_PLUGIN_UI_ERROR_INVALID_PROPERTY, + IODINE_PLUGIN_UI_ERROR_MISSING_PROPERTY +} IodinePluginUiError; + + +GQuark iodine_plugin_ui_error_quark (void); +#define IODINE_PLUGIN_UI_ERROR iodine_plugin_ui_error_quark () + +#define IODINE_TYPE_PLUGIN_UI_ERROR (iodine_plugin_ui_error_get_type ()) +GType iodine_plugin_ui_error_get_type (void); + +#define IODINE_TYPE_PLUGIN_UI (iodine_plugin_ui_get_type ()) +#define IODINE_PLUGIN_UI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IODINE_TYPE_PLUGIN_UI, IodinePluginUi)) +#define IODINE_PLUGIN_UI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IODINE_TYPE_PLUGIN_UI, IodinePluginUiClass)) +#define IODINE_IS_PLUGIN_UI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IODINE_TYPE_PLUGIN_UI)) +#define IODINE_IS_PLUGIN_UI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), IODINE_TYPE_PLUGIN_UI)) +#define IODINE_PLUGIN_UI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IODINE_TYPE_PLUGIN_UI, IodinePluginUiClass)) + +typedef struct _IodinePluginUi IodinePluginUi; +typedef struct _IodinePluginUiClass IodinePluginUiClass; + +struct _IodinePluginUi { + GObject parent; +}; + +struct _IodinePluginUiClass { + GObjectClass parent; +}; + +GType iodine_plugin_ui_get_type (void); + + +#define IODINE_TYPE_PLUGIN_UI_WIDGET (iodine_plugin_ui_widget_get_type ()) +#define IODINE_PLUGIN_UI_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IODINE_TYPE_PLUGIN_UI_WIDGET, IodinePluginUiWidget)) +#define IODINE_PLUGIN_UI_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IODINE_TYPE_PLUGIN_UI_WIDGET, IodinePluginUiWidgetClass)) +#define IODINE_IS_PLUGIN_UI_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IODINE_TYPE_PLUGIN_UI_WIDGET)) +#define IODINE_IS_PLUGIN_UI_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), IODINE_TYPE_PLUGIN_UI_WIDGET)) +#define IODINE_PLUGIN_UI_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IODINE_TYPE_PLUGIN_UI_WIDGET, IodinePluginUiWidgetClass)) + +typedef struct _IodinePluginUiWidget IodinePluginUiWidget; +typedef struct _IodinePluginUiWidgetClass IodinePluginUiWidgetClass; + +struct _IodinePluginUiWidget { + GObject parent; +}; + +struct _IodinePluginUiWidgetClass { + GObjectClass parent; +}; + +GType iodine_plugin_ui_widget_get_type (void); + +#endif /* _NM_IODINE_H_ */ + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..c7f3a1e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,30 @@ +INCLUDES = -I${top_srcdir} + +AM_CPPFLAGS = \ + $(DBUS_CFLAGS) \ + $(GTHREAD_CFLAGS) \ + $(NM_CFLAGS) \ + -DG_DISABLE_DEPRECATED \ + -DBINDIR=\"$(bindir)\" \ + -DPREFIX=\""$(prefix)"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DVERSION="\"$(VERSION)\"" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DLOCALSTATEDIR=\""$(localstatedir)"\" \ + -DDATADIR=\"$(datadir)\" + +libexec_PROGRAMS = \ + nm-iodine-service + +nm_iodine_service_SOURCES = \ + nm-iodine-service.c \ + nm-iodine-service.h + + +nm_iodine_service_LDADD = \ + $(DBUS_LIBS) \ + $(GTHREAD_LIBS) \ + $(NM_LIBS) + +CLEANFILES = *~ diff --git a/src/nm-iodine-service.c b/src/nm-iodine-service.c new file mode 100644 index 0000000..4c97581 --- /dev/null +++ b/src/nm-iodine-service.c @@ -0,0 +1,672 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * NetworkManager iodine VPN connections + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright © 2012 Guido Günther + * + * Based on network-manager-{openconnect,pptp} + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "nm-iodine-service.h" +#include "nm-utils.h" + +G_DEFINE_TYPE (NMIODINEPlugin, nm_iodine_plugin, NM_TYPE_VPN_PLUGIN) + +typedef struct { + GPid pid; + GHashTable *ip4config; +} NMIODINEPluginPrivate; + +#define NM_IODINE_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IODINE_PLUGIN, NMIODINEPluginPrivate)) + +static const char *iodine_binary_paths[] = +{ + "/usr/bin/iodine", + "/usr/sbin/iodine", + "/usr/local/bin/iodine", + "/usr/local/sbin/iodine", + "/opt/bin/iodine", + "/opt/sbin/iodine", + NULL +}; + +#define NM_IODINE_HELPER_PATH LIBEXECDIR"/nm-iodine-service-iodine-helper" + +typedef struct { + const char *name; + GType type; + gint int_min; + gint int_max; +} ValidProperty; + +static ValidProperty valid_properties[] = { + { NM_IODINE_KEY_TOPDOMAIN, G_TYPE_STRING, 0, 0 }, + { NM_IODINE_KEY_NAMESERVER, G_TYPE_STRING, 0, 0 }, + { NM_IODINE_KEY_FRAGSIZE, G_TYPE_STRING, 0, 0 }, + { NULL, G_TYPE_NONE, 0, 0 } +}; + +static ValidProperty valid_secrets[] = { + { NM_IODINE_KEY_PASSWORD, G_TYPE_STRING, 0, 0 }, + { NULL, G_TYPE_NONE, 0, 0 } +}; + +typedef struct ValidateInfo { + ValidProperty *table; + GError **error; + gboolean have_items; +} ValidateInfo; + +static void +validate_one_property (const char *key, const char *value, gpointer user_data) +{ + ValidateInfo *info = (ValidateInfo *) user_data; + int i; + + if (*(info->error)) + return; + + info->have_items = TRUE; + + /* 'name' is the setting name; always allowed but unused */ + if (!strcmp (key, NM_SETTING_NAME)) + return; + + for (i = 0; info->table[i].name; i++) { + ValidProperty prop = info->table[i]; + long int tmp; + + if (strcmp (prop.name, key)) + continue; + + switch (prop.type) { + case G_TYPE_STRING: + return; /* valid */ + case G_TYPE_INT: + errno = 0; + tmp = strtol (value, NULL, 10); + if (errno == 0 && tmp >= prop.int_min && tmp <= prop.int_max) + return; /* valid */ + + g_set_error (info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("invalid integer property '%s' or out of range " + "[%d -> %d]"), + key, prop.int_min, prop.int_max); + break; + case G_TYPE_BOOLEAN: + if (!strcmp (value, "yes") || !strcmp (value, "no")) + return; /* valid */ + + g_set_error (info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("invalid boolean property '%s' (not yes or no)"), + key); + break; + default: + g_set_error (info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("unhandled property '%s' type %s"), + key, g_type_name (prop.type)); + break; + } + } + + /* Did not find the property from valid_properties or the type did not + match */ + if (!info->table[i].name && strncmp(key, "form:", 5)) { + g_warning ("property '%s' unknown", key); + if (0) + g_set_error (info->error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("property '%s' invalid or not supported"), + key); + } +} + +static gboolean +nm_iodine_properties_validate (NMSettingVPN *s_vpn, GError **error) +{ + ValidateInfo info = { &valid_properties[0], error, FALSE }; + + nm_setting_vpn_foreach_data_item (s_vpn, validate_one_property, &info); + if (!info.have_items) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + "%s", + _("No VPN configuration options.")); + return FALSE; + } + + return *error ? FALSE : TRUE; +} + + +static gboolean +nm_iodine_secrets_validate (NMSettingVPN *s_vpn, GError **error) +{ + ValidateInfo info = { &valid_secrets[0], error, FALSE }; + + nm_setting_vpn_foreach_secret (s_vpn, validate_one_property, &info); + if (!info.have_items) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + "%s", + _("No VPN secrets!")); + return FALSE; + } + + return *error ? FALSE : TRUE; +} + +static GValue * +str_to_gvalue (const char *str, gboolean try_convert) +{ + GValue *val; + + /* Empty */ + if (!str || strlen (str) < 1) + return NULL; + + if (!g_utf8_validate (str, -1, NULL)) { + if (try_convert && !(str = g_convert (str, + -1, + "ISO-8859-1", + "UTF-8", + NULL, + NULL, + NULL))) + str = g_convert (str, -1, "C", "UTF-8", NULL, NULL, NULL); + if (!str) + /* Invalid */ + return NULL; + } + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_STRING); + g_value_set_string (val, str); + return val; +} + +static GValue * +uint_to_gvalue (guint32 num) +{ + GValue *val; + + if (num == 0) + return NULL; + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_UINT); + g_value_set_uint (val, num); + + return val; +} + +static GValue * +addr_to_gvalue (const char *str) +{ + struct in_addr temp_addr; + + /* Empty */ + if (!str || strlen (str) < 1) + return NULL; + + if (inet_pton (AF_INET, str, &temp_addr) <= 0) + return NULL; + + return uint_to_gvalue (temp_addr.s_addr); +} + +static void +value_destroy (gpointer data) +{ + GValue *val = (GValue *) data; + + g_value_unset (val); + g_slice_free (GValue, val); +} + +static gboolean +iodine_parse_stderr_line (const char* line, GHashTable *ip4config) +{ + gchar **split; + GValue *val; + gint len; + gint ret = TRUE; + + split = g_strsplit (line, " ", 0); + len = g_strv_length (split); + if (len < 2) + goto out; + + if (g_str_has_prefix(line, "Server tunnel IP is ")) { + g_debug("PTP address: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_PTP, + val); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, + val); + } else if (g_str_has_prefix(line, "Sending DNS queries for ")) { + g_debug("External gw: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY, + val); + } else if (g_str_has_prefix(line, "Sending raw traffic directly to ")) { + /* If the DNS server is directly reachable we need to set it + as external gateway overwriting the above valus */ + g_debug("Overwrite ext. gw. address: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY, + val); + } else if (g_str_has_prefix(line, "Setting IP of dns")) { + g_debug("Address: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, + val); + } else if (g_str_has_prefix(line, "Setting MTU of ")) { + g_debug("MTU: %s", split[len-1]); + val = addr_to_gvalue (split[len-1]); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_MTU, + val); + } else if (g_str_has_prefix(line, "Opened dns")) { + g_debug("Interface: %s", split[len-1]); + val = str_to_gvalue (split[len-1], FALSE); + if (val) + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, + val); + } else if (g_str_has_prefix(line, + "Connection setup complete, " + "transmitting data.")) { + val = uint_to_gvalue(27); + g_hash_table_insert (ip4config, + NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, + val); + ret = FALSE; + } else + g_debug("%s", line); + +out: + g_strfreev(split); + return ret; +} + + +static gboolean +iodine_stderr_cb (GIOChannel *source, GIOCondition condition, gpointer plugin) +{ + GIOStatus status; + GError *err = NULL; + gchar *line; + gint ret, l; + NMIODINEPluginPrivate *priv = NM_IODINE_PLUGIN_GET_PRIVATE (plugin); + + status = g_io_channel_read_line (source, &line, NULL, NULL, &err); + if (status != G_IO_STATUS_NORMAL) { + g_warning ("Fetching data failed: %s", err->message); + return FALSE; + } + + l = strlen(line); + if (l) + line[l-1] = '\0'; + + ret = iodine_parse_stderr_line(line, priv->ip4config); + if (!ret) { + g_debug("Parsing done, sending IP4 config"); + nm_vpn_plugin_set_ip4_config(plugin, priv->ip4config); + + g_hash_table_destroy (priv->ip4config); + priv->ip4config = NULL; + } + g_free (line); + return TRUE; +} + + +static void +iodine_watch_cb (GPid pid, gint status, gpointer user_data) +{ + NMIODINEPlugin *plugin = NM_IODINE_PLUGIN (user_data); + NMIODINEPluginPrivate *priv = NM_IODINE_PLUGIN_GET_PRIVATE (plugin); + guint error = 0; + + if (WIFEXITED (status)) { + error = WEXITSTATUS (status); + if (error != 0) + g_warning ("iodine exited with error code %d", error); + } + else if (WIFSTOPPED (status)) + g_warning ("iodine stopped unexpectedly with signal %d", + WSTOPSIG (status)); + else if (WIFSIGNALED (status)) + g_warning ("iodine died with signal %d", WTERMSIG (status)); + else + g_warning ("iodine died from an unknown cause"); + + /* Reap child if needed. */ + waitpid (priv->pid, NULL, WNOHANG); + priv->pid = 0; + + /* Must be after data->state is set since signals use data->state */ + if (error) { + nm_vpn_plugin_failure (NM_VPN_PLUGIN (plugin), + NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED); + } + + nm_vpn_plugin_set_state (NM_VPN_PLUGIN (plugin), + NM_VPN_SERVICE_STATE_STOPPED); +} + +static gboolean +has_user(const char* user) +{ + return (getpwnam(user) == NULL) ? FALSE : TRUE; +} + +static gint +nm_iodine_start_iodine_binary(NMIODINEPlugin *plugin, + NMSettingVPN *s_vpn, + GError **error) +{ + GPid pid; + const char **iodine_binary = NULL; + GPtrArray *iodine_argv; + GSource *iodine_watch; + GIOChannel *stderr_channel; + gint stdin_fd, stderr_fd; + const char *props_topdomain, *props_fragsize, *props_nameserver, *passwd; + + /* Find iodine */ + iodine_binary = iodine_binary_paths; + while (*iodine_binary != NULL) { + if (g_file_test (*iodine_binary, G_FILE_TEST_EXISTS)) + break; + iodine_binary++; + } + + if (!*iodine_binary) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED, + "%s", + _("Could not find iodine binary.")); + return -1; + } + + props_fragsize = nm_setting_vpn_get_data_item (s_vpn, + NM_IODINE_KEY_FRAGSIZE); + props_nameserver = nm_setting_vpn_get_data_item (s_vpn, + NM_IODINE_KEY_NAMESERVER); + props_topdomain = nm_setting_vpn_get_data_item (s_vpn, + NM_IODINE_KEY_TOPDOMAIN); + + passwd = nm_setting_vpn_get_secret (s_vpn, NM_IODINE_KEY_PASSWORD); + + iodine_argv = g_ptr_array_new (); + g_ptr_array_add (iodine_argv, (gpointer) (*iodine_binary)); + /* Run in foreground */ + g_ptr_array_add (iodine_argv, (gpointer) "-f"); + + if (props_fragsize && strlen(props_fragsize)) { + g_ptr_array_add (iodine_argv, (gpointer) "-m"); + g_ptr_array_add (iodine_argv, (gpointer) props_fragsize); + } + + if (passwd && strlen(passwd)) { + g_ptr_array_add (iodine_argv, (gpointer) "-P"); + g_ptr_array_add (iodine_argv, (gpointer) passwd); + } + + if (has_user(NM_IODINE_USER)) { + g_ptr_array_add (iodine_argv, (gpointer) "-u"); + g_ptr_array_add (iodine_argv, (gpointer) NM_IODINE_USER); + } else + g_warning("Running as root user"); + + if (props_nameserver && strlen(props_nameserver)) + g_ptr_array_add (iodine_argv, (gpointer) props_nameserver); + + if (props_topdomain && strlen(props_topdomain)) + g_ptr_array_add (iodine_argv, (gpointer) props_topdomain); + + g_ptr_array_add (iodine_argv, NULL); + + if (!g_spawn_async_with_pipes (NULL, (char **) iodine_argv->pdata, NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, + &pid, &stdin_fd, NULL, &stderr_fd, error)) { + g_ptr_array_free (iodine_argv, TRUE); + g_warning ("iodine failed to start. error: '%s'", (*error)->message); + return -1; + } + g_ptr_array_free (iodine_argv, TRUE); + + g_message ("iodine started with pid %d", pid); + close(stdin_fd); + + stderr_channel = g_io_channel_unix_new (stderr_fd); + g_io_add_watch(stderr_channel, + G_IO_IN, + iodine_stderr_cb, + plugin); + + NM_IODINE_PLUGIN_GET_PRIVATE (plugin)->pid = pid; + iodine_watch = g_child_watch_source_new (pid); + g_source_set_callback (iodine_watch, + (GSourceFunc) iodine_watch_cb, + plugin, + NULL); + g_source_attach (iodine_watch, NULL); + g_source_unref (iodine_watch); + + return 0; +} + +static gboolean +real_connect (NMVPNPlugin *plugin, + NMConnection *connection, + GError **error) +{ + NMSettingVPN *s_vpn; + gint iodine_fd = -1; + + s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, + NM_TYPE_SETTING_VPN)); + g_assert (s_vpn); + if (!nm_iodine_properties_validate (s_vpn, error)) + goto out; + + if (!nm_iodine_secrets_validate (s_vpn, error)) + goto out; + + iodine_fd = nm_iodine_start_iodine_binary (NM_IODINE_PLUGIN (plugin), + s_vpn, error); + if (!iodine_fd) + return TRUE; + + out: + return FALSE; +} + +static gboolean +real_need_secrets (NMVPNPlugin *plugin, + + NMConnection *connection, + char **setting_name, + GError **error) +{ + NMSettingVPN *s_vpn; + + g_return_val_if_fail (NM_IS_VPN_PLUGIN (plugin), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + + s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, + NM_TYPE_SETTING_VPN)); + if (!s_vpn) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_CONNECTION_INVALID, + "%s", + "Could not process the request because the VPN" + "connection settings were invalid."); + return FALSE; + } + + if (!nm_setting_vpn_get_secret (s_vpn, NM_IODINE_KEY_PASSWORD)) { + *setting_name = NM_SETTING_VPN_SETTING_NAME; + return TRUE; + } + + return FALSE; +} + +static gboolean +ensure_killed (gpointer data) +{ + int pid = GPOINTER_TO_INT (data); + + if (kill (pid, 0) == 0) + kill (pid, SIGKILL); + + return FALSE; +} + +static gboolean +real_disconnect (NMVPNPlugin *plugin, + GError **err) +{ + NMIODINEPluginPrivate *priv = NM_IODINE_PLUGIN_GET_PRIVATE (plugin); + + if (priv->pid) { + if (kill (priv->pid, SIGTERM) == 0) + g_timeout_add (2000, ensure_killed, GINT_TO_POINTER (priv->pid)); + else + kill (priv->pid, SIGKILL); + + g_message ("Terminated iodine daemon with PID %d.", priv->pid); + priv->pid = 0; + } + + return TRUE; +} + +static void +nm_iodine_plugin_init (NMIODINEPlugin *plugin) +{ + NMIODINEPluginPrivate *priv = NM_IODINE_PLUGIN_GET_PRIVATE (plugin); + + priv->ip4config = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + value_destroy); +} + +static void +nm_iodine_plugin_class_init (NMIODINEPluginClass *iodine_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (iodine_class); + NMVPNPluginClass *parent_class = NM_VPN_PLUGIN_CLASS (iodine_class); + + g_type_class_add_private (object_class, sizeof (NMIODINEPluginPrivate)); + + /* virtual methods */ + parent_class->connect = real_connect; + parent_class->need_secrets = real_need_secrets; + parent_class->disconnect = real_disconnect; +} + +NMIODINEPlugin * +nm_iodine_plugin_new (void) +{ + return (NMIODINEPlugin *) g_object_new (NM_TYPE_IODINE_PLUGIN, + NM_VPN_PLUGIN_DBUS_SERVICE_NAME, + NM_DBUS_SERVICE_IODINE, + NULL); +} + +static void +quit_mainloop (NMIODINEPlugin *plugin, gpointer user_data) +{ + g_main_loop_quit ((GMainLoop *) user_data); +} + +int main (int argc, char *argv[]) +{ + NMIODINEPlugin *plugin; + GMainLoop *main_loop; + + g_type_init (); + + plugin = nm_iodine_plugin_new (); + if (!plugin) + exit (EXIT_FAILURE); + + main_loop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (plugin, "quit", + G_CALLBACK (quit_mainloop), + main_loop); + + g_main_loop_run (main_loop); + + g_main_loop_unref (main_loop); + g_object_unref (plugin); + + exit (EXIT_SUCCESS); +} diff --git a/src/nm-iodine-service.h b/src/nm-iodine-service.h new file mode 100644 index 0000000..f82e282 --- /dev/null +++ b/src/nm-iodine-service.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright © 2012 Guido Günther + */ + +#ifndef NM_IODINE_PLUGIN_H +#define NM_IODINE_PLUGIN_H + +#include +#include + +#define NM_TYPE_IODINE_PLUGIN (nm_iodine_plugin_get_type ()) +#define NM_IODINE_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IODINE_PLUGIN, NMIODINEPlugin)) +#define NM_IODINE_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IODINE_PLUGIN, NMIODINEPluginClass)) +#define NM_IS_IODINE_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IODINE_PLUGIN)) +#define NM_IS_IODINE_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_IODINE_PLUGIN)) +#define NM_IODINE_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_IODINE_PLUGIN, NMIODINEPluginClass)) + +#define NM_DBUS_SERVICE_IODINE "org.freedesktop.NetworkManager.iodine" +#define NM_DBUS_INTERFACE_IODINE "org.freedesktop.NetworkManager.iodine" +#define NM_DBUS_PATH_IODINE "/org/freedesktop/NetworkManager/iodine" + +#define NM_IODINE_KEY_TOPDOMAIN "topdomain" +#define NM_IODINE_KEY_NAMESERVER "nameserver" +#define NM_IODINE_KEY_FRAGSIZE "fragsize" +#define NM_IODINE_KEY_PASSWORD "password" + +typedef struct { + NMVPNPlugin parent; +} NMIODINEPlugin; + +typedef struct { + NMVPNPluginClass parent; +} NMIODINEPluginClass; + +GType nm_iodine_plugin_get_type (void); + +NMIODINEPlugin *nm_iodine_plugin_new (void); + +#define NM_IODINE_USER "nm-iodine" + +#endif /* NM_IODINE_PLUGIN_H */ -- cgit v1.2.3