├── .classpath ├── .gitignore ├── .gitmodules ├── .project ├── AndroidManifest.xml ├── LICENSE ├── Makefile ├── README ├── TODO ├── assets ├── COPYRIGHT ├── racoon.head ├── racoon.sh ├── racoonctl.sh ├── setkey.head └── setkey.sh ├── build.xml ├── example-config ├── Makefile ├── cert │ ├── Makefile │ ├── manifest.mf │ ├── racoon.conf │ └── setkey.conf └── psk │ ├── Makefile │ ├── manifest.mf │ ├── racoon.conf │ └── setkey.conf ├── icon.svg ├── notification.svg ├── play ├── icon.png ├── screenshot_main-activity.png └── screenshot_peer-prefs.png ├── proguard-project.txt ├── proguard.cfg ├── project.properties ├── res ├── drawable-hdpi │ ├── icon.png │ └── notification.png ├── drawable-ldpi │ ├── icon.png │ └── notification.png ├── drawable-mdpi │ ├── icon.png │ └── notification.png ├── drawable │ └── peer_state.xml ├── layout │ ├── cert_password.xml │ └── peer_widget.xml ├── menu │ ├── options_menu.xml │ └── peer_menu.xml ├── values │ ├── arrays.xml │ └── strings.xml └── xml │ ├── global_preferences.xml │ ├── peer_preferences.xml │ └── preferences.xml ├── src └── org │ └── za │ └── hem │ └── ipsec_tools │ ├── IPsecToolsActivity.java │ ├── NativeCommand.java │ ├── Preferences.java │ ├── Utils.java │ ├── peer │ ├── OnPeerChangeListener.java │ ├── Peer.java │ ├── PeerID.java │ ├── PeerList.java │ ├── PeerPreferences.java │ └── StatePreference.java │ ├── racoon │ ├── Admin.java │ ├── ComSocket.java │ ├── Command.java │ ├── Event.java │ └── Ph1Dump.java │ └── service │ ├── CertManager.java │ ├── ConfigManager.java │ ├── ConnectivityReceiver.java │ ├── NativeService.java │ └── PolicyFile.java └── tools └── mkpolicy.sh /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | assets/armeabi 2 | assets/x86 3 | assets/example-*.zip 4 | bin/ 5 | example-config/*/policy.zip 6 | external/tarballs/ 7 | gen/ 8 | local.properties 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/openssl"] 2 | path = external/openssl 3 | url = https://github.com/mikma/openssl-android.git 4 | [submodule "external/ipsec-tools"] 5 | path = external/ipsec-tools 6 | url = https://github.com/mikma/ipsec-tools-android.git 7 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ipsec-android 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | 35 | FileExplorer_src 36 | 2 37 | _android_FileExplorer_4b3dc9fb/src 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012 by Mikael Magnusson. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 18 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 22 | USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | INKSCAPE_FLAGS := --export-background-opacity=0 2 | 3 | DIR_SSL := external/openssl/libs 4 | DIR_IPSEC := external/ipsec-tools/libs 5 | 6 | FILES_SSL := libcrypto.so \ 7 | libssl.so 8 | 9 | FILES_IPSEC := libipsec.so \ 10 | libracoonlib.so \ 11 | racoon \ 12 | racoonctl \ 13 | setkey 14 | 15 | all: build install examples 16 | 17 | examples: 18 | $(MAKE) -C example-config 19 | cp -a example-config/psk/policy.zip assets/example-psk.zip 20 | cp -a example-config/cert/policy.zip assets/example-cert.zip 21 | 22 | clean: clean-rec 23 | 24 | clean-rec: 25 | ndk-build -C external/openssl clean 26 | ndk-build -C external/ipsec-tools clean 27 | 28 | build: build-openssl build-ipsec-tools 29 | 30 | build-openssl: MAKE = ndk-build $(MAKEFLAGS) 31 | build-openssl: 32 | $(MAKE) -C external/openssl 33 | 34 | build-ipsec-tools: MAKE=ndk-build $(MAKEFLAGS) 35 | build-ipsec-tools: 36 | OPENSSL_INC=$(PWD)/external/openssl/include OPENSSL_LIB=$(PWD)/external/openssl/libs $(MAKE) -C external/ipsec-tools 37 | 38 | install: build 39 | test -e bin || mkdir bin 40 | test -e bin/ipsec-tools || mkdir bin/ipsec-tools 41 | test -e bin/armeabi || mkdir bin/armeabi 42 | test -e bin/x86 || mkdir bin/x86 43 | test -e assets/armeabi || mkdir assets/armeabi 44 | test -e assets/x86 || mkdir assets/x86 45 | for i in $(FILES_SSL); do cp $(DIR_SSL)/armeabi/$$i bin/armeabi; done 46 | for i in $(FILES_SSL); do cp $(DIR_SSL)/x86/$$i bin/x86; done 47 | for i in $(FILES_IPSEC); do cp $(DIR_IPSEC)/armeabi/$$i bin/armeabi; done 48 | for i in $(FILES_IPSEC); do cp $(DIR_IPSEC)/x86/$$i bin/x86; done 49 | mv bin/armeabi/racoon bin/armeabi/racoon.mikma 50 | mv bin/x86/racoon bin/x86/racoon.mikma 51 | zip -j assets/armeabi/ipsec-tools.zip bin/armeabi/* 52 | zip -j assets/x86/ipsec-tools.zip bin/x86/* 53 | 54 | play-icon: 55 | inkscape icon.svg --export-png=play/icon.png -w512 -h512 --export-background-opacity=0 56 | 57 | icons: 58 | inkscape icon.svg --export-png=res/drawable-ldpi/icon.png -d 67 $(INKSCAPE_FLAGS) 59 | inkscape icon.svg --export-png=res/drawable-mdpi/icon.png -d 90 $(INKSCAPE_FLAGS) 60 | inkscape icon.svg --export-png=res/drawable-hdpi/icon.png -d 135 $(INKSCAPE_FLAGS) 61 | 62 | inkscape notification.svg --export-png=res/drawable-ldpi/notification.png -d 67 63 | inkscape notification.svg --export-png=res/drawable-mdpi/notification.png -d 90 $(INKSCAPE_FLAGS) 64 | inkscape notification.svg --export-png=res/drawable-hdpi/notification.png -d 135 $(INKSCAPE_FLAGS) 65 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ipsec-android 2 | ============= 3 | 4 | IPsec VPN for Android which uses IPsec in the Linux kernel and racoon 5 | and setkey from ipsec-tools. Configured with racoon.conf and 6 | setkey.conf templates. 7 | 8 | Root is required to run this Android App 9 | 10 | Get the source 11 | -------------- 12 | $ git clone https://github.com/mikma/ipsec-android.git 13 | $ git submodule init 14 | $ git submodule update 15 | 16 | Get external library 17 | -------------------- 18 | Download "Android File Dialog" from 19 | https://code.google.com/p/android-file-dialog/ 20 | 21 | VPN policy 22 | ---------- 23 | A VPN policy is a JAR file containing racoon.conf and setkey.conf 24 | 25 | Variables usuable in racoon.conf and setkey.conf 26 | ------------------------------------------------ 27 | ${bindir} - Absolute path to bin directory of the App. 28 | ${extdir} - Absolute path to the external storage directory. 29 | ${remote_addr} - IP address of remote peer. 30 | ${local_addr} - Local IP address. 31 | ${uid} - UID of the java process. 32 | ${gid} - GID of the java process. 33 | ${name} - Name of the peer. 34 | ${action} - One of "none", "add", "delete" or "update". 35 | ${cert} - Certificate file path 36 | ${key} - Private key file path 37 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Flush setkey SAs for changed peers. 2 | 3 | * Support for "virtual" IP addresses. 4 | 5 | * Detect IP address changes 6 | 7 | * Include no connected peers in notification 8 | 9 | 10 | Policy handling 11 | --------------- 12 | 13 | Startup: Build all peer configs, Flush all SPDs and SADs 14 | 15 | Disconnect: Delete SPD for peer, Disconnect peer, Delete SAD for peer, Disable Peer 16 | -------------------------------------------------------------------------------- /assets/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012 by Mikael Magnusson. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 5 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 6 | * Neither the name of the Mikael Magnusson nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 7 | 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MIKAEL MAGNUSSSON OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 9 | 10 | The code is copyright 1995, 1996, 1997, 1998, and 1999 by the WIDE Project and licensed under the BSD license. 11 | 12 | Copyright (c) The Regents of the University of California. All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 15 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 16 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 17 | 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | -------------------------------------------------------------------------------- /assets/racoon.head: -------------------------------------------------------------------------------- 1 | privsep { 2 | # user ${uid}; 3 | # group ${gid}; 4 | # chroot "${bindir}/root"; 5 | } 6 | 7 | listen { 8 | adminsock "${bindir}/racoon.sock" "${uid}" "${gid}" 0600; 9 | } 10 | 11 | path pre_shared_key "${bindir}/psk.txt"; 12 | path certificate "${certdir}"; 13 | path scripts "${bindir}/scripts"; 14 | path pidfile "${bindir}/racoon.pid"; 15 | -------------------------------------------------------------------------------- /assets/racoon.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | dir=`dirname $0` 4 | su -c "LD_LIBRARY_PATH=$dir $dir/racoon.mikma $*" 5 | -------------------------------------------------------------------------------- /assets/racoonctl.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | dir=`dirname $0` 4 | LD_LIBRARY_PATH=$dir $dir/racoonctl "$@" 5 | -------------------------------------------------------------------------------- /assets/setkey.head: -------------------------------------------------------------------------------- 1 | # 2 | # Setkey 3 | # 4 | # HEAD 5 | # 6 | 7 | spdflush; 8 | -------------------------------------------------------------------------------- /assets/setkey.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | dir=`dirname $0` 4 | LD_LIBRARY_PATH=$dir $dir/setkey "$@" 5 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 40 | 41 | 42 | 43 | 47 | 48 | 60 | 61 | 62 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /example-config/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | $(MAKE) -C psk 3 | $(MAKE) -C cert 4 | -------------------------------------------------------------------------------- /example-config/cert/Makefile: -------------------------------------------------------------------------------- 1 | policy.zip: manifest.mf setkey.conf racoon.conf 2 | ../../tools/mkpolicy.sh 3 | -------------------------------------------------------------------------------- /example-config/cert/manifest.mf: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 0.98 3 | Name: Example cert policy 4 | -------------------------------------------------------------------------------- /example-config/cert/racoon.conf: -------------------------------------------------------------------------------- 1 | # 2 | # ${name} 3 | # 4 | 5 | remote ${remote_addr} { 6 | my_identifier asn1dn; 7 | certificate_type x509 "${cert}" "${key}"; 8 | exchange_mode main,aggressive; 9 | proposal { 10 | encryption_algorithm 3des; 11 | hash_algorithm sha1; 12 | authentication_method rsasig; 13 | dh_group modp1024; 14 | lifetime time 3660 sec; 15 | } 16 | passive off; 17 | generate_policy off; 18 | send_cr off; 19 | verify_cert off; 20 | nat_traversal on; 21 | } 22 | 23 | sainfo address ${local_addr}/32[any] any address ${remote_addr}/32[any] any { 24 | pfs_group modp1024; 25 | encryption_algorithm 3des; 26 | authentication_algorithm hmac_md5; 27 | compression_algorithm deflate; 28 | lifetime time 3660 sec; 29 | } 30 | -------------------------------------------------------------------------------- /example-config/cert/setkey.conf: -------------------------------------------------------------------------------- 1 | # 2 | # setkey.conf ${name} 3 | # 4 | 5 | spd${action} ${local_addr}/32[any] ${remote_addr}/32[any] any -P out ipsec 6 | esp/tunnel/${local_addr}-${remote_addr}/require; 7 | 8 | spd${action} ${remote_addr}/32[any] ${local_addr}/32[any] any -P in ipsec 9 | esp/tunnel/${remote_addr}-${local_addr}/require; 10 | 11 | deleteall ${local_addr} ${remote_addr} esp; 12 | deleteall ${remote_addr} ${local_addr} esp; 13 | -------------------------------------------------------------------------------- /example-config/psk/Makefile: -------------------------------------------------------------------------------- 1 | policy.zip: manifest.mf setkey.conf racoon.conf 2 | ../../tools/mkpolicy.sh 3 | -------------------------------------------------------------------------------- /example-config/psk/manifest.mf: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 0.98 3 | Name: Example psk policy 4 | -------------------------------------------------------------------------------- /example-config/psk/racoon.conf: -------------------------------------------------------------------------------- 1 | # 2 | # ${name} 3 | # 4 | 5 | remote ${remote_addr} { 6 | my_identifier user_fqdn "user@example.com"; 7 | exchange_mode aggressive,main,aggressive; 8 | proposal { 9 | encryption_algorithm 3des; 10 | hash_algorithm sha1; 11 | authentication_method pre_shared_key; 12 | dh_group modp1024; 13 | } 14 | passive off; 15 | generate_policy off; 16 | nat_traversal force; 17 | } 18 | 19 | sainfo address ${local_addr}/32[any] any address ${remote_addr}/32[any] any { 20 | pfs_group modp768; 21 | encryption_algorithm 3des; 22 | authentication_algorithm hmac_md5; 23 | compression_algorithm deflate; 24 | } 25 | -------------------------------------------------------------------------------- /example-config/psk/setkey.conf: -------------------------------------------------------------------------------- 1 | # 2 | # setkey.conf ${name} 3 | # 4 | 5 | spd${action} ${local_addr}/32[any] ${remote_addr}/32[any] any -P out ipsec 6 | esp/tunnel/${local_addr}-${remote_addr}/require; 7 | 8 | spd${action} ${remote_addr}/32[any] ${local_addr}/32[any] any -P in ipsec 9 | esp/tunnel/${remote_addr}-${local_addr}/require; 10 | 11 | deleteall ${local_addr} ${remote_addr} esp; 12 | deleteall ${remote_addr} ${local_addr} esp; 13 | -------------------------------------------------------------------------------- /notification.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 31 | 40 | 50 | 60 | 70 | 80 | 90 | 100 | 109 | 118 | 127 | 137 | 146 | 156 | 166 | 176 | 186 | 196 | 206 | 216 | 226 | 236 | 246 | 256 | 266 | 276 | 286 | 296 | 306 | 316 | 318 | 322 | 326 | 330 | 331 | 333 | 337 | 341 | 342 | 344 | 348 | 352 | 353 | 355 | 359 | 363 | 364 | 366 | 370 | 374 | 375 | 384 | 386 | 390 | 394 | 395 | 404 | 406 | 410 | 414 | 415 | 425 | 427 | 431 | 435 | 436 | 446 | 448 | 452 | 456 | 457 | 466 | 468 | 472 | 476 | 477 | 486 | 488 | 492 | 496 | 497 | 506 | 508 | 512 | 516 | 517 | 527 | 529 | 533 | 537 | 538 | 548 | 550 | 554 | 558 | 559 | 569 | 571 | 575 | 579 | 580 | 590 | 592 | 596 | 600 | 601 | 611 | 613 | 617 | 621 | 622 | 632 | 634 | 638 | 642 | 646 | 647 | 657 | 659 | 663 | 667 | 668 | 678 | 680 | 684 | 688 | 689 | 699 | 701 | 705 | 709 | 710 | 720 | 722 | 726 | 730 | 731 | 741 | 743 | 747 | 751 | 752 | 762 | 764 | 768 | 772 | 773 | 783 | 785 | 789 | 793 | 794 | 804 | 806 | 810 | 814 | 815 | 825 | 827 | 831 | 835 | 836 | 846 | 848 | 852 | 856 | 857 | 861 | 865 | 870 | 872 | 876 | 880 | 881 | 882 | 886 | 889 | 890 | 891 | 914 | 916 | 917 | 919 | image/svg+xml 920 | 922 | 923 | 924 | 925 | 926 | 931 | 940 | 953 | 960 | 967 | 968 | 969 | -------------------------------------------------------------------------------- /play/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/play/icon.png -------------------------------------------------------------------------------- /play/screenshot_main-activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/play/screenshot_main-activity.png -------------------------------------------------------------------------------- /play/screenshot_peer-prefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/play/screenshot_peer-prefs.png -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers enum * { 30 | public static **[] values(); 31 | public static ** valueOf(java.lang.String); 32 | } 33 | 34 | -keep class * implements android.os.Parcelable { 35 | public static final android.os.Parcelable$Creator *; 36 | } 37 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | android.library.reference.1=../android-file-dialog/FileExplorer 11 | # Project target. 12 | target=android-8 13 | -------------------------------------------------------------------------------- /res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-hdpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/res/drawable-hdpi/notification.png -------------------------------------------------------------------------------- /res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /res/drawable-ldpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/res/drawable-ldpi/notification.png -------------------------------------------------------------------------------- /res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-mdpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikma/ipsec-android/bb8acc29f88899b8ebca5cbfd5694944d385b5b0/res/drawable-mdpi/notification.png -------------------------------------------------------------------------------- /res/drawable/peer_state.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 13 | 16 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /res/layout/cert_password.xml: -------------------------------------------------------------------------------- 1 | 5 | 13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /res/layout/peer_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /res/menu/options_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 14 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /res/menu/peer_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | cert_key_store 5 | cert_files 6 | psk 7 | 8 | 9 | @string/cert_key_store 10 | @string/cert_files 11 | @string/psk 12 | 13 | 14 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IPsec Tools 6 | Preferences 7 | IPsec VPN running 8 | IPsec VPN 9 | Connect to peer 10 | Disconnect peer 11 | Edit peer 12 | Delete peer 13 | Start service 14 | Stop service 15 | Name 16 | Template file 17 | Remote address 18 | IPsec VPN peer up 19 | IPsec VPN peer down 20 | Delete peer 21 | The peer \"%1$s\" will be deleted. 22 | You must disconnect first. 23 | Please, fill in a name. 24 | Add IPsec VPN 25 | IPsec VPNs 26 | IPsec VPN Peer 27 | Unknown host name \"%1$s\". 28 | About 29 | About %s 30 | DNS Server 1 31 | DNS Server 2 32 | Pre shared key (PSK) 33 | Certificate file 34 | Private key file 35 | Save examples 36 | Peer State 37 | OK 38 | No certificates found 39 | Import certificate 40 | Password 41 | Certificate Alias 42 | Use Certificate file and Private key file settings 43 | Certificate and private key files (.pem+.key) 44 | Cerificate Key Store 45 | Authentication/Certificate type 46 | 47 | 48 | -------------------------------------------------------------------------------- /res/xml/global_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /res/xml/peer_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 11 | 14 | 17 | 20 | 25 | 26 | 29 | 33 | 36 | 39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/IPsecToolsActivity.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.Dialog; 6 | import android.app.Notification; 7 | import android.app.NotificationManager; 8 | import android.app.PendingIntent; 9 | import android.content.BroadcastReceiver; 10 | import android.content.ComponentName; 11 | import android.content.Context; 12 | import android.content.DialogInterface; 13 | import android.content.Intent; 14 | import android.content.IntentFilter; 15 | import android.content.ServiceConnection; 16 | import android.content.SharedPreferences; 17 | import android.content.res.Resources; 18 | import android.os.Bundle; 19 | import android.os.Environment; 20 | import android.os.Handler; 21 | import android.os.IBinder; 22 | import android.preference.Preference; 23 | import android.preference.Preference.OnPreferenceClickListener; 24 | import android.preference.PreferenceActivity; 25 | import android.preference.PreferenceGroup; 26 | import android.util.Base64; 27 | import android.util.Base64OutputStream; 28 | import android.util.Log; 29 | import android.view.ContextMenu; 30 | import android.view.LayoutInflater; 31 | import android.view.Menu; 32 | import android.view.MenuInflater; 33 | import android.view.MenuItem; 34 | import android.view.View; 35 | import android.widget.AdapterView; 36 | import android.widget.EditText; 37 | import android.widget.ListView; 38 | import android.widget.TextView; 39 | import android.widget.Toast; 40 | 41 | import java.io.BufferedInputStream; 42 | import java.io.BufferedOutputStream; 43 | import java.io.File; 44 | import java.io.FileFilter; 45 | import java.io.FileInputStream; 46 | import java.io.FileOutputStream; 47 | import java.io.FilenameFilter; 48 | import java.io.IOException; 49 | import java.io.InputStream; 50 | import java.io.InputStreamReader; 51 | import java.io.OutputStream; 52 | import java.io.OutputStreamWriter; 53 | import java.io.Reader; 54 | import java.net.InetSocketAddress; 55 | import java.security.Key; 56 | import java.security.KeyStore; 57 | import java.security.KeyStoreException; 58 | import java.security.NoSuchAlgorithmException; 59 | import java.security.UnrecoverableKeyException; 60 | import java.security.cert.Certificate; 61 | import java.security.cert.CertificateException; 62 | import java.util.Enumeration; 63 | import java.util.logging.Level; 64 | import java.util.logging.Logger; 65 | 66 | import org.w3c.dom.Text; 67 | import org.za.hem.ipsec_tools.peer.OnPeerChangeListener; 68 | import org.za.hem.ipsec_tools.peer.Peer; 69 | import org.za.hem.ipsec_tools.peer.PeerID; 70 | import org.za.hem.ipsec_tools.peer.PeerList; 71 | import org.za.hem.ipsec_tools.peer.PeerPreferences; 72 | import org.za.hem.ipsec_tools.peer.StatePreference; 73 | import org.za.hem.ipsec_tools.service.CertManager; 74 | import org.za.hem.ipsec_tools.service.ConfigManager; 75 | import org.za.hem.ipsec_tools.service.NativeService; 76 | 77 | import com.lamerman.FileDialog; 78 | 79 | /* 80 | * Register 81 | * android.telephony.TelephonyManager.DATA_CONNECTED 82 | * android.telephony.TelephonyManager.DATA_DISCONNECTED 83 | * 84 | * Context.getSystemService(Context.CONNECTIVITY_SERVICE). 85 | * CONNECTIVITY_ACTION 86 | */ 87 | 88 | /** 89 | * Main activity 90 | * 91 | * @author mikael 92 | * 93 | */ 94 | 95 | public class IPsecToolsActivity extends PreferenceActivity 96 | implements OnPreferenceClickListener, OnPeerChangeListener { 97 | final private String binaries[] = { 98 | NativeService.RACOON_EXEC_NAME, 99 | "racoonctl.sh", 100 | NativeService.SETKEY_EXEC_NAME, 101 | }; 102 | 103 | final private String examples[] = { 104 | "example-psk.zip", 105 | "example-cert.zip", 106 | }; 107 | 108 | static final int REQUEST_SAVE_EXAMPLES = 1; 109 | 110 | static final int DIALOG_CERT_PASSWORD = 1; 111 | 112 | private final boolean RACOON_STARTUP = false; 113 | 114 | private boolean mIsBound; /** True if bound. */ 115 | private NotificationManager mNM; 116 | private NativeService mBoundService; 117 | private NativeCommand mNative; 118 | private ConfigManager mCM; 119 | private static final String ADD_PREFERENCE = "addPref"; 120 | private static final String PEERS_PREFERENCE = "peersPref"; 121 | private static final String COUNT_PREFERENCE = "countPref"; 122 | private static final String COPYRIGHT_FILE = "COPYRIGHT"; 123 | private static final String ZIP_FILE = "ipsec-tools.zip"; 124 | 125 | private static final String P12_FILE_NAME = "p12_file_name"; 126 | private PeerList mPeers; 127 | private PeerID selectedID; 128 | private Peer selectedPeer; 129 | private Handler mGuiHandler; 130 | private CertManager mCertManager; 131 | 132 | @Override 133 | public void onCreate(Bundle savedInstanceState) { 134 | super.onCreate(savedInstanceState); 135 | 136 | selectedID = null; 137 | selectedPeer = null; 138 | 139 | mGuiHandler = new Handler(); 140 | 141 | mNative = new NativeCommand(this); 142 | try { 143 | mCertManager = new CertManager(this); 144 | } catch(Exception e) { 145 | // TODO 146 | throw new RuntimeException(e); 147 | } 148 | mCM = new ConfigManager(this, mNative, mCertManager); 149 | 150 | addPreferencesFromResource(R.xml.preferences); 151 | 152 | for (int i=0; i < binaries.length; i++) { 153 | mNative.putBinary(binaries[i]); 154 | } 155 | try { 156 | mNative.checkZipBinaries(ZIP_FILE); 157 | } catch (IOException e) { 158 | throw new RuntimeException(e); 159 | } 160 | 161 | Preference addPref = findPreference(ADD_PREFERENCE); 162 | addPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 163 | public boolean onPreferenceClick(Preference preference) { 164 | Intent settingsActivity = new Intent(getBaseContext(), 165 | PeerPreferences.class); 166 | PeerID id = mPeers.createPeer(IPsecToolsActivity.this); 167 | settingsActivity.putExtra(PeerPreferences.EXTRA_ID, id.intValue()); 168 | startActivity(settingsActivity); 169 | return true; 170 | } 171 | }); 172 | 173 | // For each id, update name 174 | PreferenceGroup peersPref = (PreferenceGroup)findPreference(PEERS_PREFERENCE); 175 | peersPref.removeAll(); 176 | SharedPreferences sharedPreferences = 177 | getPreferenceScreen().getSharedPreferences(); 178 | int count = sharedPreferences.getInt(COUNT_PREFERENCE,0); 179 | mPeers = new PeerList(mGuiHandler, getApplicationContext(), mCM, count); 180 | mPeers.setOnPeerChangeListener(this); 181 | 182 | Log.i("ipsec-tools", "Count: " + count); 183 | for (int i = 0; i < count; i++) { 184 | PeerID id = new PeerID(i); 185 | String key = id.toString(); 186 | Log.i("ipsec-tools", "Add pref: " + key); 187 | if (sharedPreferences.getBoolean(key, true)) { 188 | StatePreference peerPref = new StatePreference(this); 189 | peerPref.setKey(key); 190 | peerPref.setOnPreferenceClickListener(this); 191 | peerPref.setWidgetLayoutResource(R.layout.peer_widget); 192 | Log.i("ipsec-tools", "Add peerPref: " + key); 193 | peersPref.addPreference(peerPref); 194 | mPeers.add(new Peer(mGuiHandler, this, id, peerPref)); 195 | } else { 196 | mPeers.add(null); 197 | } 198 | id = id.next(); 199 | } 200 | 201 | startService(); 202 | 203 | } 204 | 205 | protected void startService() { 206 | if (mBoundService != null) 207 | return; 208 | if (!NativeService.isServiceRunning(this)) { 209 | startService(new Intent(IPsecToolsActivity.this, 210 | NativeService.class)); 211 | 212 | } 213 | 214 | doBindService(); 215 | } 216 | 217 | protected void stopService() { 218 | if (mBoundService == null) 219 | return; 220 | 221 | doUnbindService(); 222 | stopService(new Intent(IPsecToolsActivity.this, 223 | NativeService.class)); 224 | } 225 | 226 | /* 227 | protected void updatePeers() { 228 | if (mBoundService == null) 229 | return; 230 | 231 | Log.i("ipsec-tools", "updatePeers"); 232 | mBoundService.vpnConnect(addr); 233 | } 234 | */ 235 | 236 | public void onDeletePeer(Peer peer) { 237 | PeerID id = peer.getPeerID(); 238 | PreferenceGroup peersPref = (PreferenceGroup)findPreference(PEERS_PREFERENCE); 239 | Preference peerPref = peer.getPreference(); 240 | Log.i("ipsec-tools", "Remove peerPref: " + mPeers.size() + " " + id + " " + peerPref); 241 | peersPref.removePreference(peerPref); 242 | 243 | // Hide peer 244 | SharedPreferences.Editor editor; 245 | SharedPreferences sharedPreferences = 246 | getPreferenceScreen().getSharedPreferences(); 247 | editor = sharedPreferences.edit(); 248 | editor.putBoolean(id.toString(), false); 249 | editor.commit(); 250 | } 251 | 252 | public void onCreatePeer(Peer peer) { 253 | String key = peer.getPeerID().toString(); 254 | int id = peer.getPeerID().intValue(); 255 | 256 | PreferenceGroup peersPref = (PreferenceGroup)findPreference(PEERS_PREFERENCE); 257 | SharedPreferences sharedPreferences = 258 | getPreferenceScreen().getSharedPreferences(); 259 | // Start transaction 260 | SharedPreferences.Editor editor = sharedPreferences.edit(); 261 | 262 | StatePreference peerPref = new StatePreference(this); 263 | peerPref.setKey(key); 264 | peerPref.setOnPreferenceClickListener(this); 265 | peerPref.setWidgetLayoutResource(R.layout.peer_widget); 266 | peersPref.addPreference(peerPref); 267 | peer.setPreference(peerPref); 268 | 269 | if (id >= sharedPreferences.getInt(COUNT_PREFERENCE, 0)) 270 | editor.putInt(COUNT_PREFERENCE, id + 1); 271 | editor.putBoolean(key, true); 272 | editor.commit(); 273 | } 274 | 275 | protected void onStart() 276 | { 277 | Log.i("ipsec-tools", "onStart:" + this); 278 | super.onStart(); 279 | } 280 | 281 | protected void onResume() 282 | { 283 | Log.i("ipsec-tools", "onResume:" + this); 284 | super.onResume(); 285 | mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); 286 | 287 | IntentFilter filter = new IntentFilter(); 288 | filter.addAction(NativeService.ACTION_DESTROYED); 289 | filter.addAction(NativeService.ACTION_PHASE1_UP); 290 | filter.addAction(NativeService.ACTION_PHASE1_DOWN); 291 | filter.addAction(NativeService.ACTION_SERVICE_READY); 292 | registerReceiver(mReceiver, filter); 293 | registerForContextMenu(getListView()); 294 | 295 | SharedPreferences sharedPreferences = 296 | getPreferenceScreen().getSharedPreferences(); 297 | 298 | for (int i=0; i < mPeers.size(); i++) { 299 | PeerID id = new PeerID(i); 300 | 301 | if (sharedPreferences.getBoolean(id.toString(), true) 302 | && mPeers.get(i) != null ) { 303 | Peer peer = mPeers.get(i); 304 | peer.onPreferenceActivityResume(); 305 | } 306 | } 307 | } 308 | 309 | protected void onPause() 310 | { 311 | Log.i("ipsec-tools", "onPause:" + this); 312 | super.onPause(); 313 | unregisterReceiver(mReceiver); 314 | unregisterForContextMenu(getListView()); 315 | mNM = null; 316 | 317 | SharedPreferences sharedPreferences = 318 | getPreferenceScreen().getSharedPreferences(); 319 | 320 | for (int i=0; i < mPeers.size(); i++) { 321 | PeerID id = new PeerID(i); 322 | 323 | if (sharedPreferences.getBoolean(id.toString(), true) 324 | && mPeers.get(i) != null ) { 325 | Peer peer = mPeers.get(i); 326 | peer.onPreferenceActivityPause(); 327 | } 328 | } 329 | } 330 | 331 | @Override 332 | protected void onStop() 333 | { 334 | Log.i("ipsec-tools", "onStop:" + this); 335 | super.onStop(); 336 | } 337 | 338 | @Override 339 | protected void onDestroy() 340 | { 341 | Log.i("ipsec-tools", "onDestroy:" + this); 342 | if (mIsBound) 343 | doUnbindService(); 344 | super.onDestroy(); 345 | } 346 | 347 | @Override 348 | public void onCreateContextMenu(ContextMenu menu, View v, 349 | ContextMenu.ContextMenuInfo menuInfo) { 350 | AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; 351 | ListView list = (ListView)v; 352 | Preference pref = (Preference)list.getItemAtPosition(info.position); 353 | 354 | try { 355 | selectedID = PeerID.fromString(pref.getKey()); 356 | 357 | if (selectedID.isValid()) { 358 | selectedPeer = mPeers.get(selectedID); 359 | boolean isRacoonRunning = mBoundService.isRacoonRunning(); 360 | Log.i("ipsec-tools", "onCreateContextMenu " + info.id + " " + info.position + " " + pref + " " + selectedPeer); 361 | 362 | MenuInflater inflater = getMenuInflater(); 363 | inflater.inflate(R.menu.peer_menu, menu); 364 | menu.setHeaderTitle(selectedPeer.getName()); 365 | menu.findItem(R.id.connect_peer).setEnabled(isRacoonRunning && selectedPeer.canConnect()); 366 | menu.findItem(R.id.disconnect_peer).setEnabled(isRacoonRunning && selectedPeer.canDisconnect()); 367 | menu.findItem(R.id.edit_peer).setEnabled(selectedPeer.canEdit()); 368 | menu.findItem(R.id.delete_peer).setEnabled(selectedPeer.canEdit()); 369 | } else { 370 | selectedPeer = null; 371 | Log.i("ipsec-tools", "onCreateContextMenu item not found"); 372 | } 373 | } catch (PeerID.KeyFormatException e) { 374 | Logger.getLogger(IPsecToolsActivity.class.getName()).log( 375 | Level.SEVERE, "onCreateContextMenu " + e); 376 | } 377 | } 378 | 379 | @Override 380 | public boolean onContextItemSelected(MenuItem item) { 381 | //AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); 382 | 383 | Log.i("ipsec-tools", "onContextItemSelected " + item); 384 | 385 | switch (item.getItemId()) { 386 | case R.id.connect_peer: 387 | mPeers.enableAndConnect(selectedID); 388 | return true; 389 | case R.id.disconnect_peer: 390 | mPeers.disconnectAndDisable(selectedID); 391 | return true; 392 | case R.id.edit_peer: 393 | mPeers.updateConfig(selectedID, ConfigManager.Action.DELETE); 394 | mPeers.edit(this, selectedID); 395 | 396 | return true; 397 | case R.id.delete_peer: 398 | mPeers.deletePeer(selectedID, this); 399 | return true; 400 | default: 401 | return super.onContextItemSelected(item); 402 | } 403 | } 404 | 405 | @Override 406 | public void onContextMenuClosed(Menu menu) { 407 | selectedID = null; 408 | } 409 | 410 | @Override 411 | public boolean onPreferenceClick(Preference arg0) { 412 | try { 413 | PeerID id = PeerID.fromString(arg0.getKey()); 414 | Log.i("ipsec-tools", "click " + id); 415 | mPeers.toggle(id); 416 | return true; 417 | } catch (PeerID.KeyFormatException e) { 418 | return false; 419 | } 420 | } 421 | 422 | @Override 423 | public boolean onCreateOptionsMenu(Menu menu) { 424 | MenuInflater inflater = getMenuInflater(); 425 | inflater.inflate(R.menu.options_menu, menu); 426 | return true; 427 | } 428 | 429 | @Override 430 | public boolean onPrepareOptionsMenu (Menu menu) { 431 | boolean isRacoonRunning = mBoundService != null && mBoundService.isRacoonRunning(); 432 | menu.findItem(R.id.start_service).setVisible(!isRacoonRunning); 433 | menu.findItem(R.id.stop_service).setVisible(isRacoonRunning); 434 | return true; 435 | } 436 | 437 | @Override 438 | public boolean onOptionsItemSelected(MenuItem item) { 439 | // Handle item selection 440 | switch (item.getItemId()) { 441 | case R.id.start_service: 442 | mBoundService.startRacoon(); 443 | return true; 444 | case R.id.stop_service: 445 | mBoundService.stopRacoon(); 446 | return true; 447 | case R.id.save_examples: 448 | return handleSaveExamples(); 449 | case R.id.import_cert: 450 | return handleImportCert(); 451 | // case R.id.preferences: 452 | // Intent settingsActivity = new Intent(getBaseContext(), 453 | // Preferences.class); 454 | // startActivity(settingsActivity); 455 | // return true; 456 | case R.id.show_about: 457 | StringBuffer str = new StringBuffer(); 458 | try { 459 | Reader input; 460 | input = new InputStreamReader(getAssets().open(COPYRIGHT_FILE)); 461 | int read; 462 | char[] buffer = new char[4096]; 463 | 464 | while ((read = input.read(buffer)) > 0) { 465 | str.append(buffer, 0, read); 466 | } 467 | input.close(); 468 | } catch (IOException e) { 469 | // TODO Auto-generated catch block 470 | str.append(e.getStackTrace()); 471 | } 472 | 473 | Resources res = getResources(); 474 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 475 | builder.setCancelable(true) 476 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 477 | public void onClick(DialogInterface dialog, int id) { 478 | dialog.cancel(); 479 | } 480 | }) 481 | .setTitle(res.getString(R.string.about_title, 482 | res.getString(R.string.app_name))) 483 | .setMessage(str); 484 | 485 | AlertDialog alert = builder.create(); 486 | alert.show(); 487 | return true; 488 | default: 489 | return super.onOptionsItemSelected(item); 490 | } 491 | } 492 | 493 | private void showNotification(Peer peer, int id) { 494 | CharSequence text = getString(id) + " " + peer.getName(); 495 | Notification notification = new Notification(R.drawable.notification, 496 | text, 497 | System.currentTimeMillis()); 498 | notification.flags |= Notification.FLAG_AUTO_CANCEL; 499 | 500 | Intent intent = new Intent(this, IPsecToolsActivity.class); 501 | //intent.setAction(ACTION_NOTIFICATION); 502 | 503 | PendingIntent contentIntent = PendingIntent.getActivity(this, 0, 504 | intent, 0); 505 | 506 | notification.setLatestEventInfo(this, getText(R.string.native_service_label), 507 | text, contentIntent); 508 | 509 | // Send the notification. 510 | mNM.notify(peer.getName(), R.string.notify_peer_up, notification); 511 | } 512 | 513 | 514 | private BroadcastReceiver mReceiver = new BroadcastReceiver() { 515 | public void onReceive(Context context, Intent intent) { 516 | boolean isSynthetic = intent.getBooleanExtra("synthetic", false); 517 | Log.i("ipsec-tools", "broadcast received: " + intent); 518 | Log.i("ipsec-tools", "Intent: isSynthetic:" + isSynthetic); 519 | String action = intent.getAction(); 520 | 521 | if (action.equals(NativeService.ACTION_SERVICE_READY)) { 522 | if (mBoundService != null) 523 | mPeers.dumpIsakmpSA(); 524 | return; 525 | } else if (action.equals(NativeService.ACTION_DESTROYED)) { 526 | mPeers.onDestroy(); 527 | return; 528 | } 529 | 530 | InetSocketAddress remote_address = (InetSocketAddress)intent.getSerializableExtra("remote_addr"); 531 | Log.i("ipsec-tools", "onReceive remote_addr:" + remote_address); 532 | if (remote_address == null) 533 | throw new RuntimeException("No remote_addr in broadcastintent"); 534 | Peer peer = null; 535 | 536 | int notifyType = -1; 537 | 538 | if (action.equals(NativeService.ACTION_PHASE1_UP)) { 539 | peer = mPeers.findForRemote(remote_address, false); 540 | if (peer == null) { 541 | Log.i("ipsec-tools", 542 | "Unknown peer up " + remote_address); 543 | return; 544 | } 545 | 546 | notifyType = R.string.notify_peer_up; 547 | peer.onPhase1Up(); 548 | if (!isSynthetic) { 549 | String dns1 = peer.getDns1(); 550 | if (dns1 != null && dns1.length() > 0) { 551 | mBoundService.storeDns(dns1, peer.getDns2()); 552 | } 553 | } 554 | } else if (action.equals(NativeService.ACTION_PHASE1_DOWN)) { 555 | peer = mPeers.findForRemote(remote_address, true); 556 | if (peer == null) { 557 | Log.i("ipsec-tools", 558 | "Unknown peer down " + remote_address); 559 | return; 560 | } 561 | 562 | notifyType = R.string.notify_peer_down; 563 | peer.onPhase1Down(); 564 | if (!isSynthetic) { 565 | mBoundService.restoreDns(); 566 | } 567 | } 568 | 569 | if (peer == null) { 570 | } 571 | 572 | if (!isSynthetic && notifyType >= 0 && !hasWindowFocus()) { 573 | //if (!isSynthetic && notifyType >= 0) { 574 | showNotification(peer, notifyType); 575 | } 576 | } 577 | }; 578 | 579 | private void onServiceUnbound() { 580 | mBoundService = null; 581 | output("Disconnected"); 582 | mPeers.clearService(); 583 | // Toast.makeText(Binding.this, R.string.native_service_disconnected, 584 | // Toast.LENGTH_SHORT).show(); 585 | } 586 | 587 | private ServiceConnection mConnection = new ServiceConnection() { 588 | public void onServiceConnected(ComponentName className, IBinder service) { 589 | // This is called when the connection with the service has been 590 | // established, giving us the service object we can use to 591 | // interact with the service. Because we have bound to a explicit 592 | // service that we know is running in our own process, we can 593 | // cast its IBinder to a concrete class and directly access it. 594 | mBoundService = ((NativeService.NativeBinder)service).getService(); 595 | output("Connected"); 596 | Log.i("ipsec-tools", "connected " + mBoundService); 597 | mPeers.setService(mBoundService); 598 | 599 | if (mBoundService.isRacoonRunning()) 600 | mPeers.dumpIsakmpSA(); 601 | else { 602 | mPeers.disableAll(); 603 | try { 604 | mCM.build(mPeers, true); 605 | } catch (IOException e) { 606 | throw new RuntimeException(e); 607 | } 608 | 609 | if ( RACOON_STARTUP ) 610 | mBoundService.startRacoon(); 611 | } 612 | 613 | // Tell the user about this for our demo. 614 | // Toast.makeText(Binding.this, R.string.native_service_connected, 615 | // Toast.LENGTH_SHORT).show(); 616 | } 617 | 618 | public void onServiceDisconnected(ComponentName className) { 619 | // This is called when the connection with the service has been 620 | // unexpectedly disconnected -- that is, its process crashed. 621 | // Because it is running in our same process, we should never 622 | // see this happen. 623 | onServiceUnbound(); 624 | } 625 | }; 626 | 627 | void doBindService() { 628 | // Establish a connection with the service. We use an explicit 629 | // class name because we want a specific service implementation that 630 | // we know will be running in our own process (and thus won't be 631 | // supporting component replacement by other applications). 632 | // FIXME handle start errors 633 | mIsBound = bindService(new Intent(IPsecToolsActivity.this, 634 | NativeService.class), mConnection, 0); 635 | Log.i("ipsec-tools", "doBindService " + mIsBound); 636 | } 637 | 638 | void doUnbindService() { 639 | if (mIsBound) { 640 | Log.i("ipsec-tools", "doUnBindService"); 641 | // Detach our existing connection. 642 | unbindService(mConnection); 643 | onServiceUnbound(); 644 | mIsBound = false; 645 | } else 646 | Log.i("ipsec-tools", "not bound"); 647 | } 648 | 649 | @Override 650 | public Dialog onCreateDialog(int id, Bundle savedInstanceState) { 651 | switch(id) { 652 | case DIALOG_CERT_PASSWORD: 653 | return onCreateCertPasswordDialog(savedInstanceState); 654 | default: 655 | return super.onCreateDialog(id, savedInstanceState); 656 | } 657 | } 658 | 659 | private Dialog onCreateCertPasswordDialog(Bundle savedInstanceState) { 660 | Activity activity = this; 661 | AlertDialog.Builder builder = new AlertDialog.Builder(activity); 662 | LayoutInflater inflater = activity.getLayoutInflater(); 663 | 664 | // Inflate and set the layout for the dialog 665 | // Pass null as the parent view because its going in the dialog layout 666 | View view = inflater.inflate(R.layout.cert_password, null); 667 | final EditText password = (EditText)view.findViewById(R.id.password); 668 | 669 | final File p12FileName = new File(savedInstanceState.getString(P12_FILE_NAME)); 670 | TextView title = (TextView)view.findViewById(R.id.title); 671 | title.setText(p12FileName.getName()); 672 | 673 | builder.setView(view) 674 | // Add action buttons 675 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 676 | @Override 677 | public void onClick(DialogInterface dialog, int id) { 678 | // Import certificate 679 | try { 680 | InputStream is = new BufferedInputStream(new FileInputStream(p12FileName)); 681 | mCertManager.load(is, 682 | password.getText().toString().toCharArray()); 683 | // TODO remove cert? 684 | is.close(); 685 | } catch (CertificateException e) { 686 | throw new RuntimeException(e); 687 | } catch (NoSuchAlgorithmException e) { 688 | throw new RuntimeException(e); 689 | } catch (KeyStoreException e) { 690 | throw new RuntimeException(e); 691 | } catch (IOException e) { 692 | throw new RuntimeException(e); 693 | } 694 | } 695 | }) 696 | .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 697 | public void onClick(DialogInterface dialog, int id) { 698 | } 699 | }); 700 | return builder.create(); 701 | } 702 | 703 | private boolean handleImportCert() { 704 | final File storagePath = Environment.getExternalStorageDirectory(); 705 | File[] p12Files = storagePath.listFiles(new FilenameFilter(){ 706 | @Override 707 | public boolean accept(File dir, String fileName) { 708 | if (!fileName.endsWith(CertManager.P12_POSTFIX)) 709 | return false; 710 | File file = new File(dir, fileName); 711 | return file.canRead(); 712 | }}); 713 | 714 | if (p12Files.length == 0) { 715 | int duration = Toast.LENGTH_SHORT; 716 | Toast toast = Toast.makeText(this, R.string.no_certs, duration); 717 | toast.show(); 718 | return false; 719 | } 720 | 721 | for (int i = 0; i < p12Files.length; i++) { 722 | Bundle bundle = new Bundle(); 723 | bundle.putString(P12_FILE_NAME, p12Files[i].toString()); 724 | 725 | showDialog(DIALOG_CERT_PASSWORD, bundle); 726 | } 727 | 728 | return true; 729 | } 730 | 731 | private boolean handleSaveExamples() { 732 | final String startPath = Environment.getExternalStorageDirectory().getAbsolutePath(); 733 | Intent intent = 734 | new Intent(this, FileDialog.class); 735 | intent.putExtra(FileDialog.START_PATH, startPath); 736 | intent.putExtra(FileDialog.ALLOW_DIRECTORY, true); 737 | this.startActivityForResult(intent, REQUEST_SAVE_EXAMPLES); 738 | return true; 739 | } 740 | 741 | private void saveExamples(final Intent data) { 742 | String filePath = data.getStringExtra(FileDialog.RESULT_PATH); 743 | File dir = new File(filePath); 744 | for (int i=0; i < examples.length; i++) { 745 | mNative.putFile(dir, examples[i]); 746 | } 747 | } 748 | 749 | @Override 750 | protected void onActivityResult (int requestCode, int resultCode, 751 | final Intent data) { 752 | if (resultCode == Activity.RESULT_OK) { 753 | switch (requestCode) { 754 | case REQUEST_SAVE_EXAMPLES: 755 | saveExamples(data); 756 | break; 757 | } 758 | } else if (resultCode == Activity.RESULT_CANCELED) { 759 | Logger.getLogger(IPsecToolsActivity.class.getName()).log( 760 | Level.WARNING, "file not selected"); 761 | } 762 | } 763 | 764 | private void output(final String str) { 765 | int duration = Toast.LENGTH_SHORT; 766 | 767 | Toast toast = Toast.makeText(this, str, duration); 768 | toast.show(); 769 | } 770 | } 771 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/NativeCommand.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.DataOutputStream; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.io.OutputStream; 11 | import java.util.zip.ZipEntry; 12 | import java.util.zip.ZipInputStream; 13 | 14 | import android.content.Context; 15 | import android.os.Build; 16 | import android.os.Environment; 17 | import android.util.Log; 18 | 19 | import org.za.hem.ipsec_tools.service.NativeService; 20 | 21 | public class NativeCommand { 22 | private File mBinDir; 23 | private File mSystemBin; 24 | private Context mContext; 25 | private File mBinGetProp; 26 | private File mBinSetProp; 27 | 28 | public NativeCommand(Context context) { 29 | mContext = context; 30 | mBinDir = context.getDir("bin", Context.MODE_PRIVATE); 31 | mSystemBin = new File(Environment.getRootDirectory(), "bin"); 32 | mBinGetProp = new File(mSystemBin, "getprop"); 33 | mBinSetProp = new File(mSystemBin, "setprop"); 34 | } 35 | 36 | 37 | /** 38 | * Copy file from assets into specified directory. 39 | */ 40 | public void putFile(File dir, String fileName, int mode) { 41 | try { 42 | File file = new File(dir, fileName); 43 | InputStream input = mContext.getAssets().open(fileName); 44 | int read; 45 | byte[] buffer = new byte[4096]; 46 | OutputStream output = new FileOutputStream(file); 47 | 48 | while ((read = input.read(buffer)) > 0) { 49 | output.write(buffer, 0, read); 50 | } 51 | input.close(); 52 | output.close(); 53 | if (mode >= 0) { 54 | chmod(file, mode); 55 | } 56 | } catch (IOException e) { 57 | throw new RuntimeException(e); 58 | } 59 | } 60 | 61 | public void putFile(File dir, String fileName) { 62 | putFile(dir, fileName, -1); 63 | } 64 | 65 | /** 66 | * Copy binary file from assets into bin directory. 67 | */ 68 | public void putBinary(String fileName) { 69 | putFile(mBinDir, fileName, 711); 70 | } 71 | 72 | /** 73 | * Copy binary file from ZIP assets into bin directory. 74 | */ 75 | public void putZipBinary(ZipInputStream zis, ZipEntry ze) throws IOException { 76 | String fileName = ze.getName(); 77 | File file = new File(mBinDir, fileName); 78 | 79 | if (file.lastModified() >= ze.getTime()) { 80 | Log.i("ipsec-tools", "File fresh:" + file); 81 | return; 82 | } 83 | 84 | int read; 85 | byte[] buffer = new byte[4096]; 86 | OutputStream output = new FileOutputStream(file); 87 | 88 | try { 89 | while ((read = zis.read(buffer)) > 0) { 90 | output.write(buffer, 0, read); 91 | } 92 | 93 | chmod(file, 711); 94 | } finally { 95 | output.close(); 96 | } 97 | } 98 | 99 | /** 100 | * Copy binary files from ZIP assets into bin directory. 101 | */ 102 | public void putZipBinaries(String zipName) throws IOException { 103 | ZipInputStream zis = 104 | new ZipInputStream(mContext.getAssets().open(zipName)); 105 | 106 | try { 107 | ZipEntry ze; 108 | while ((ze = zis.getNextEntry()) != null) { 109 | putZipBinary(zis, ze); 110 | } 111 | } finally { 112 | zis.close(); 113 | } 114 | } 115 | 116 | 117 | public boolean isModifiedZipBinary(ZipInputStream zis, ZipEntry ze) throws IOException { 118 | String fileName = ze.getName(); 119 | File file = new File(mBinDir, fileName); 120 | 121 | if (file.lastModified() >= ze.getTime()) { 122 | return false; 123 | } else { 124 | return true; 125 | } 126 | } 127 | 128 | public boolean areModifiedZipBinaries(String zipName) throws IOException { 129 | ZipInputStream zis = 130 | new ZipInputStream(mContext.getAssets().open(zipName)); 131 | 132 | try { 133 | ZipEntry ze; 134 | while ((ze = zis.getNextEntry()) != null) { 135 | if (isModifiedZipBinary(zis, ze)) return true; 136 | } 137 | } finally { 138 | zis.close(); 139 | } 140 | 141 | return false; 142 | } 143 | 144 | public boolean checkZipBinaries(String zipName) throws IOException { 145 | String[] list; 146 | String path; 147 | 148 | path = Build.CPU_ABI; 149 | list = mContext.getAssets().list(path); 150 | if (list == null || list.length == 0) { 151 | path = Build.CPU_ABI2; 152 | list = mContext.getAssets().list(path); 153 | if (list == null || list.length == 0) 154 | return false; 155 | } 156 | 157 | String pathName = path + "/" + zipName; 158 | 159 | if (areModifiedZipBinaries(pathName)) { 160 | // Kill any old racoon instances before trying to write 161 | NativeCommand.system("killall " 162 | + NativeService.RACOON_BIN_NAME); 163 | // TODO add a few seconds delay to allow racoon to exit 164 | putZipBinaries(pathName); 165 | } 166 | 167 | return true; 168 | } 169 | 170 | /** 171 | * Set file mode 172 | * @param file File to modify 173 | * @param mode New file mode 174 | */ 175 | private void chmod(File file, int mode) { 176 | system(new File(mSystemBin, "chmod").getAbsolutePath() + " " + mode + " " + file.getAbsolutePath()); 177 | } 178 | 179 | 180 | /** 181 | * Set file owner 182 | * @param file File to modify 183 | * @param user New file user 184 | * @param group New file group (or null) 185 | */ 186 | public void chown(File file, String user, String group) { 187 | String param; 188 | 189 | if (group == null) 190 | param = user; 191 | else 192 | param = user + "." + group; 193 | 194 | system(new File(mSystemBin, "chown").getAbsolutePath() + " " + param + " " + file.getAbsolutePath()); 195 | } 196 | 197 | 198 | /** 199 | * Run system command wait for and return result 200 | * @param cmd System command to run 201 | * @return stdout 202 | */ 203 | public static String system(String cmd) { 204 | try { 205 | // Executes the command. 206 | Process process = Runtime.getRuntime().exec("su"); 207 | 208 | DataOutputStream os = new DataOutputStream(process.getOutputStream()); 209 | Log.i("ipsec-tools", "su command:" + cmd); 210 | os.writeBytes(cmd+"\n"); 211 | 212 | os.writeBytes("exit\n"); 213 | os.flush(); 214 | 215 | // Reads stdout. 216 | // NOTE: You can write to stdin of the command using 217 | // process.getOutputStream(). 218 | BufferedReader reader = new BufferedReader( 219 | new InputStreamReader(process.getInputStream()), 8192); 220 | int read; 221 | char[] buffer = new char[4096]; 222 | StringBuffer output = new StringBuffer(); 223 | while ((read = reader.read(buffer)) > 0) { 224 | output.append(buffer, 0, read); 225 | } 226 | reader.close(); 227 | 228 | BufferedReader errReader = new BufferedReader( 229 | new InputStreamReader(process.getErrorStream()), 8192); 230 | int errRead; 231 | char[] errBuffer = new char[4096]; 232 | StringBuffer error = new StringBuffer(); 233 | while ((errRead = errReader.read(errBuffer)) > 0) { 234 | error.append(errBuffer, 0, errRead); 235 | } 236 | errReader.close(); 237 | 238 | // Waits for the command to finish. 239 | process.waitFor(); 240 | 241 | if (error.length() > 0) 242 | Log.i("ipsec-tools", "System cmd error:" + error); 243 | 244 | Log.i("ipsec-tools", "System cmd output:" + output); 245 | return output.toString(); 246 | } catch (IOException e) { 247 | throw new RuntimeException(e); 248 | } catch (InterruptedException e) { 249 | throw new RuntimeException(e); 250 | } 251 | } 252 | 253 | public static String system(String prog, String[] parameters) 254 | { 255 | StringBuffer buf = new StringBuffer(4096); 256 | 257 | buf.append(prog); 258 | for ( String str : parameters ) { 259 | buf.append(' '); 260 | buf.append(str); 261 | } 262 | 263 | return system(buf.toString()); 264 | } 265 | 266 | public String ls(String[] parameters) { 267 | return system(new File(mSystemBin, "ls").getAbsolutePath(), parameters); 268 | } 269 | 270 | public String getprop(String name) { 271 | String value = system(mBinGetProp.getAbsolutePath() + " " + name); 272 | return value.trim(); 273 | } 274 | 275 | public void setprop(String name, String value) { 276 | system(mBinSetProp.getAbsolutePath() + " " + name + " \"" + value + "\""); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/Preferences.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools; 2 | 3 | import android.content.SharedPreferences; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceActivity; 6 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 7 | 8 | public class Preferences extends PreferenceActivity implements OnSharedPreferenceChangeListener { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | addPreferencesFromResource(R.xml.global_preferences); 14 | } 15 | 16 | @Override 17 | protected void onResume() { 18 | super.onResume(); 19 | } 20 | 21 | @Override 22 | protected void onPause() { 23 | super.onPause(); 24 | } 25 | 26 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 27 | // Let's do something a preference value changes 28 | } 29 | } -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/Utils.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools; 2 | 3 | import java.lang.StringBuffer; 4 | import java.net.DatagramSocket; 5 | import java.net.InetAddress; 6 | import java.net.SocketException; 7 | 8 | public class Utils { 9 | public static final int ISAKMP_PORT = 500; 10 | 11 | public static String join(T[] array, String delimiter) { 12 | if (array.length == 0) 13 | return ""; 14 | int i = 0; 15 | StringBuffer buffer = new StringBuffer(array[i++].toString()); 16 | for (; i < array.length; i++) { 17 | buffer.append(delimiter).append(array[i++]); 18 | } 19 | return buffer.toString(); 20 | } 21 | 22 | public static InetAddress getLocalAddress(InetAddress dstAddr) { 23 | if (dstAddr == null) 24 | return null; 25 | try { 26 | DatagramSocket sock = new DatagramSocket(); 27 | sock.connect(dstAddr, ISAKMP_PORT); 28 | InetAddress srcAddr = sock.getLocalAddress(); 29 | sock.close(); 30 | return srcAddr; 31 | } catch (SocketException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/peer/OnPeerChangeListener.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.peer; 2 | 3 | 4 | public interface OnPeerChangeListener { 5 | public abstract void onDeletePeer(Peer peer); 6 | public abstract void onCreatePeer(Peer peer); 7 | } 8 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/peer/Peer.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.peer; 2 | 3 | import java.io.File; 4 | import java.net.InetAddress; 5 | import java.net.UnknownHostException; 6 | 7 | import org.za.hem.ipsec_tools.R; 8 | import org.za.hem.ipsec_tools.Utils; 9 | 10 | import android.app.Activity; 11 | import android.content.Context; 12 | import android.content.SharedPreferences; 13 | import android.content.SharedPreferences.Editor; 14 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 15 | import android.os.Handler; 16 | import android.preference.Preference; 17 | import android.util.Log; 18 | 19 | /** 20 | * Peer controller object 21 | * 22 | * @author mikael 23 | * 24 | */ 25 | public class Peer implements OnSharedPreferenceChangeListener { 26 | /** Peer is down but enabled. */ 27 | public static final int STATUS_DISCONNECTED = 0; 28 | /** Peer is up. */ 29 | public static final int STATUS_CONNECTED = 1; 30 | /** Peer is up, and disconnection has been initiated. */ 31 | public static final int STATUS_DISCONNECTING = 2; 32 | /** Peer is down, and connection has been initiated. */ 33 | public static final int STATUS_CONNECTING = 3; 34 | /** Peer is down, and disabled. */ 35 | public static final int STATUS_DISABLED = 4; 36 | /** Peer is faulty. Unused */ 37 | public static final int STATUS_BUSY = 5; 38 | public static final int STATUS_NUM = 6; 39 | 40 | public static final int[] STATUS_SUMMARY = { 41 | R.string.connect_peer, 42 | R.string.disconnect_peer, 43 | R.string.connect_peer, 44 | R.string.disconnect_peer, 45 | R.string.connect_peer, 46 | -1, 47 | }; 48 | 49 | /* 50 | * Icons 51 | * 52 | * 0 - presence_invisible grey dot 53 | * 1 - presence_online green dot 54 | * 2 - presence_away blue clock 55 | * 3 - presence_away blue clock 56 | * 4 - presence_offline grey cross 57 | * 5 - presence_busy red dash 58 | */ 59 | 60 | public static final int[] STATUS_ICON = { 61 | 62 | }; 63 | 64 | private PeerID mID; 65 | private StatePreference mPref; 66 | private SharedPreferences mShared; 67 | private Handler mGuiHandler; 68 | private int mStatus; 69 | 70 | public Peer(Handler guiHandler, Context context, PeerID id, StatePreference pref) { 71 | mGuiHandler = guiHandler; 72 | mID = id; 73 | mPref = pref; 74 | mShared = context.getSharedPreferences( 75 | PeerPreferences.getSharedPreferencesName(context, id), 76 | Activity.MODE_PRIVATE); 77 | mStatus = -1; 78 | setStatus(isEnabled() ? STATUS_DISCONNECTED : STATUS_DISABLED); 79 | } 80 | 81 | public void clear() { 82 | Editor editor = mShared.edit(); 83 | editor.clear(); 84 | editor.commit(); 85 | } 86 | 87 | public PeerID getPeerID() { 88 | return mID; 89 | } 90 | 91 | public boolean isEnabled() { 92 | return mShared.getBoolean(PeerPreferences.ENABLED_PREFERENCE, true); 93 | } 94 | 95 | public void setEnabled(boolean enabled) { 96 | Editor editor = mShared.edit(); 97 | editor.putBoolean(PeerPreferences.ENABLED_PREFERENCE, enabled); 98 | editor.commit(); 99 | } 100 | 101 | public boolean canConnect() { 102 | return mStatus == STATUS_DISCONNECTED || mStatus == STATUS_DISABLED; 103 | } 104 | 105 | public boolean canDisconnect() { 106 | return mStatus == STATUS_CONNECTED || mStatus == STATUS_DISCONNECTING || mStatus == STATUS_CONNECTING; 107 | } 108 | 109 | public boolean isConnected() { 110 | return mStatus == STATUS_CONNECTED 111 | || mStatus == STATUS_DISCONNECTING; 112 | } 113 | 114 | public boolean isDisconnected() { 115 | return mStatus == STATUS_DISCONNECTED 116 | || mStatus == STATUS_CONNECTING 117 | || mStatus == STATUS_DISABLED; 118 | } 119 | 120 | public boolean canEdit() { 121 | return mStatus == STATUS_DISCONNECTED 122 | || mStatus == STATUS_DISABLED; 123 | } 124 | 125 | public Preference getPreference() { 126 | return mPref; 127 | } 128 | 129 | public void setPreference(StatePreference pref) { 130 | mPref = pref; 131 | } 132 | 133 | public String getName() { 134 | return mShared.getString(PeerPreferences.NAME_PREFERENCE, ""); 135 | } 136 | 137 | public String getCertAlias() { 138 | return mShared.getString(PeerPreferences.CERT_ALIAS_PREFERENCE, ""); 139 | } 140 | 141 | public String getCert() { 142 | return mShared.getString(PeerPreferences.CERT_PREFERENCE, ""); 143 | } 144 | 145 | public String getKey() { 146 | return mShared.getString(PeerPreferences.KEY_PREFERENCE, ""); 147 | } 148 | 149 | public InetAddress getRemoteAddr() { 150 | String host = mShared.getString(PeerPreferences.REMOTE_ADDR_PREFERENCE, null); 151 | String ip = mShared.getString(PeerPreferences.REMOTE_ADDR_IP_PREFERENCE, null); 152 | try { 153 | InetAddress ipAddr = InetAddress.getByName(ip); 154 | InetAddress addr = InetAddress.getByAddress(host, ipAddr.getAddress()); 155 | Log.i("ipsec-tools", "getRemoteAddr " + addr); 156 | return addr; 157 | } catch (UnknownHostException e) { 158 | return null; 159 | } 160 | } 161 | 162 | public InetAddress getLocalAddr() { 163 | return Utils.getLocalAddress(getRemoteAddr()); 164 | } 165 | 166 | public File getTemplateFile() { 167 | String addr = mShared.getString(PeerPreferences.TEMPLATE_PREFERENCE, null); 168 | Log.i("ipsec-tools", "getTemplateFile " + addr); 169 | if (addr == null) 170 | return null; 171 | return new File(addr); 172 | } 173 | 174 | public String getDns1() { 175 | return mShared.getString(PeerPreferences.DNS1_PREFERENCE, null); 176 | } 177 | 178 | public String getDns2() { 179 | return mShared.getString(PeerPreferences.DNS2_PREFERENCE, null); 180 | } 181 | 182 | public String getPsk() { 183 | return mShared.getString(PeerPreferences.PSK_PREFERENCE, null); 184 | } 185 | 186 | public int getStatus() { 187 | return mStatus; 188 | } 189 | 190 | public void setStatus(final int status) { 191 | mGuiHandler.post(new Runnable() { 192 | public void run() { 193 | if (mStatus != status && status < STATUS_NUM) { 194 | mStatus = status; 195 | if (mPref != null) { 196 | mPref.setIconLevel(mStatus); 197 | mPref.setSummary(STATUS_SUMMARY[mStatus]); 198 | } 199 | Log.i("ipsec-tools", "setStatus " + getName() + " "+ mStatus); 200 | } 201 | } 202 | }); 203 | } 204 | 205 | /** Called when Phase 1 goes up */ 206 | public void onPhase1Up() { 207 | setStatus(STATUS_CONNECTED); 208 | } 209 | 210 | /** Called when Phase 1 goes down */ 211 | public void onPhase1Down() { 212 | setStatus(isEnabled() ? STATUS_DISCONNECTED : STATUS_DISABLED); 213 | } 214 | 215 | /** Called when initiating disconnect */ 216 | public void onConnect() { 217 | setStatus(STATUS_CONNECTING); 218 | } 219 | 220 | /** Called when initiating disconnect */ 221 | public void onDisconnect() { 222 | setStatus(STATUS_DISCONNECTING); 223 | } 224 | 225 | public void onPreferenceActivityResume() { 226 | mShared.registerOnSharedPreferenceChangeListener(this); 227 | updatePreferenceName(); 228 | } 229 | 230 | public void onPreferenceActivityPause() { 231 | mShared.unregisterOnSharedPreferenceChangeListener(this); 232 | } 233 | 234 | @Override 235 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 236 | //Called when a shared preference is changed, added, or removed. 237 | Log.i("ipsec-tools", "peer pref " + key + " changed"); 238 | if (key.equals(PeerPreferences.NAME_PREFERENCE)) { 239 | updatePreferenceName(); 240 | } 241 | } 242 | 243 | protected void updatePreferenceName() { 244 | mGuiHandler.post(new Runnable() { 245 | public void run() { 246 | getPreference().setTitle(getName()); 247 | } 248 | }); 249 | } 250 | 251 | public String toString() { 252 | return "Peer[" + getName() + " " + getRemoteAddr() + "]"; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/peer/PeerID.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.peer; 2 | 3 | import java.lang.Exception; 4 | 5 | import android.util.Log; 6 | 7 | /** 8 | * Peer Identity Name 9 | * 10 | * @author mikael 11 | * 12 | */ 13 | public class PeerID implements Comparable { 14 | public static final String PREFIX = "peer_"; 15 | int id; 16 | String key; 17 | 18 | public static class KeyFormatException extends Exception { 19 | /** 20 | * 21 | */ 22 | private static final long serialVersionUID = 7506071001428989998L; 23 | } 24 | 25 | public PeerID(int theId) { 26 | id = theId; 27 | key = PREFIX + id; 28 | } 29 | 30 | public PeerID() { 31 | id = -1; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return id; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (o instanceof PeerID) { 42 | return ((PeerID)o).id == id; 43 | } else { 44 | return false; 45 | } 46 | } 47 | 48 | @Override 49 | public int compareTo(PeerID arg0) { 50 | Log.i("ipsec-tools", "Compare: " + arg0); 51 | 52 | return id - arg0.id; 53 | } 54 | 55 | public PeerID next() { 56 | return new PeerID(id + 1); 57 | } 58 | 59 | public boolean isValid() { 60 | return id >= 0; 61 | } 62 | 63 | public String toString() { 64 | return key; 65 | } 66 | 67 | public int intValue() { 68 | return id; 69 | } 70 | 71 | public static boolean isKey(String key) { 72 | return key.startsWith(PREFIX); 73 | } 74 | 75 | public static PeerID fromString(String key) throws KeyFormatException { 76 | if (!isKey(key)) 77 | throw new KeyFormatException(); 78 | 79 | String idStr = key.substring(PREFIX.length()); 80 | try { 81 | int id = Integer.parseInt(idStr); 82 | return new PeerID(id); 83 | } catch(NumberFormatException e) { 84 | throw new KeyFormatException(); 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/peer/PeerList.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.peer; 2 | 3 | import java.io.File; 4 | import java.io.FileWriter; 5 | import java.io.IOException; 6 | import java.io.Writer; 7 | import java.net.InetAddress; 8 | import java.net.InetSocketAddress; 9 | import java.util.ArrayList; 10 | import java.util.Iterator; 11 | 12 | import org.za.hem.ipsec_tools.NativeCommand; 13 | import org.za.hem.ipsec_tools.R; 14 | import org.za.hem.ipsec_tools.service.ConfigManager; 15 | import org.za.hem.ipsec_tools.service.ConfigManager.Action; 16 | import org.za.hem.ipsec_tools.service.NativeService; 17 | 18 | import android.app.AlertDialog; 19 | import android.content.Context; 20 | import android.content.DialogInterface; 21 | import android.content.Intent; 22 | import android.os.Bundle; 23 | import android.os.Handler; 24 | import android.os.HandlerThread; 25 | import android.os.Message; 26 | import android.util.Log; 27 | 28 | /** 29 | * Peer List Controller 30 | * 31 | * @author mikael 32 | * 33 | */ 34 | public class PeerList extends ArrayList { 35 | 36 | public static final int HANDLER_VPN_CONNECT = 1; 37 | public static final int HANDLER_VPN_DISCONNECT = 2; 38 | public static final int HANDLER_DUMP_ISAKMP_SA = 3; 39 | public static final int HANDLER_ENABLE_AND_CONNECT = 4; 40 | public static final int HANDLER_DISCONNECT_AND_DISABLE = 5; 41 | public static final int HANDLER_UPDATE_CONFIG = 6; 42 | 43 | public static final String HANDLER_KEY_ACTION = "action"; 44 | 45 | /** 46 | * Serial for Serializable 47 | */ 48 | private static final long serialVersionUID = -3584858864706289236L; 49 | 50 | private ArrayList mPeers; 51 | private OnPeerChangeListener mListener; 52 | private HandlerThread mHandlerThread; 53 | private Handler mHandler; 54 | private NativeService mBoundService; 55 | private Context mContext; 56 | private ConfigManager mConfigManager; 57 | private Handler mGuiHandler; 58 | 59 | public PeerList(Handler guiHandler, Context context, ConfigManager configManager, int capacity) { 60 | super(capacity); 61 | mGuiHandler = guiHandler; 62 | mContext = context; 63 | mConfigManager = configManager; 64 | mBoundService = null; 65 | mPeers = this; 66 | mHandler = null; 67 | mHandlerThread = null; 68 | } 69 | 70 | private void startHandler() { 71 | mHandlerThread = new HandlerThread("PeerList"); 72 | mHandlerThread.start(); 73 | mHandler = new Handler(mHandlerThread.getLooper()) { 74 | public void handleMessage(Message msg) { 75 | String addr; 76 | switch (msg.what) { 77 | case HANDLER_VPN_CONNECT: 78 | addr = (String)msg.obj; 79 | mBoundService.vpnConnect(addr); 80 | break; 81 | case HANDLER_VPN_DISCONNECT: 82 | addr = (String)msg.obj; 83 | mBoundService.vpnDisconnect(addr); 84 | break; 85 | case HANDLER_DUMP_ISAKMP_SA: 86 | mBoundService.dumpIsakmpSA(); 87 | break; 88 | case HANDLER_ENABLE_AND_CONNECT: 89 | doEnableAndConnect((PeerID)msg.obj); 90 | break; 91 | case HANDLER_DISCONNECT_AND_DISABLE: 92 | doDisconnectAndDisable((PeerID)msg.obj); 93 | break; 94 | case HANDLER_UPDATE_CONFIG: 95 | try { 96 | doUpdateConfig((PeerID)msg.obj, 97 | (Action)msg.getData().getSerializable(HANDLER_KEY_ACTION)); 98 | } catch (IOException e) { 99 | e.printStackTrace(); 100 | } 101 | } 102 | } 103 | }; 104 | } 105 | 106 | private void stopHandler() { 107 | if (mHandler == null) 108 | return; 109 | mHandler.getLooper().quit(); 110 | try { 111 | mHandlerThread.join(1000); 112 | } catch (InterruptedException e) { 113 | } 114 | mHandlerThread = null; 115 | mHandler = null; 116 | } 117 | 118 | public void setService(NativeService service) { 119 | if (service == null) 120 | throw new NullPointerException(); 121 | 122 | mBoundService = service; 123 | if (mHandlerThread == null) 124 | startHandler(); 125 | } 126 | 127 | public void clearService() { 128 | stopHandler(); 129 | mBoundService = null; 130 | } 131 | 132 | public Peer get(PeerID id) { 133 | int i = id.intValue(); 134 | return mPeers.get(i); 135 | } 136 | 137 | protected void set(PeerID id, Peer peer) { 138 | mPeers.set(id.intValue(), peer); 139 | } 140 | 141 | public void setOnPeerChangeListener(OnPeerChangeListener listener) { 142 | mListener = listener; 143 | } 144 | 145 | public Peer findForRemote(final InetSocketAddress sa, 146 | boolean isUp) { 147 | InetAddress addr = sa.getAddress(); 148 | Iterator iter = iterator(); 149 | 150 | while (iter.hasNext()) { 151 | Peer peer = iter.next(); 152 | if (peer == null) 153 | continue; 154 | Log.i("ipsec-tools", "findForRemote " + peer + " " + peer.getStatus() + " " + peer.isEnabled()); 155 | if (isUp) { 156 | if (!peer.isConnected()) continue; 157 | } else { 158 | if (!peer.isEnabled()) continue; 159 | } 160 | InetAddress peerAddr = peer.getRemoteAddr(); 161 | if (peerAddr != null && peerAddr.equals(addr)) 162 | return peer; 163 | } 164 | 165 | return null; 166 | } 167 | 168 | public PeerID createPeer(Context context) 169 | { 170 | int empty = mPeers.indexOf(null); 171 | if (empty == -1) { 172 | empty = mPeers.size(); 173 | Log.i("ipsec-tools", "Size " + mPeers.size()); 174 | } 175 | mPeers.ensureCapacity(empty+1); 176 | 177 | Log.i("ipsec-tools", "New id " + empty); 178 | PeerID newId = new PeerID(empty); 179 | Peer peer = new Peer(mGuiHandler, context, newId, null); 180 | if (empty >= mPeers.size()) 181 | mPeers.add(peer); 182 | else 183 | mPeers.set(empty, peer); 184 | 185 | if (mListener != null) 186 | mListener.onCreatePeer(peer); 187 | 188 | return newId; 189 | } 190 | 191 | public void deletePeer(final PeerID id, Context context) { 192 | final Peer peer = get(id); 193 | 194 | Log.i("ipsec-tools", "deletePeer"); 195 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 196 | builder.setTitle(R.string.title_delete_peer); 197 | String msgFormat = context.getResources().getString(R.string.msg_delete_peer); 198 | String msg = String.format(msgFormat, peer.getName()); 199 | builder.setMessage(msg); 200 | builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 201 | public void onClick(DialogInterface arg0, int arg1) { 202 | // do something when the OK button is clicked 203 | if (mListener != null) 204 | mListener.onDeletePeer(peer); 205 | 206 | peer.clear(); 207 | set(id, null); 208 | } 209 | }); 210 | builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 211 | public void onClick(DialogInterface arg0, int arg1) { 212 | // do something when the Cancel button is clicked 213 | } 214 | }); 215 | AlertDialog alert = builder.create(); 216 | alert.show(); 217 | Log.i("ipsec-tools", "After show"); 218 | } 219 | 220 | 221 | 222 | public void edit(Context context, final PeerID id) { 223 | Peer peer = mPeers.get(id.intValue()); 224 | if (!peer.canEdit()) { 225 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 226 | builder.setIcon(android.R.drawable.ic_dialog_alert); 227 | builder.setTitle(peer.getName()); 228 | builder.setMessage(R.string.msg_disconnect_first); 229 | builder.setPositiveButton(android.R.string.ok, null); 230 | AlertDialog alert = builder.create(); 231 | alert.show(); 232 | return; 233 | } 234 | 235 | Intent settingsActivity = new Intent(context, 236 | PeerPreferences.class); 237 | settingsActivity.putExtra(PeerPreferences.EXTRA_ID, id.intValue()); 238 | context.startActivity(settingsActivity); 239 | } 240 | 241 | public void dumpIsakmpSA() { 242 | Message msg = mHandler.obtainMessage(HANDLER_DUMP_ISAKMP_SA); 243 | msg.sendToTarget(); 244 | } 245 | 246 | public void connect(final PeerID id) { 247 | if (mBoundService == null) 248 | return; 249 | 250 | Peer peer = get(id); 251 | InetAddress addr = peer.getRemoteAddr(); 252 | if (addr == null) 253 | throw new NullPointerException(); 254 | Log.i("ipsec-tools", "connectPeer " + addr); 255 | peer.onConnect(); 256 | 257 | Message msg = mHandler.obtainMessage(HANDLER_VPN_CONNECT); 258 | msg.obj = addr.getHostAddress(); 259 | msg.sendToTarget(); 260 | } 261 | 262 | // TODO add return value 263 | public void enableAndConnect(PeerID id) 264 | { 265 | Message msg = mHandler.obtainMessage(HANDLER_ENABLE_AND_CONNECT); 266 | msg.obj = id; 267 | msg.sendToTarget(); 268 | } 269 | 270 | 271 | protected void doEnableAndConnect(PeerID id) 272 | { 273 | try { 274 | Peer peer = get(id); 275 | if (!peer.isEnabled()) { 276 | Log.i("ipsec-tools", "Enable " + id); 277 | peer.setEnabled(true); 278 | doUpdateConfig(id, ConfigManager.Action.ADD); 279 | } 280 | else 281 | Log.i("ipsec-tools", "Already enabled " + id); 282 | connect(id); 283 | } catch (IOException e) { 284 | // TODO display error 285 | } 286 | } 287 | 288 | 289 | public void disconnect(Peer peer) { 290 | if (mBoundService == null) { 291 | Log.i("ipsec-tools", "No service"); 292 | return; 293 | } 294 | 295 | InetAddress addr = peer.getRemoteAddr(); 296 | if (addr == null) 297 | throw new NullPointerException(); 298 | Log.i("ipsec-tools", "disconnectPeer " + addr); 299 | peer.onDisconnect(); 300 | Message msg = mHandler.obtainMessage(HANDLER_VPN_DISCONNECT); 301 | msg.obj = addr.getHostAddress(); 302 | msg.sendToTarget(); 303 | } 304 | 305 | public void disconnectAndDisable(final PeerID id) { 306 | Message msg = mHandler.obtainMessage(HANDLER_DISCONNECT_AND_DISABLE); 307 | msg.obj = id; 308 | msg.sendToTarget(); 309 | } 310 | 311 | private void doDisconnectAndDisable(final PeerID id) { 312 | Peer peer = get(id); 313 | deleteSPD(peer); 314 | disconnect(peer); 315 | Log.i("ipsec-tools", "Disable " + id); 316 | peer.setEnabled(false); 317 | try { 318 | // TODO Remove at disconnect 319 | doUpdateConfig(id, ConfigManager.Action.DELETE); 320 | } catch (IOException e) { 321 | // TODO handle error 322 | } 323 | } 324 | 325 | public void toggle(final PeerID id) { 326 | Peer peer = get(id); 327 | Boolean isRacoonRunning = mBoundService.isRacoonRunning(); 328 | Log.i("ipsec-tools", "togglePeer " + id + " " + peer); 329 | if (isRacoonRunning && peer.canDisconnect()) { 330 | disconnectAndDisable(id); 331 | } else if (isRacoonRunning && peer.canConnect()) { 332 | enableAndConnect(id); 333 | } 334 | } 335 | 336 | /** 337 | * Racoon destroyed. Notify all peers on phase1 down. 338 | */ 339 | public void onDestroy() { 340 | Iterator iter = iterator(); 341 | 342 | while (iter.hasNext()) { 343 | Peer peer = iter.next(); 344 | if (peer == null) 345 | continue; 346 | peer.onPhase1Down(); 347 | } 348 | } 349 | 350 | // TODO add return value 351 | public void updateConfig(PeerID id, Action action) 352 | { 353 | Bundle data = new Bundle(); 354 | Message msg = mHandler.obtainMessage(HANDLER_UPDATE_CONFIG); 355 | data.putSerializable(HANDLER_KEY_ACTION, action); 356 | msg.obj = id; 357 | msg.setData(data); 358 | msg.sendToTarget(); 359 | } 360 | 361 | 362 | public void doUpdateConfig(PeerID id, Action action) throws IOException 363 | { 364 | mConfigManager.build(this, false); 365 | Peer peer = get(id); 366 | Log.i("ipsec-tools", "updateConfig peer " + id); 367 | 368 | File binDir = mContext.getDir("bin", Context.MODE_PRIVATE); 369 | FileWriter setKeyOs = new FileWriter(new File(binDir, ConfigManager.SETKEY_CONFIG)); 370 | Writer pskOs = null; 371 | if (peer != null) { 372 | mConfigManager.buildPeerConfig(action, peer, setKeyOs, pskOs); 373 | } 374 | setKeyOs.close(); 375 | if (mBoundService != null) 376 | mBoundService.runSetKey(); 377 | // if (peer.isEnabled()) { 378 | // peer.setStatus(Peer.STATUS_DISCONNECTED); 379 | // } else { 380 | // peer.setStatus(Peer.STATUS_DISABLED); 381 | // } 382 | if (mBoundService != null) 383 | mBoundService.reloadConf(); 384 | } 385 | 386 | public void addSPD(Peer peer) { 387 | try { 388 | mConfigManager.buildAddSPD(peer); 389 | } catch (IOException e) { 390 | // TODO handle error 391 | } 392 | File binDir = mContext.getDir("bin", Context.MODE_PRIVATE); 393 | NativeCommand.system(new File(binDir, NativeService.SETKEY_EXEC_NAME).getAbsolutePath() + 394 | " -FP"); 395 | } 396 | 397 | public void deleteSPD(Peer peer) { 398 | try { 399 | mConfigManager.buildDeleteSPD(peer); 400 | } catch (IOException e) { 401 | // TODO handle error 402 | } 403 | File binDir = mContext.getDir("bin", Context.MODE_PRIVATE); 404 | NativeCommand.system(new File(binDir, NativeService.SETKEY_EXEC_NAME).getAbsolutePath() + 405 | " -FP"); 406 | } 407 | 408 | public void disableAll() { 409 | Iterator i = mPeers.iterator(); 410 | 411 | while (i.hasNext()) { 412 | Peer peer = i.next(); 413 | if ( peer != null ) 414 | peer.setEnabled(false); 415 | } 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/peer/PeerPreferences.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.peer; 2 | 3 | import java.io.File; 4 | import java.net.InetAddress; 5 | import java.net.UnknownHostException; 6 | import java.util.Iterator; 7 | import java.util.logging.Level; 8 | import java.util.logging.Logger; 9 | import java.util.Map; 10 | 11 | import org.za.hem.ipsec_tools.R; 12 | import org.za.hem.ipsec_tools.service.CertManager; 13 | 14 | import android.app.Activity; 15 | import android.app.AlertDialog; 16 | import android.content.Context; 17 | import android.content.Intent; 18 | import android.content.SharedPreferences; 19 | import android.content.SharedPreferences.Editor; 20 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 21 | import android.os.Bundle; 22 | import android.os.Environment; 23 | import android.os.Handler; 24 | import android.os.HandlerThread; 25 | import android.preference.CheckBoxPreference; 26 | import android.preference.EditTextPreference; 27 | import android.preference.ListPreference; 28 | import android.preference.Preference; 29 | import android.preference.PreferenceActivity; 30 | import android.preference.PreferenceCategory; 31 | import android.preference.PreferenceGroup; 32 | import android.preference.PreferenceManager; 33 | import android.preference.Preference.OnPreferenceChangeListener; 34 | import android.preference.Preference.OnPreferenceClickListener; 35 | import android.util.Log; 36 | 37 | import com.lamerman.FileDialog; 38 | 39 | // Order "public protected private static final transient volatile" 40 | 41 | /** 42 | * IPsec peer preference activity 43 | * Managed by Peer object. 44 | * 45 | * @author mikael 46 | */ 47 | public class PeerPreferences extends PreferenceActivity implements OnSharedPreferenceChangeListener { 48 | public static final String EXTRA_ID = "org.za.hem.ipsec_tools.ID"; 49 | 50 | static final String TEMPLATE_PREFERENCE = "templatePref"; 51 | static final String CERT_ALIAS_PREFERENCE = "certAliasPref"; 52 | static final String CERT_PREFERENCE = "certPref"; 53 | static final String KEY_PREFERENCE = "keyPref"; 54 | static final String NAME_PREFERENCE = "namePref"; 55 | static final String ENABLED_PREFERENCE = "enabledPref"; 56 | static final String REMOTE_ADDR_PREFERENCE = "remoteAddrPref"; 57 | static final String REMOTE_ADDR_IP_PREFERENCE = "remoteAddrIpPref"; 58 | static final String DNS1_PREFERENCE = "dns1Pref"; 59 | static final String DNS2_PREFERENCE = "dns2Pref"; 60 | static final String PSK_PREFERENCE = "pskPref"; 61 | static final String AUTH_TYPE_PREFERENCE = "authTypePref"; 62 | static final String AUTH_TYPE_CAT_PREFERENCE = "authTypeCatPref"; 63 | 64 | // Auth type values (sync with @array/auth_type_values) 65 | static final String AUTH_TYPE_PSK = "psk"; 66 | static final String AUTH_TYPE_CERT_FILES = "cert_files"; 67 | static final String AUTH_TYPE_CERT_KEY_STORE = "cert_key_store"; 68 | 69 | static final int REQUEST_TEMPLATE = 1; 70 | static final int REQUEST_CERT = 2; 71 | static final int REQUEST_KEY = 3; 72 | 73 | private PeerID mID; 74 | private Handler mHandler; 75 | private HandlerThread mHandlerThread; 76 | private PreferenceCategory mAuthTypeCatPref; 77 | private Preference mPskPref; 78 | private ListPreference mCertAliasPref; 79 | private Preference mCertFilePref; 80 | private Preference mKeyFilePref; 81 | 82 | @Override 83 | protected void onCreate(Bundle savedInstanceState) { 84 | super.onCreate(savedInstanceState); 85 | 86 | mHandlerThread = new HandlerThread("PeerPreferences"); 87 | mHandlerThread.start(); 88 | mHandler = new Handler(mHandlerThread.getLooper()) {}; 89 | 90 | mID = new PeerID(getIntent().getIntExtra(EXTRA_ID, -1)); 91 | 92 | PreferenceManager manager = getPreferenceManager(); 93 | manager.setSharedPreferencesName(getSharedPreferencesName(this, mID)); 94 | addPreferencesFromResource(R.xml.peer_preferences); 95 | 96 | setFilePathListener(TEMPLATE_PREFERENCE, REQUEST_TEMPLATE); 97 | setFilePathListener(CERT_PREFERENCE, REQUEST_CERT); 98 | setFilePathListener(KEY_PREFERENCE, REQUEST_KEY); 99 | 100 | mAuthTypeCatPref = (PreferenceCategory)findPreference(AUTH_TYPE_CAT_PREFERENCE); 101 | mCertAliasPref = (ListPreference)findPreference(CERT_ALIAS_PREFERENCE); 102 | mCertFilePref = findPreference(CERT_PREFERENCE); 103 | mKeyFilePref = findPreference(KEY_PREFERENCE); 104 | mPskPref = findPreference(PSK_PREFERENCE); 105 | 106 | Preference remoteAddrPref = findPreference(REMOTE_ADDR_PREFERENCE); 107 | remoteAddrPref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 108 | public boolean onPreferenceChange (Preference preference, Object newValue) { 109 | return onRemoteAddrChange(preference, newValue); 110 | } 111 | }); 112 | 113 | try { 114 | Context context = getApplicationContext(); 115 | CertManager certs = new CertManager(context); 116 | CharSequence[] aliases = certs.getAliases(); 117 | /* 118 | int len = aliases.length; 119 | CharSequence[] aliasesEntries = new CharSequence[len + 1]; 120 | CharSequence[] aliasesValues = new CharSequence[len + 1]; 121 | System.arraycopy(aliases, 0, aliasesEntries, 0, len); 122 | System.arraycopy(aliases, 0, aliasesValues, 0, len); 123 | aliasesEntries[len] = context.getResources().getString(R.string.use_certificate_file_and_key_file); 124 | aliasesValues[len] = ""; 125 | mCertAliasPref.setEntries(aliasesEntries); 126 | mCertAliasPref.setEntryValues(aliasesValues); 127 | */ 128 | mCertAliasPref.setEntries(aliases); 129 | mCertAliasPref.setEntryValues(aliases); 130 | } catch (Exception e) { 131 | throw new RuntimeException(e); 132 | } 133 | 134 | ListPreference authTypePref = (ListPreference)findPreference(AUTH_TYPE_PREFERENCE); 135 | authTypePref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 136 | @Override 137 | public boolean onPreferenceChange(Preference preference, Object newValue) { 138 | updateAuthType((String)newValue); 139 | return true; 140 | } 141 | }); 142 | 143 | SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); 144 | String authType = sharedPreferences.getString(AUTH_TYPE_PREFERENCE, null); 145 | if (authType == null) { 146 | if (sharedPreferences.getString(CERT_PREFERENCE, null) != null && 147 | sharedPreferences.getString(KEY_PREFERENCE, null) != null) { 148 | authType = AUTH_TYPE_CERT_FILES; 149 | } else if (sharedPreferences.getString(PSK_PREFERENCE, null) != null) { 150 | authType = AUTH_TYPE_PSK; 151 | } else { 152 | authType = AUTH_TYPE_CERT_KEY_STORE; 153 | } 154 | 155 | Editor editor = sharedPreferences.edit(); 156 | editor.putString(AUTH_TYPE_PREFERENCE, authType); 157 | editor.commit(); 158 | } 159 | updateAuthType(authType); 160 | } 161 | 162 | private void updateAuthType(String authType) { 163 | if (mAuthTypeCatPref == null) 164 | return; 165 | mAuthTypeCatPref.removeAll(); 166 | if (authType.equals(AUTH_TYPE_PSK)) { 167 | mAuthTypeCatPref.addPreference(mPskPref); 168 | } else if (authType.equals(AUTH_TYPE_CERT_FILES)) { 169 | mAuthTypeCatPref.addPreference(mCertFilePref); 170 | mAuthTypeCatPref.addPreference(mKeyFilePref); 171 | } else if (authType.equals(AUTH_TYPE_CERT_KEY_STORE)) { 172 | mAuthTypeCatPref.addPreference(mCertAliasPref); 173 | } 174 | } 175 | 176 | private void stopHandler() { 177 | if (mHandler == null) 178 | return; 179 | mHandler.getLooper().quit(); 180 | try { 181 | mHandlerThread.join(1000); 182 | } catch (InterruptedException e) { 183 | } 184 | mHandlerThread = null; 185 | mHandler = null; 186 | } 187 | 188 | @Override 189 | protected void onDestroy() { 190 | super.onDestroy(); 191 | stopHandler(); 192 | } 193 | 194 | protected boolean onRemoteAddrChange(final Preference preference, final Object newValue) { 195 | mHandler.post(new Runnable() { 196 | public void run() { 197 | SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); 198 | String addr = (String)newValue; 199 | try { 200 | InetAddress ip = InetAddress.getByName(addr); 201 | Editor editor = sharedPreferences.edit(); 202 | editor.putString(REMOTE_ADDR_IP_PREFERENCE, ip.getHostAddress()); 203 | editor.commit(); 204 | } catch (UnknownHostException e) { 205 | Log.i("ipsec-tools", e.toString()); 206 | final Context context = preference.getContext(); 207 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 208 | builder.setTitle(android.R.string.dialog_alert_title); 209 | String msgFormat = context.getString(R.string.unknown_host_name); 210 | String msg = String.format(msgFormat, addr); 211 | builder.setMessage(msg); 212 | builder.setPositiveButton(android.R.string.ok, null); 213 | builder.show(); 214 | } 215 | } 216 | }); 217 | return true; 218 | } 219 | 220 | private void setFilePathListener(String pref, final int requestCode) { 221 | Preference customPref = findPreference(pref); 222 | SharedPreferences shared = getSharedPreferences( 223 | getSharedPreferencesName(this, mID), Activity.MODE_PRIVATE); 224 | String tmpl = shared.getString(pref, null); 225 | final String startPath; 226 | if (tmpl != null) { 227 | File tmplFile = new File(tmpl); 228 | startPath = tmplFile.getParentFile().getAbsolutePath(); 229 | } else { 230 | startPath = Environment.getExternalStorageDirectory().getAbsolutePath(); 231 | } 232 | customPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 233 | public boolean onPreferenceClick(Preference preference) { 234 | Intent intent = new Intent(PeerPreferences.this.getBaseContext(), 235 | FileDialog.class); 236 | intent.putExtra(FileDialog.START_PATH, startPath); 237 | PeerPreferences.this.startActivityForResult(intent, requestCode); 238 | return true; 239 | } 240 | }); 241 | } 242 | 243 | 244 | @Override 245 | protected void onActivityResult (int requestCode, int resultCode, final Intent data) { 246 | if (resultCode == Activity.RESULT_OK) { 247 | switch (requestCode) { 248 | case REQUEST_TEMPLATE: 249 | putFilePath(TEMPLATE_PREFERENCE, data); 250 | break; 251 | case REQUEST_CERT: 252 | putFilePath(CERT_PREFERENCE, data); 253 | break; 254 | case REQUEST_KEY: 255 | putFilePath(KEY_PREFERENCE, data); 256 | break; 257 | } 258 | } else if (resultCode == Activity.RESULT_CANCELED) { 259 | Logger.getLogger(PeerPreferences.class.getName()).log( 260 | Level.WARNING, "file not selected"); 261 | } 262 | 263 | } 264 | 265 | private void putFilePath(String pref, final Intent data) { 266 | String filePath = data.getStringExtra(FileDialog.RESULT_PATH); 267 | 268 | SharedPreferences preference = getSharedPreferences( 269 | getSharedPreferencesName(this, mID), Activity.MODE_PRIVATE); 270 | SharedPreferences.Editor editor = preference.edit(); 271 | editor.putString(pref, filePath); 272 | editor.commit(); 273 | } 274 | 275 | @Override 276 | protected void onResume() { 277 | super.onResume(); 278 | 279 | SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); 280 | 281 | Map map = sharedPreferences.getAll(); 282 | Iterator iter = map.keySet().iterator(); 283 | while(iter.hasNext()){ 284 | String key = iter.next(); 285 | Preference pref= getPreferenceScreen().findPreference(key); 286 | Object val = map.get(key); 287 | UpdateSummary(pref, key, val); 288 | } 289 | 290 | // Set up a listener whenever a key changes 291 | sharedPreferences.registerOnSharedPreferenceChangeListener(this); 292 | } 293 | 294 | @Override 295 | protected void onPause() { 296 | super.onPause(); 297 | 298 | // Unregister the listener whenever a key changes 299 | getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); 300 | } 301 | 302 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 303 | Log.i("ipsec-tools", "onSharedPreferenceChanged key:" + key); 304 | 305 | Map map = sharedPreferences.getAll(); 306 | Preference pref= getPreferenceScreen().findPreference(key); 307 | Object val = map.get(key); 308 | UpdateSummary(pref, key, val); 309 | } 310 | 311 | @Override 312 | public void onBackPressed() { 313 | SharedPreferences sharedPreferences = getPreferenceScreen() 314 | .getSharedPreferences(); 315 | if (sharedPreferences.getString(NAME_PREFERENCE, "").length() == 0) { 316 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 317 | builder.setTitle(android.R.string.dialog_alert_title); 318 | builder.setIcon(android.R.drawable.ic_dialog_alert); 319 | builder.setMessage(R.string.msg_fill_in_name); 320 | builder.setPositiveButton(android.R.string.ok, null); 321 | AlertDialog alert = builder.create(); 322 | alert.show(); 323 | return; 324 | } 325 | finish(); 326 | } 327 | 328 | private void UpdateSummary(Preference pref, String key, Object val) { 329 | if (pref == null || key == null) { 330 | Log.i("PeerPreferences", "Pref: " + pref + " key: " + key); 331 | return; 332 | } 333 | SharedPreferences sharedPreferences = getPreferenceScreen() 334 | .getSharedPreferences(); 335 | 336 | if (key.equals(TEMPLATE_PREFERENCE) || 337 | key.equals(CERT_PREFERENCE) || 338 | key.equals(KEY_PREFERENCE)) { 339 | pref.setSummary(val.toString()); 340 | } else if (key.equals(REMOTE_ADDR_PREFERENCE)) { 341 | String host = (String)val; 342 | String ip = sharedPreferences.getString(REMOTE_ADDR_IP_PREFERENCE,null); 343 | if (ip != null && !ip.equals(host)) 344 | pref.setSummary(host + "/" + ip); 345 | else 346 | pref.setSummary(host); 347 | } else if (key.equals(PSK_PREFERENCE)) { 348 | String psk = (String)val; 349 | if (psk.length() <= 0) { 350 | pref.setSummary(""); 351 | } else { 352 | pref.setSummary("********"); 353 | } 354 | } else if (pref instanceof EditTextPreference) { 355 | pref.setSummary(val.toString()); 356 | } else if (pref instanceof CheckBoxPreference) { 357 | } else if (pref instanceof ListPreference) { 358 | ListPreference listPref = (ListPreference)pref; 359 | pref.setSummary(listPref.getEntry()); 360 | } 361 | } 362 | 363 | public static String getSharedPreferencesName(Context context, PeerID id) { 364 | return context.getPackageName() + "_" + id; 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/peer/StatePreference.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.peer; 2 | 3 | import org.za.hem.ipsec_tools.R; 4 | 5 | import android.content.Context; 6 | import android.preference.Preference; 7 | import android.view.View; 8 | import android.widget.ImageView; 9 | 10 | public class StatePreference extends Preference { 11 | 12 | private ImageView mIconView; 13 | private int mState; 14 | 15 | public StatePreference(Context context) { 16 | super(context); 17 | mState = 0; 18 | } 19 | 20 | public void setIconLevel(int level) { 21 | mState = level; 22 | updateState(); 23 | } 24 | 25 | public int getIconLevel() { 26 | return mState; 27 | } 28 | 29 | protected void onBindView (View view) { 30 | mIconView = (ImageView)view.findViewById(R.id.icon); 31 | updateState(); 32 | super.onBindView(view); 33 | } 34 | 35 | protected void updateState() { 36 | if (mIconView != null) { 37 | mIconView.getDrawable().setLevel(mState); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/racoon/Admin.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.racoon; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.nio.ByteBuffer; 6 | import java.util.concurrent.ArrayBlockingQueue; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import android.util.Log; 10 | 11 | public class Admin { 12 | // FIXME 13 | public static final int MESSAGE_COMMAND = 2; 14 | 15 | private Thread mThread; 16 | private volatile ComSocket mCom; 17 | private ArrayBlockingQueue mQueue; 18 | private OnCommandListener mListener; 19 | private volatile boolean isStopping; 20 | private String mSocketPath; 21 | 22 | public Admin(String socketPath) { 23 | Log.i("ipsec-tools", "Admin ctor " + this); 24 | mSocketPath = socketPath; 25 | mCom = new ComSocket(mSocketPath); 26 | isStopping = false; 27 | mQueue = new ArrayBlockingQueue(1); 28 | mListener = new OnCommandListener() { 29 | public void onCommand(Command cmd) { 30 | Log.i("ipsec-tools", "onCommand " + cmd); 31 | mQueue.add(cmd); 32 | } 33 | }; 34 | } 35 | 36 | public interface OnCommandListener { 37 | public abstract void onCommand(Command cmd); 38 | } 39 | 40 | protected void open() throws IOException { 41 | if (mCom != null) { 42 | if (mCom.isConnected()) 43 | throw new IOException("Already connected."); 44 | } 45 | 46 | mCom.connect(); 47 | } 48 | 49 | protected void start() throws IOException { 50 | if (mThread != null) 51 | return; 52 | 53 | isStopping = false; 54 | 55 | mThread = new Thread(new Runnable() { 56 | public void run() { 57 | while (!isStopping) { 58 | try { 59 | // FIXME mCom can be null, synchronize! 60 | Log.i("ipsec-tools", "Before receive " + Thread.currentThread()); 61 | Command cmd = mCom.receive(); 62 | Log.i("ipsec-tools", "After receive " + Thread.currentThread()); 63 | 64 | if (cmd !=null ) { 65 | if (mListener != null) { 66 | mListener.onCommand(cmd); 67 | } 68 | } 69 | } catch (IOException e) { 70 | Log.i("ipsec-tools", "Admin com socket EOF " + e); 71 | //mCom.close(); 72 | return; 73 | } 74 | } 75 | } 76 | 77 | }); 78 | mThread.start(); 79 | } 80 | 81 | public void close() throws IOException { 82 | if (mCom.isConnected()) { 83 | Log.i("ipsec-tools", "Stop admin"); 84 | isStopping = true; 85 | mCom.close(); 86 | try { 87 | if (mThread != null) 88 | mThread.join(1000); 89 | } catch (InterruptedException e) { 90 | } 91 | mThread = null; 92 | Log.i("ipsec-tools", "Stopped admin"); 93 | } 94 | } 95 | 96 | // vpn-connect == establish-sa isakpm inet 97 | 98 | protected Command call(ByteBuffer bb) { 99 | try { 100 | open(); 101 | mQueue.clear(); 102 | start(); 103 | mCom.send(bb); 104 | Command cmd = mQueue.poll(1000, TimeUnit.MILLISECONDS); 105 | Log.i("ipsec-tools", "call result: " + cmd); 106 | return cmd; 107 | } catch (InterruptedException e) { 108 | Log.i("ipsec-tools", "call interrupted"); 109 | return null; 110 | } catch (IOException e) { 111 | Log.i("ipsec-tools", "call i/o error"); 112 | return null; 113 | } finally { 114 | try { 115 | close(); 116 | } catch (IOException e) { 117 | } 118 | } 119 | } 120 | 121 | public int reloadConf() { 122 | Command cmd = call(Command.buildReloadConf()); 123 | return cmd.getErrno(); 124 | } 125 | 126 | public Command dumpIsakmpSA() { 127 | return call(Command.buildDumpIsakmpSA()); 128 | } 129 | 130 | public int vpnConnect(InetAddress vpnGateway) { 131 | Command cmd = call(Command.buildVpnConnect(vpnGateway)); 132 | return cmd.getErrno(); 133 | } 134 | 135 | // TODO handle null cmd or use exceptions 136 | public int vpnDisconnect(InetAddress vpnGateway) { 137 | Log.i("ipsec-tools", "vpn-disconnect " + vpnGateway); 138 | Command cmd = call(Command.buildVpnDisconnect(vpnGateway)); 139 | return cmd.getErrno(); 140 | } 141 | 142 | public void showEvt() throws IOException { 143 | Log.i("ipsec-tools", "show-event"); 144 | open(); 145 | start(); 146 | mCom.send(Command.buildShowEvt()); 147 | // TODO check result 148 | } 149 | 150 | public void setOnCommandListener(OnCommandListener listener) { 151 | Log.i("ipsec-tools", "setOnCommandListener " + this); 152 | mListener = listener; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/racoon/ComSocket.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.racoon; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.nio.ByteBuffer; 7 | 8 | import android.net.LocalSocket; 9 | import android.net.LocalSocketAddress; 10 | import android.util.Log; 11 | 12 | public class ComSocket { 13 | private String mAdminSockPath; 14 | private LocalSocket mSocket; 15 | private OutputStream mOs; 16 | private InputStream mIs; 17 | 18 | public ComSocket(final String adminsock_path) { 19 | mAdminSockPath = adminsock_path; 20 | } 21 | 22 | public boolean isConnected() { 23 | return mSocket != null && mSocket.isConnected(); 24 | } 25 | 26 | public void connect() throws IOException { 27 | mSocket = new LocalSocket(); 28 | mSocket.connect(new LocalSocketAddress(mAdminSockPath, 29 | LocalSocketAddress.Namespace.FILESYSTEM)); 30 | mOs = mSocket.getOutputStream(); 31 | mIs = mSocket.getInputStream(); 32 | } 33 | 34 | 35 | public void send(ByteBuffer bb) throws IOException { 36 | Log.i("ipsec-tools", "send: " + bb); 37 | mOs.write(bb.array()); 38 | } 39 | 40 | public Command receive() throws IOException { 41 | return Command.receive(mIs); 42 | } 43 | 44 | public void close() throws IOException { 45 | Log.i("ipsec-tools", "Close racoon com socket"); 46 | mSocket.shutdownInput(); 47 | mSocket.shutdownOutput(); 48 | mSocket.close(); 49 | mSocket = null; 50 | mOs.close(); 51 | mIs.close(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/racoon/Command.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.racoon; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.InetAddress; 6 | import java.net.InetSocketAddress; 7 | import java.net.UnknownHostException; 8 | import java.nio.ByteBuffer; 9 | import java.nio.ByteOrder; 10 | 11 | import android.util.Log; 12 | 13 | import org.za.hem.ipsec_tools.Utils; 14 | 15 | public class Command { 16 | /* 17 | * Version field in request is valid. 18 | */ 19 | public static final int ADMIN_FLAG_VERSION = 0x8000; 20 | public static final int ADMIN_FLAG_LONG_REPLY = 0x8000; 21 | 22 | /* 23 | * No data follows as the data. 24 | * These don't use proto field. 25 | */ 26 | public static final int ADMIN_RELOAD_CONF = 0x0001; 27 | public static final int ADMIN_SHOW_SCHED = 0x0002; 28 | public static final int ADMIN_SHOW_EVT = 0x0003; 29 | 30 | /* 31 | * No data follows as the data. 32 | * These use proto field. 33 | */ 34 | public static final int ADMIN_SHOW_SA = 0x0101; 35 | public static final int ADMIN_FLUSH_SA = 0x0102; 36 | 37 | /* 38 | * The admin_com_indexes follows, see below. 39 | */ 40 | public static final int ADMIN_DELETE_SA = 0x0201; 41 | public static final int ADMIN_ESTABLISH_SA = 0x0202; 42 | public static final int ADMIN_DELETE_ALL_SA_DST = 0x0204; /* All SA for a given peer */ 43 | 44 | public static final int ADMIN_GET_SA_CERT = 0x0206; 45 | 46 | /* 47 | * The admin_com_indexes and admin_com_psk follow, see below. 48 | */ 49 | public static final int ADMIN_ESTABLISH_SA_PSK = 0x0203; 50 | 51 | /* 52 | * user login follows 53 | */ 54 | public static final int ADMIN_LOGOUT_USER = 0x0205; /* Delete SA for a given Xauth user */ 55 | 56 | /* 57 | * Range 0x08xx is reserved for privilege separation, see privsep.h 58 | */ 59 | 60 | /* the value of proto */ 61 | public static final int ADMIN_PROTO_ISAKMP = 0x01ff; 62 | public static final int ADMIN_PROTO_IPSEC = 0x02ff; 63 | public static final int ADMIN_PROTO_AH = 0x0201; 64 | public static final int ADMIN_PROTO_ESP = 0x0202; 65 | public static final int ADMIN_PROTO_INTERNAL = 0x0301; 66 | 67 | public static final int AF_INET = 2; 68 | public static final int AF_INET6 = 10; 69 | 70 | public static final int HEADER_LEN = 8; 71 | 72 | private int mLen; 73 | private int mCmd; 74 | private short mErrno; 75 | private int mProto; 76 | 77 | public int getLen() { 78 | return mLen; 79 | } 80 | 81 | public int getCmd() { 82 | return mCmd; 83 | } 84 | 85 | public short getErrno() { 86 | return mErrno; 87 | } 88 | 89 | public int getProto() { 90 | return mProto; 91 | } 92 | 93 | protected Command(int cmd, int proto, int len) { 94 | mCmd = cmd; 95 | mProto = proto; 96 | mLen = len; 97 | } 98 | 99 | public static Command receive(InputStream is) throws IOException { 100 | int res; 101 | byte[] buf = new byte[HEADER_LEN]; 102 | 103 | res = is.read(buf, 0, HEADER_LEN); 104 | if (res < 0) 105 | throw new IOException("Bad data"); 106 | if (res != HEADER_LEN) 107 | throw new IOException("Expected " + HEADER_LEN + " bytes, got " + res); 108 | 109 | ByteBuffer bb = wrap(buf); 110 | Log.i("ipsec-tools", "receive: " + bb); 111 | 112 | int lenLow = getUnsignedShort(bb); 113 | int cmd = getUnsignedShort(bb); 114 | int pos = bb.position(); 115 | short errno = bb.getShort(pos); 116 | int lenHigh = getUnsignedShort(bb); 117 | int proto = getUnsignedShort(bb); 118 | int len; 119 | 120 | if (errno != 0 && (cmd & ADMIN_FLAG_LONG_REPLY) == 0) { 121 | Command c = new Command(cmd, proto, lenLow); 122 | c.mErrno = errno; 123 | return c; 124 | } 125 | 126 | if ((cmd & ADMIN_FLAG_LONG_REPLY) != 0) { 127 | len = lenLow + (lenHigh << 16); 128 | } else { 129 | len = lenLow; 130 | } 131 | 132 | final int dataLen = len - HEADER_LEN; 133 | byte[] dataBuf = new byte[dataLen]; 134 | 135 | int p = 0; 136 | while (p < dataLen) { 137 | Log.i("ipsec-tools", "Read dataLen:" + dataLen + " p:" + p); 138 | 139 | if ((res = is.read(dataBuf, p, dataLen - p)) < 0) { 140 | throw new IOException("EOF not expected"); 141 | } 142 | 143 | p = p + res; 144 | } 145 | 146 | int c = cmd & ~ADMIN_FLAG_LONG_REPLY; 147 | Log.i("ipsec-tools", "Command: " + c); 148 | 149 | switch (cmd & ~ADMIN_FLAG_LONG_REPLY) { 150 | case ADMIN_SHOW_SCHED: 151 | return null; 152 | case ADMIN_SHOW_EVT: 153 | if (len == HEADER_LEN) 154 | return null; 155 | else 156 | return Event.create(proto, len, dataBuf); 157 | case ADMIN_GET_SA_CERT: 158 | return null; 159 | case ADMIN_SHOW_SA: 160 | switch (proto) { 161 | case ADMIN_PROTO_ISAKMP: 162 | return Ph1Dump.create(len, dataBuf); 163 | default: 164 | return null; 165 | } 166 | default: 167 | return new Command(cmd, proto, len); 168 | } 169 | } 170 | 171 | public static ByteBuffer buildShowEvt() { 172 | return buildHeader(ADMIN_SHOW_EVT, 0, 0); 173 | } 174 | 175 | public static ByteBuffer buildVpnConnect(InetAddress vpnGateway) { 176 | InetAddress srcAddr = Utils.getLocalAddress(vpnGateway); 177 | InetSocketAddress dst = new InetSocketAddress(vpnGateway, 0); 178 | InetSocketAddress src = new InetSocketAddress(srcAddr, 0); 179 | Log.i("ipsec-tools", "vpn-connect " + src + "->" + dst); 180 | 181 | int prefixLenSrc = src.getAddress().getAddress().length * 8; 182 | int prefixLenDst = dst.getAddress().getAddress().length * 8; 183 | 184 | return buildEstablishSA( 185 | ADMIN_PROTO_ISAKMP, 186 | src, prefixLenSrc, 187 | dst, prefixLenDst); 188 | } 189 | 190 | public static ByteBuffer buildEstablishSA( 191 | int proto, 192 | InetSocketAddress src, int prefixLenSrc, 193 | InetSocketAddress dst, int prefixLenDst) { 194 | 195 | ByteBuffer index = buildComIndexes( 196 | src, prefixLenSrc, 197 | dst, prefixLenDst, 198 | 0); 199 | int indexLen = index.position(); 200 | index.rewind(); 201 | 202 | ByteBuffer header = buildHeader(ADMIN_ESTABLISH_SA, 203 | proto, 204 | indexLen); 205 | 206 | header.put(index); 207 | return header; 208 | } 209 | 210 | /** 211 | * Delete all security association to destination. 212 | */ 213 | public static ByteBuffer buildVpnDisconnect( 214 | InetAddress vpnGateway) { 215 | 216 | int prefixLen = vpnGateway.getAddress().length * 8; 217 | InetAddress anyAddr; 218 | 219 | try { 220 | if (prefixLen == 32) { 221 | anyAddr = InetAddress.getByName("0.0.0.0"); 222 | } else if (prefixLen == 128){ 223 | anyAddr = InetAddress.getByName("::"); 224 | } else { 225 | throw new RuntimeException("Invalid destination address length."); 226 | } 227 | } catch (UnknownHostException e) { 228 | throw new RuntimeException(e); 229 | } 230 | 231 | InetSocketAddress src = new InetSocketAddress(anyAddr, 0); 232 | InetSocketAddress dst = new InetSocketAddress(vpnGateway, 0); 233 | return buildDeleteAllSADst( 234 | ADMIN_PROTO_ISAKMP, 235 | src, prefixLen, dst, prefixLen); 236 | } 237 | 238 | /** 239 | * Delete all security association to destination. 240 | */ 241 | public static ByteBuffer buildDeleteAllSADst( 242 | int proto, 243 | InetSocketAddress src, int prefixLenSrc, 244 | InetSocketAddress dst, int prefixLenDst) { 245 | 246 | ByteBuffer index = buildComIndexes( 247 | src, prefixLenSrc, 248 | dst, prefixLenDst, 249 | 0); 250 | int indexLen = index.position(); 251 | index.rewind(); 252 | 253 | ByteBuffer header = buildHeader(ADMIN_DELETE_ALL_SA_DST, 254 | proto, 255 | indexLen); 256 | 257 | header.put(index); 258 | return header; 259 | } 260 | 261 | public static ByteBuffer buildDumpIsakmpSA() { 262 | ByteBuffer header = buildHeader(ADMIN_SHOW_SA, 263 | ADMIN_PROTO_ISAKMP, 264 | 0); 265 | return header; 266 | } 267 | 268 | public static ByteBuffer buildReloadConf() { 269 | ByteBuffer header = buildHeader(ADMIN_RELOAD_CONF, 270 | 0, 271 | 0); 272 | return header; 273 | } 274 | 275 | protected static ByteBuffer buildHeader(int cmd, 276 | int proto, int len) { 277 | int totalLen = len + 8; 278 | ByteBuffer bb = allocate(totalLen); 279 | putUnsignedShort(bb, totalLen); 280 | putUnsignedShort(bb, ADMIN_FLAG_VERSION | cmd); 281 | putUnsignedShort(bb, 1); 282 | putUnsignedShort(bb, proto); 283 | return bb; 284 | } 285 | 286 | protected static ByteBuffer allocate(int len) { 287 | ByteBuffer bb = ByteBuffer.allocate(len); 288 | bb.order(ByteOrder.nativeOrder()); 289 | return bb; 290 | } 291 | 292 | protected static ByteBuffer wrap(byte[] b) { 293 | ByteBuffer bb = ByteBuffer.wrap(b); 294 | bb.order(ByteOrder.nativeOrder()); 295 | return bb; 296 | } 297 | 298 | protected static short getUnsignedByte(ByteBuffer bb) { 299 | return ((short)(bb.get() & 0xff)); 300 | } 301 | 302 | protected static void putUnsignedByte(ByteBuffer bb, short value) { 303 | bb.put((byte)(value & 0xff)); 304 | } 305 | 306 | protected static int getUnsignedShort(ByteBuffer bb) { 307 | return ((int)bb.getShort() & 0xffff); 308 | } 309 | 310 | protected static void putUnsignedShort(ByteBuffer bb, int value) { 311 | bb.putShort((short)(value & 0xffff)); 312 | } 313 | 314 | protected static long getUnsignedInt(ByteBuffer bb) { 315 | return ((long)bb.getInt() & 0xffffffffL); 316 | } 317 | 318 | protected static long getUnsignedInt(ByteBuffer bb, int pos) { 319 | return ((long)bb.getInt(pos) & 0xffffffffL); 320 | } 321 | 322 | protected static void putUnsignedInt(ByteBuffer bb, long value) { 323 | bb.putInt((int)(value & 0xffffffffL)); 324 | } 325 | 326 | protected static InetSocketAddress getSocketAddressStorage(ByteBuffer bb) { 327 | ByteOrder tmp = bb.order(); 328 | int pos = bb.position(); 329 | int family = getUnsignedShort(bb); 330 | bb.order(ByteOrder.BIG_ENDIAN); 331 | int port = getUnsignedShort(bb); 332 | byte[] addr; 333 | 334 | switch (family) { 335 | case AF_INET: 336 | addr = new byte[4]; 337 | long intAddr = getUnsignedInt(bb, bb.position()); 338 | Log.i("ipsec-tools", "sa " + family + " " + port + " " + intAddr); 339 | bb.get(addr); 340 | break; 341 | case AF_INET6: 342 | getUnsignedInt(bb); // Should be 0 343 | addr = new byte[16]; 344 | bb.get(addr); 345 | getUnsignedInt(bb); // Should be 0 346 | break; 347 | default: 348 | bb.order(tmp); 349 | throw new RuntimeException("Unknown address family: " + family); 350 | } 351 | 352 | bb.position(pos + 128); 353 | bb.order(tmp); 354 | 355 | try { 356 | return new InetSocketAddress(InetAddress.getByAddress(addr), port); 357 | } catch (Exception e) { 358 | throw new RuntimeException(e); 359 | } 360 | } 361 | 362 | protected static void putSocketAddressStorage(ByteBuffer bb, 363 | InetSocketAddress sa) { 364 | byte[] addr = sa.getAddress().getAddress(); 365 | 366 | ByteOrder tmp = bb.order(); 367 | int pos = bb.position(); 368 | 369 | if (addr.length == 4) { 370 | putUnsignedShort(bb, AF_INET); 371 | bb.order(ByteOrder.BIG_ENDIAN); 372 | putUnsignedShort(bb, sa.getPort()); 373 | bb.put(addr); 374 | } else if (addr.length == 16){ 375 | putUnsignedShort(bb, AF_INET6); 376 | bb.order(ByteOrder.BIG_ENDIAN); 377 | putUnsignedShort(bb, sa.getPort()); 378 | putUnsignedInt(bb, 0); 379 | bb.put(addr); 380 | putUnsignedInt(bb, 0); 381 | } else { 382 | throw new RuntimeException("Invalid length: " + addr.length); 383 | } 384 | 385 | bb.position(pos + 128); 386 | bb.order(tmp); 387 | } 388 | 389 | protected static ByteBuffer buildComIndexes( 390 | InetSocketAddress src, 391 | int srcPrefixLen, 392 | InetSocketAddress dst, 393 | int dstPrefixLen, 394 | int upperLayerProto) { 395 | ByteBuffer bb = allocate(4 + 128 + 128); 396 | 397 | putUnsignedByte(bb, (short)srcPrefixLen); 398 | putUnsignedByte(bb, (short)dstPrefixLen); 399 | putUnsignedByte(bb, (short)upperLayerProto); 400 | putUnsignedByte(bb, (short)0); // reserved 401 | putSocketAddressStorage(bb, src); 402 | putSocketAddressStorage(bb, dst); 403 | return bb; 404 | } 405 | 406 | public String toString() { 407 | return "Command: " + mCmd + " " + mProto + " " + mErrno + " " + mLen; 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/racoon/Event.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.racoon; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.nio.ByteBuffer; 5 | 6 | public class Event extends Command { 7 | 8 | /* type */ 9 | public static final int EVT_RACOON_QUIT = 0x0001; 10 | 11 | public static final int EVT_PHASE1_UP = 0x0100; 12 | public static final int EVT_PHASE1_DOWN = 0x0101; 13 | public static final int EVT_PHASE1_NO_RESPONSE = 0x0102; 14 | public static final int EVT_PHASE1_NO_PROPOSAL = 0x0103; 15 | public static final int EVT_PHASE1_AUTH_FAILED = 0x0104; 16 | public static final int EVT_PHASE1_DPD_TIMEOUT = 0x0105; 17 | public static final int EVT_PHASE1_PEER_DELETED = 0x0106; 18 | public static final int EVT_PHASE1_MODE_CFG = 0x0107; 19 | public static final int EVT_PHASE1_XAUTH_SUCCESS= 0x0108; 20 | public static final int EVT_PHASE1_XAUTH_FAILED = 0x0109; 21 | 22 | public static final int EVT_PHASE2_NO_PHASE1 = 0x0200; 23 | public static final int EVT_PHASE2_UP = 0x0201; 24 | public static final int EVT_PHASE2_DOWN = 0x0202; 25 | public static final int EVT_PHASE2_NO_RESPONSE = 0x0203; 26 | 27 | private int mType; 28 | private long mTimeStamp; 29 | private InetSocketAddress mPh1src; 30 | private InetSocketAddress mPh1dst; 31 | private long mPh2MsgId; 32 | private boolean mSynthetic; 33 | 34 | public int getType() { 35 | return mType; 36 | } 37 | 38 | public long getTimeStamp() { 39 | return mTimeStamp; 40 | } 41 | 42 | public InetSocketAddress getPh1src() { 43 | return mPh1src; 44 | } 45 | 46 | public InetSocketAddress getPh1dst() { 47 | return mPh1dst; 48 | } 49 | 50 | public long getPh2MsgId() { 51 | return mPh2MsgId; 52 | } 53 | 54 | public boolean getSynthetic() { 55 | return mSynthetic; 56 | } 57 | 58 | public void setSynthetic(boolean synthetic) { 59 | mSynthetic = synthetic; 60 | } 61 | 62 | public Event(int proto, int len, 63 | int type, long timeStamp, 64 | InetSocketAddress ph1src, InetSocketAddress ph1dst, long ph2MsgId) { 65 | super(ADMIN_SHOW_EVT, proto, len); 66 | mType = type; 67 | mTimeStamp = timeStamp; 68 | mPh1src = ph1src; 69 | mPh1dst = ph1dst; 70 | mPh2MsgId = ph2MsgId; 71 | mSynthetic = false; 72 | } 73 | 74 | protected static Event create(int proto, int len, byte[] data) { 75 | ByteBuffer bb = wrap(data); 76 | 77 | int type = (int)getUnsignedInt(bb); 78 | long timeStamp = bb.getInt(); 79 | InetSocketAddress ph1src = getSocketAddressStorage(bb); 80 | InetSocketAddress ph1dst = getSocketAddressStorage(bb); 81 | long ph2MsgId = getUnsignedInt(bb); 82 | 83 | return new Event(proto, len, type, timeStamp, ph1src, ph1dst, ph2MsgId); 84 | } 85 | 86 | public String toString() { 87 | return "Event: " + mType + " " + mTimeStamp + " " + mPh1src + " " + mPh1dst + " " + mPh2MsgId; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/racoon/Ph1Dump.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.racoon; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.nio.ByteBuffer; 5 | import java.util.AbstractCollection; 6 | import java.util.ArrayList; 7 | 8 | import android.util.Log; 9 | 10 | public class Ph1Dump extends Command { 11 | 12 | private ArrayList mItems; 13 | 14 | public class Item { 15 | public byte[] mInitiatorCookie; 16 | public byte[] mResponderCookie; 17 | public int mStatus; 18 | public int mSide; 19 | public InetSocketAddress mRemote; 20 | public InetSocketAddress mLocal; 21 | public short mVersion; 22 | public short mEType; 23 | public long mTimeCreated; 24 | public int mPh2Cnt; 25 | 26 | public Item( 27 | // index 28 | byte[] icookie, byte[] rcookie, 29 | int status, int side, 30 | InetSocketAddress remote, 31 | InetSocketAddress local, 32 | short version, 33 | short eType, 34 | long timeCreated, 35 | int ph2Cnt) { 36 | mInitiatorCookie = icookie; 37 | mResponderCookie = rcookie; 38 | mStatus = status; 39 | mSide = side; 40 | mRemote = remote; 41 | mLocal = local; 42 | mVersion = version; 43 | mEType = eType; 44 | mTimeCreated = timeCreated; 45 | mPh2Cnt = ph2Cnt; 46 | } 47 | } 48 | 49 | protected Ph1Dump(int len) { 50 | super(ADMIN_SHOW_SA, ADMIN_PROTO_ISAKMP, len); 51 | mItems = new ArrayList(); 52 | } 53 | 54 | public AbstractCollection getItems() { return mItems; } 55 | 56 | private static final String HEXES = "0123456789ABCDEF"; 57 | private static String getHex( byte [] raw ) { 58 | if ( raw == null ) { 59 | return null; 60 | } 61 | final StringBuilder hex = new StringBuilder( 2 * raw.length ); 62 | for ( final byte b : raw ) { 63 | hex.append(HEXES.charAt((b & 0xF0) >> 4)) 64 | .append(HEXES.charAt((b & 0x0F))); 65 | } 66 | return hex.toString(); 67 | } 68 | 69 | protected static Ph1Dump create(int len, byte[] data) { 70 | ByteBuffer bb = wrap(data); 71 | Ph1Dump pd = new Ph1Dump(len); 72 | 73 | int count = len / (8 + 8 + 4 + 4 + 128 + 128 + 2 + 2 + 4 + 4); 74 | 75 | Log.i("ipsec-tools", "Ph1Dump create len:" + len + " count:" + count + " size:" + data.length); 76 | 77 | for (int i = 0; i aliases = mKS.aliases(); 80 | int i = 0; 81 | while (aliases.hasMoreElements()) { 82 | String alias = aliases.nextElement(); 83 | aliasArray[i++] = alias; 84 | } 85 | return aliasArray; 86 | } 87 | 88 | public void load(InputStream p12Stream, char[] password) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException { 89 | KeyStore ks = getPKCS12Instance(); 90 | ks.load(p12Stream, password); 91 | 92 | Enumeration aliases = ks.aliases(); 93 | while (aliases.hasMoreElements()) { 94 | String alias = aliases.nextElement(); 95 | try { 96 | KeyStore.Entry entry = ks.getEntry(alias, null); 97 | mKS.setEntry(alias, entry, null); 98 | } catch (UnrecoverableEntryException e) { 99 | // Ignore cert 100 | e.printStackTrace(); 101 | } 102 | } 103 | 104 | OutputStream os = new BufferedOutputStream(new FileOutputStream(mKeyStoreFile)); 105 | mKS.store(os, MASTER_PWD); 106 | os.close(); 107 | } 108 | 109 | static protected KeyStore getPKCS12Instance() throws KeyStoreException { 110 | return java.security.KeyStore.getInstance(KEYSTORE_PKCS12); 111 | } 112 | 113 | public void writeCert(File path, String filePrefix, String alias) { 114 | File certFile = new File(path, filePrefix + CERT_POSTFIX); 115 | File keyFile = new File(path, filePrefix + KEY_POSTFIX); 116 | try { 117 | if (!mKS.containsAlias(alias)) 118 | // TODO handle error 119 | return; 120 | Certificate[] chain = mKS.getCertificateChain(alias); 121 | Key key = mKS.getKey(alias, null); 122 | 123 | OutputStream certOs = new BufferedOutputStream(new FileOutputStream(certFile)); 124 | for(int i = 0; i < chain.length; i++) { 125 | Certificate cert = chain[i]; 126 | writeBase64(certOs, PEM_CERT_NAME, cert.getEncoded()); 127 | } 128 | certOs.close(); 129 | 130 | OutputStream keyOs = new BufferedOutputStream(new FileOutputStream(keyFile)); 131 | writeBase64(keyOs, PEM_KEY_NAME, key.getEncoded()); 132 | keyOs.close(); 133 | return; 134 | } catch (IOException e) { 135 | // Ignore 136 | e.printStackTrace(); 137 | } catch (CertificateEncodingException e) { 138 | // Ignore 139 | e.printStackTrace(); 140 | } catch (KeyStoreException e) { 141 | // Ignore 142 | e.printStackTrace(); 143 | } catch (NoSuchAlgorithmException e) { 144 | // Ignore 145 | e.printStackTrace(); 146 | } catch (UnrecoverableKeyException e) { 147 | // Ignore 148 | e.printStackTrace(); 149 | } 150 | 151 | // Failed 152 | if (certFile.exists()) 153 | certFile.delete(); 154 | if (keyFile.exists()) 155 | keyFile.delete(); 156 | } 157 | 158 | private void writeBase64(OutputStream fos, String type, byte[] encoded) throws IOException { 159 | OutputStreamWriter os = new OutputStreamWriter(fos); 160 | os.write("-----BEGIN " + type + "-----\n"); 161 | os.flush(); 162 | OutputStream base64 = new Base64OutputStream(fos, 163 | Base64.NO_CLOSE | Base64.NO_PADDING); 164 | base64.write(encoded); 165 | base64.close(); 166 | 167 | switch(encoded.length % 3) { 168 | case 0: break; 169 | case 1: os.write("=="); break; 170 | case 2: os.write("="); break; 171 | } 172 | os.write("\n-----END " + type + "-----\n"); 173 | os.flush(); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/service/ConfigManager.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.service; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.FileReader; 7 | import java.io.FileWriter; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.io.Reader; 11 | import java.io.Writer; 12 | import java.net.InetAddress; 13 | import java.util.AbstractCollection; 14 | import java.util.HashMap; 15 | import java.util.Iterator; 16 | import java.util.Map; 17 | import java.util.regex.Matcher; 18 | import java.util.regex.Pattern; 19 | 20 | import org.za.hem.ipsec_tools.NativeCommand; 21 | import org.za.hem.ipsec_tools.peer.Peer; 22 | 23 | import android.content.Context; 24 | import android.os.Environment; 25 | import android.os.Process; 26 | 27 | /** 28 | * Racoon and setkey config files builder. 29 | * 30 | * @author mikael 31 | * 32 | */ 33 | public class ConfigManager { 34 | 35 | public static final String PATTERN = "\\$\\{([a-zA-Z0-9_]+)\\}"; 36 | public static final String CONFIG_POSTFIX = ".conf"; 37 | public static final String PEERS_CONFIG = "peers.conf"; 38 | public static final String SETKEY_CONFIG = "setkey.conf"; 39 | public static final String PSK_CONFIG = "psk.txt"; 40 | public static final String RACOON_HEAD = "racoon.head"; 41 | public static final String SETKEY_HEAD = "setkey.head"; 42 | public static final String PIDFILE = "racoon.pid"; 43 | 44 | public static final String BINDIR = "bin"; 45 | public static final String CERTDIR = "certs"; 46 | 47 | public enum Action {NONE, ADD, DELETE, UPDATE}; 48 | 49 | // Variables usable in config files 50 | private static final String VAR_BINDIR = "bindir"; 51 | private static final String VAR_CERTDIR = "certdir"; 52 | private static final String VAR_EXTDIR = "extdir"; 53 | private static final String VAR_REMOTE_ADDR = "remote_addr"; 54 | private static final String VAR_LOCAL_ADDR = "local_addr"; 55 | private static final String VAR_UID = "uid"; 56 | private static final String VAR_GID = "gid"; 57 | private static final String VAR_NAME = "name"; 58 | private static final String VAR_ACTION = "action"; 59 | private static final String VAR_CERT = "cert"; 60 | private static final String VAR_KEY = "key"; 61 | 62 | 63 | private Pattern mPat; 64 | private Map mVariables; 65 | private Context mContext; 66 | private NativeCommand mNative; 67 | private File mBinDir; 68 | private File mCertDir; 69 | private CertManager mCertManager; 70 | 71 | public ConfigManager(Context context, NativeCommand nativeCmd, CertManager certManager) { 72 | mBinDir = context.getDir(BINDIR, Context.MODE_PRIVATE); 73 | mCertDir = context.getDir(CERTDIR, Context.MODE_PRIVATE); 74 | mVariables = new HashMap(); 75 | mVariables.put(VAR_BINDIR, mBinDir.getAbsolutePath()); 76 | mVariables.put(VAR_CERTDIR, mCertDir.getAbsolutePath()); 77 | mVariables.put(VAR_EXTDIR, Environment.getExternalStorageDirectory().getAbsolutePath()); 78 | mVariables.put(VAR_UID, "" + Process.myUid()); 79 | mVariables.put(VAR_GID, "" + Process.myUid()); 80 | mPat = Pattern.compile(PATTERN); 81 | mContext = context; 82 | mNative = nativeCmd; 83 | mCertManager = certManager; 84 | } 85 | 86 | protected File getPeerConfigFile(Peer peer) { 87 | return new File(mBinDir, peer.getPeerID().toString() + CONFIG_POSTFIX); 88 | } 89 | 90 | /** 91 | * Build racoon config for one peer and setkey portion 92 | * @param peer 93 | * @param racoonOs 94 | * @param setkeyUpOs 95 | * @param setkeyDownOs 96 | * @throws IOException 97 | */ 98 | private void writePeerConfig(Action action, Peer peer, Writer racoonOs, 99 | Writer setkeyOs, Writer pskOs) throws IOException { 100 | String certPrefix = CertManager.CERT_PREFIX + peer.getPeerID().intValue(); 101 | String certAlias = peer.getCertAlias(); 102 | InetAddress addr = peer.getRemoteAddr(); 103 | if (addr != null) 104 | mVariables.put(VAR_REMOTE_ADDR, addr.getHostAddress()); 105 | mVariables.put(VAR_LOCAL_ADDR, peer.getLocalAddr().getHostAddress()); 106 | mVariables.put(VAR_NAME, peer.getName()); 107 | if (certAlias == null || certAlias == "") { 108 | mVariables.put(VAR_CERT, peer.getCert()); 109 | mVariables.put(VAR_KEY, peer.getKey()); 110 | } else { 111 | mVariables.put(VAR_CERT, certPrefix + CertManager.CERT_POSTFIX); 112 | mVariables.put(VAR_KEY, certPrefix + CertManager.KEY_POSTFIX); 113 | } 114 | mVariables.put(VAR_ACTION, actionToString(action)); 115 | File tmpl = peer.getTemplateFile(); 116 | if (tmpl == null) 117 | return; 118 | PolicyFile policy = new PolicyFile(tmpl); 119 | if (racoonOs != null) 120 | substitute(policy.getRacoonConfStream(), racoonOs); 121 | if (action != Action.NONE && setkeyOs != null) 122 | substitute(policy.getSetkeyConfStream(), setkeyOs); 123 | if (pskOs != null && addr != null) { 124 | pskOs.write("# Peer " + peer.getName() + "\n"); 125 | pskOs.write(mVariables.get(VAR_REMOTE_ADDR) + " " + peer.getPsk() + "\n"); 126 | } 127 | mCertManager.writeCert(mCertDir, certPrefix, certAlias); 128 | } 129 | 130 | private static String actionToString(Action action) { 131 | switch (action) { 132 | case NONE: 133 | return "none"; 134 | case ADD: 135 | return "add"; 136 | case DELETE: 137 | return "delete"; 138 | case UPDATE: 139 | return "update"; 140 | default: 141 | throw new RuntimeException("Unknown action: " + action); 142 | } 143 | } 144 | 145 | /** 146 | * Build racoon config for one peer and setkey portion 147 | * @param action 148 | * @param peer 149 | * @param setkeyOs 150 | * @return racoon config file 151 | * @throws IOException 152 | */ 153 | public File buildPeerConfig(Action action, Peer peer, Writer setkeyOs, Writer pskOs) throws IOException { 154 | mVariables.put(VAR_LOCAL_ADDR, peer.getLocalAddr().getHostAddress()); 155 | 156 | File racoonFile = getPeerConfigFile(peer); 157 | FileWriter racoonOs = new FileWriter(racoonFile); 158 | 159 | writePeerConfig(action, peer, racoonOs, setkeyOs, pskOs); 160 | racoonOs.close(); 161 | return racoonFile; 162 | } 163 | 164 | /** 165 | * 166 | * @param peers 167 | * @param updateAllPeers 168 | * @throws IOException 169 | */ 170 | public void build(AbstractCollection peers, 171 | boolean addAllPeers) throws IOException { 172 | Iterator iter = peers.iterator(); 173 | File pskFile = new File(mBinDir, PSK_CONFIG); 174 | Writer out = null; 175 | Writer setkeyOut = null; 176 | Writer pskOut = null; 177 | Reader inHead = null; 178 | Reader setkeyHead = null; 179 | 180 | try { 181 | out = new FileWriter(new File(mBinDir, PEERS_CONFIG)); 182 | inHead = new InputStreamReader(mContext.getAssets().open(RACOON_HEAD)); 183 | substitute(inHead, out); 184 | 185 | setkeyOut = new FileWriter(new File(mBinDir, SETKEY_CONFIG)); 186 | setkeyHead = new InputStreamReader(mContext.getAssets().open(SETKEY_HEAD)); 187 | substitute(setkeyHead, setkeyOut); 188 | 189 | pskFile.delete(); 190 | pskOut = new FileWriter(pskFile); 191 | 192 | while (iter.hasNext()) { 193 | Peer peer = iter.next(); 194 | if (peer == null) 195 | continue; 196 | if (!peer.isEnabled()) 197 | continue; 198 | mVariables.remove(VAR_REMOTE_ADDR); 199 | mVariables.remove(VAR_LOCAL_ADDR); 200 | mVariables.remove(VAR_NAME); 201 | try { 202 | File output; 203 | if (addAllPeers) 204 | output = buildPeerConfig(Action.ADD, peer, setkeyOut, pskOut); 205 | else { 206 | writePeerConfig(Action.NONE, peer, null, setkeyOut, pskOut); 207 | output = getPeerConfigFile(peer); 208 | } 209 | out.write("include \"" + output.getAbsolutePath() + "\";\n"); 210 | } catch (IOException e){ 211 | } 212 | } 213 | 214 | mNative.chown(pskFile, "root", "root"); 215 | } finally { 216 | if (out != null) 217 | out.close(); 218 | if (setkeyOut != null) 219 | setkeyOut.close(); 220 | if (pskOut != null) 221 | pskOut.close(); 222 | if (inHead != null) 223 | inHead.close(); 224 | if (setkeyHead != null) 225 | setkeyHead.close(); 226 | } 227 | // build peers.conf 228 | } 229 | 230 | public void buildSPDAction(Peer peer, Action action) throws IOException { 231 | Writer setkeyOut = null; 232 | Reader setkeyHead = null; 233 | 234 | setkeyOut = new FileWriter(new File(mBinDir, SETKEY_CONFIG)); 235 | setkeyHead = new InputStreamReader(mContext.getAssets().open(SETKEY_HEAD)); 236 | substitute(setkeyHead, setkeyOut); 237 | 238 | mVariables.remove(VAR_REMOTE_ADDR); 239 | mVariables.remove(VAR_LOCAL_ADDR); 240 | mVariables.remove(VAR_NAME); 241 | 242 | writePeerConfig(action, peer, null, setkeyOut, null); 243 | 244 | if (setkeyOut != null) 245 | setkeyOut.close(); 246 | if (setkeyHead != null) 247 | setkeyHead.close(); 248 | } 249 | 250 | public void buildAddSPD(Peer peer) throws IOException { 251 | buildSPDAction(peer, Action.ADD); 252 | } 253 | 254 | public void buildDeleteSPD(Peer peer) throws IOException { 255 | buildSPDAction(peer, Action.DELETE); 256 | } 257 | 258 | public void addVariable(String key, String value) { 259 | mVariables.put(key, value); 260 | } 261 | 262 | private String substituteLine(String line) { 263 | StringBuffer buf = new StringBuffer(); 264 | Matcher m = mPat.matcher(line); 265 | 266 | while (m.find()) { 267 | String var = m.group(1); 268 | String value; 269 | 270 | if (mVariables.containsKey(var)) { 271 | value = mVariables.get(var); 272 | } else { 273 | value = ""; 274 | } 275 | 276 | m.appendReplacement(buf, value); 277 | } 278 | m.appendTail(buf); 279 | buf.append('\n'); 280 | 281 | return buf.toString(); 282 | } 283 | 284 | private void substitute(Reader input, Writer os) { 285 | BufferedReader is = new BufferedReader(input, 8192); 286 | 287 | try { 288 | String line; 289 | while ( (line = is.readLine()) != null) { 290 | os.write(substituteLine(line)); 291 | } 292 | } catch (FileNotFoundException e) { 293 | throw new RuntimeException(e); 294 | } catch (IOException e) { 295 | throw new RuntimeException(e); 296 | } finally { 297 | } 298 | } 299 | 300 | private void substitute(java.io.InputStream input, Writer os) { 301 | substitute(new InputStreamReader(input), os); 302 | } 303 | 304 | private void substitute(File input, Writer os) { 305 | Reader is = null; 306 | try { 307 | is = new FileReader(input); 308 | substitute(is, os); 309 | } catch (IOException e) { 310 | throw new RuntimeException(e); 311 | } finally { 312 | try { 313 | if (is != null) 314 | is.close(); 315 | } catch (IOException e) { 316 | } 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/service/ConnectivityReceiver.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.service; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.util.Log; 7 | 8 | public class ConnectivityReceiver extends BroadcastReceiver { 9 | 10 | @Override 11 | public void onReceive(Context arg0, Intent arg1) { 12 | // TODO Auto-generated method stub 13 | Log.i("ipsec-tools", "ConnectivityReceiver"); 14 | Log.i("ipsec-tools", "ConnectivityReceiver " + arg1.getExtras()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/service/NativeService.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.service; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.io.OutputStream; 10 | import java.net.InetAddress; 11 | import java.net.InetSocketAddress; 12 | import java.net.UnknownHostException; 13 | import java.util.Iterator; 14 | 15 | import org.za.hem.ipsec_tools.IPsecToolsActivity; 16 | import org.za.hem.ipsec_tools.NativeCommand; 17 | import org.za.hem.ipsec_tools.R; 18 | import org.za.hem.ipsec_tools.racoon.Admin; 19 | import org.za.hem.ipsec_tools.racoon.Command; 20 | import org.za.hem.ipsec_tools.racoon.Event; 21 | import org.za.hem.ipsec_tools.racoon.Ph1Dump; 22 | import org.za.hem.ipsec_tools.racoon.Ph1Dump.Item; 23 | 24 | import android.app.ActivityManager; 25 | import android.app.Notification; 26 | import android.app.NotificationManager; 27 | import android.app.PendingIntent; 28 | import android.app.Service; 29 | import android.app.ActivityManager.RunningServiceInfo; 30 | import android.content.Context; 31 | import android.content.Intent; 32 | import android.os.Binder; 33 | import android.os.Environment; 34 | import android.os.Handler; 35 | import android.os.HandlerThread; 36 | import android.os.Message; 37 | import android.os.IBinder; 38 | import android.util.Log; 39 | 40 | 41 | public class NativeService extends Service { 42 | public static final int HANDLER_RACOON_STARTED = 1; 43 | 44 | /* Worker handlers */ 45 | public static final int HANDLER_INTENT = 1; 46 | public static final int HANDLER_VPN_CONNECT = 2; 47 | public static final int HANDLER_VPN_DISCONNECT = 3; 48 | public static final int HANDLER_DUMP_ISAKMP_SA = 4; 49 | 50 | public static final String PACKAGE = "org.za.hem.ipsec_tool.service"; 51 | public static final String SERVICE_NAME = PACKAGE + ".NativeService"; 52 | public static final String ACTION_NOTIFICATION = PACKAGE + ".NOTIFICATION"; 53 | public static final String ACTION_DESTROYED = PACKAGE + ".DESTROYED"; 54 | public static final String ACTION_PHASE1_UP = PACKAGE + ".PHASE1_UP"; 55 | public static final String ACTION_PHASE1_DOWN = PACKAGE + ".PHASE1_DOWN"; 56 | public static final String ACTION_VPN_CONNECT = PACKAGE + ".VPN_CONNECT"; 57 | public static final String ACTION_VPN_DISCONNECT = PACKAGE + ".VPN_DISCONNECT"; 58 | public static final String ACTION_SERVICE_READY = PACKAGE + ".SERVICE_READY"; 59 | 60 | public static final String RACOON_EXEC_NAME = "racoon.sh"; 61 | public static final String RACOON_BIN_NAME = "racoon.mikma"; 62 | public static final String SETKEY_EXEC_NAME = "setkey.sh"; 63 | 64 | private static final String PROP_NET_DNS1 = "net.dns1"; 65 | private static final String PROP_NET_DNS2 = "net.dns2"; 66 | private static final String PROP_NET_DNSCHANGE = "net.dnschange"; 67 | 68 | private HandlerThread mWorker; 69 | private NotificationManager mNM; 70 | private Admin mAdmin; 71 | private Admin mAdminCmd; 72 | 73 | // Unique Identification Number for the Notification. 74 | // We use it on Notification start, and to cancel it. 75 | private int NOTIFICATION = R.string.native_service_started; 76 | private String mSocketPath; 77 | private int mPid = -1; 78 | 79 | private NativeCommand mNative; 80 | private String mSaveDns1; 81 | private String mSaveDns2; 82 | 83 | static public class NativeBinder extends Binder { 84 | NativeService mService; 85 | 86 | protected void setService(NativeService service) { 87 | mService = service; 88 | } 89 | 90 | protected void clearService() { 91 | mService = null; 92 | } 93 | 94 | public NativeService getService() { 95 | return mService; 96 | } 97 | } 98 | 99 | public static boolean isServiceRunning(Context context) { 100 | ActivityManager manager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE); 101 | for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { 102 | if (SERVICE_NAME.equals(service.service.getClassName())) { 103 | return true; 104 | } 105 | } 106 | return false; 107 | } 108 | 109 | private Handler mWorkerHandler; 110 | 111 | // This is the object that receives interactions from clients. See 112 | // RemoteService for a more complete example. 113 | private final NativeBinder mBinder = new NativeBinder(); 114 | 115 | @Override 116 | public void onCreate() { 117 | mNative = new NativeCommand(this); 118 | flushAllSPD(); 119 | 120 | mBinder.setService(this); 121 | 122 | mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); 123 | mSocketPath = new File(getDir("bin", 0), "racoon.sock").getPath(); 124 | mWorker = new HandlerThread("NativeService"); 125 | mWorker.start(); 126 | 127 | mWorkerHandler = new Handler(mWorker.getLooper()) { 128 | @Override 129 | public void handleMessage(Message msg) { 130 | switch (msg.what) { 131 | case HANDLER_INTENT: 132 | onHandleIntent((Intent)msg.obj); 133 | break; 134 | case HANDLER_VPN_CONNECT: 135 | onVpnConnect((String)msg.obj); 136 | break; 137 | case HANDLER_VPN_DISCONNECT: 138 | onVpnDisconnect((String)msg.obj); 139 | break; 140 | case HANDLER_DUMP_ISAKMP_SA: 141 | onDumpIsakmpSA(); 142 | break; 143 | default: 144 | Log.i("ipsec-tools", "Unhandled " + msg.obj); 145 | break; 146 | } 147 | } 148 | }; 149 | 150 | } 151 | 152 | @Override 153 | public int onStartCommand(Intent intent, int flags, int startId) { 154 | Log.i("ipsec-tools", "Received start id " + startId + ": " + intent); 155 | 156 | if (intent == null || intent.getAction() == null ) { 157 | } else if (intent.getAction().equals(ACTION_NOTIFICATION)) { 158 | Log.i("ipsec-tools", "Notification"); 159 | } else { 160 | Message msg = mWorkerHandler.obtainMessage(HANDLER_INTENT); 161 | msg.obj = intent; 162 | msg.arg1 = flags; 163 | msg.arg2 = startId; 164 | msg.sendToTarget(); 165 | } 166 | 167 | // We want this service to continue running until it is explicitly 168 | // stopped, so return sticky. 169 | return START_STICKY; 170 | } 171 | 172 | @Override 173 | public void onDestroy() { 174 | // Clear service reference in binder 175 | mBinder.clearService(); 176 | 177 | mWorkerHandler.getLooper().quit(); 178 | 179 | if (isRacoonRunning()) 180 | stopRacoon(); 181 | } 182 | 183 | @Override 184 | public IBinder onBind(Intent arg0) { 185 | Log.i("ipsec-tools", "onBind " + mBinder); 186 | return mBinder; 187 | } 188 | 189 | protected void onHandleIntent(Intent intent) { 190 | 191 | } 192 | 193 | public void startRacoon() { 194 | Log.i("ipsec-tools", "Start racoon"); 195 | if (isRacoonRunning()) 196 | return; 197 | runSetKey(); 198 | 199 | // Display a notification about us starting. We put an icon in the status bar. 200 | //showNotification(); 201 | startForeground(NOTIFICATION, createNotification()); 202 | 203 | mAdmin = new Admin(mSocketPath); 204 | mAdminCmd = new Admin(mSocketPath); 205 | 206 | Log.i("ipsec-tools", "Start thread"); 207 | new Thread(new Runnable() { 208 | public void run() { 209 | // FIXME DEBUGing code 210 | runRacoon(); 211 | //Message.obtain(mHandler, HANDLER_RACOON_STARTED).sendToTarget(); 212 | } 213 | }).start(); 214 | } 215 | 216 | public void stopRacoon() { 217 | Log.i("ipsec-tools", "Stop racoon"); 218 | 219 | if (!isRacoonRunning()) 220 | return; 221 | 222 | try { 223 | mAdmin.close(); 224 | } catch (IOException e) { 225 | } 226 | mAdmin = null; 227 | mAdminCmd = null; 228 | 229 | // Kill racoon instance 230 | if (mPid > 0) { 231 | Log.i("ipsec-tools", "kill racoon " + mPid); 232 | String out = NativeCommand.system("kill " + mPid); 233 | Log.i("ipsec-tools", "outt " + out); 234 | mPid = -1; 235 | } 236 | 237 | // TODO clear setkey? 238 | 239 | flushAllSPD(); 240 | 241 | Intent broadcastIntent = new Intent(); 242 | broadcastIntent.setAction(ACTION_DESTROYED); 243 | sendBroadcast(broadcastIntent); 244 | 245 | Log.i("ipsec-tools", "Destroyed"); 246 | 247 | // Cancel the persistent notification. 248 | //mNM.cancel(NOTIFICATION); 249 | 250 | stopForeground(true); 251 | 252 | // Tell the user we stopped. 253 | //Toast.makeText(this, R.string.native_service_stopped, Toast.LENGTH_SHORT).show(); 254 | 255 | } 256 | 257 | public int reloadConf() { 258 | if (isRacoonRunning()) 259 | return mAdminCmd.reloadConf(); 260 | else 261 | return -1; 262 | } 263 | 264 | public boolean isRacoonRunning() { 265 | return mPid > 0; 266 | } 267 | 268 | public void dumpIsakmpSA() { 269 | Log.i("ipsec-tools", "dumpIsakmpSA"); 270 | Message msg = mWorkerHandler.obtainMessage(HANDLER_DUMP_ISAKMP_SA); 271 | msg.sendToTarget(); 272 | } 273 | 274 | protected void onDumpIsakmpSA() { 275 | Command cmd = mAdminCmd.dumpIsakmpSA(); 276 | Ph1Dump pd = (Ph1Dump)cmd; 277 | if (pd == null) 278 | return; 279 | Iterator iter = pd.getItems().iterator(); 280 | 281 | while (iter.hasNext()) { 282 | Item dump = iter.next(); 283 | 284 | Log.i("ipsec-tools", "onDumpIsakmpSA " + dump.mRemote); 285 | 286 | if (mListener == null) { 287 | Log.i("ipsec-tools", "No listener " + this); 288 | return; 289 | } 290 | 291 | final int INITIATOR = 0; 292 | //final int RESPONDER = 1; 293 | InetSocketAddress ph1src; 294 | InetSocketAddress ph1dst; 295 | 296 | if (dump.mSide == INITIATOR) { 297 | ph1src = dump.mLocal; 298 | ph1dst = dump.mRemote; 299 | } else { 300 | ph1src = dump.mRemote; 301 | ph1dst = dump.mLocal; 302 | } 303 | 304 | Event evt = new Event(Command.ADMIN_PROTO_ISAKMP, -1, Event.EVT_PHASE1_UP, 305 | dump.mTimeCreated, ph1src, ph1dst, -1); 306 | evt.setSynthetic(true); 307 | mListener.onCommand(evt); 308 | } 309 | } 310 | 311 | public void vpnConnect(String gw) { 312 | Message msg = mWorkerHandler.obtainMessage(HANDLER_VPN_CONNECT); 313 | msg.obj = gw; 314 | msg.sendToTarget(); 315 | } 316 | 317 | private void increaseDnsChange() { 318 | int change = Integer.parseInt(mNative.getprop(PROP_NET_DNSCHANGE)); 319 | mNative.setprop(PROP_NET_DNSCHANGE, Integer.toString(change + 1)); 320 | } 321 | 322 | public void storeDns(String dns1, String dns2) { 323 | if (dns1 == null) { 324 | return; 325 | } 326 | if (dns2 == null) { 327 | dns2 = ""; 328 | } 329 | 330 | if (mSaveDns1 == null) { 331 | mSaveDns1 = mNative.getprop(PROP_NET_DNS1); 332 | 333 | if (mSaveDns2 == null) { 334 | mSaveDns2 = mNative.getprop(PROP_NET_DNS2); 335 | } 336 | } 337 | mNative.setprop(PROP_NET_DNS1, dns1); 338 | mNative.setprop(PROP_NET_DNS2, dns2); 339 | increaseDnsChange(); 340 | Log.i("ipsec-tools", "VPN save " + mSaveDns1 + " " + mSaveDns2 + " " + dns1 + " " + dns2); 341 | } 342 | 343 | public void restoreDns() { 344 | boolean isChanged = false; 345 | 346 | if (mSaveDns1 != null) { 347 | mNative.setprop(PROP_NET_DNS1, mSaveDns1); 348 | mSaveDns1 = null; 349 | isChanged = true; 350 | } 351 | if (mSaveDns2 != null) { 352 | mNative.setprop(PROP_NET_DNS2, mSaveDns2); 353 | mSaveDns2 = null; 354 | isChanged = true; 355 | } 356 | if (isChanged) { 357 | increaseDnsChange(); 358 | } 359 | } 360 | 361 | protected void onVpnConnect(String gw) { 362 | try { 363 | InetAddress addr = InetAddress.getByName(gw); 364 | mAdminCmd.vpnConnect(addr); 365 | } catch (UnknownHostException e) { 366 | throw new RuntimeException(e); 367 | } 368 | } 369 | 370 | public void vpnDisconnect(String gw) { 371 | Message msg = mWorkerHandler.obtainMessage(HANDLER_VPN_DISCONNECT); 372 | msg.obj = gw; 373 | msg.sendToTarget(); 374 | } 375 | 376 | public void onVpnDisconnect(String gw) { 377 | try { 378 | InetAddress addr = InetAddress.getByName(gw); 379 | mAdminCmd.vpnDisconnect(addr); 380 | } catch (UnknownHostException e) { 381 | throw new RuntimeException(e); 382 | } 383 | } 384 | 385 | private Notification createNotification() { 386 | // In this sample, we'll use the same text for the ticker and the expanded notification 387 | CharSequence text = getText(R.string.native_service_started); 388 | 389 | // Set the icon, scrolling text and timestamp 390 | Notification notification = new Notification(R.drawable.notification, 391 | text, 392 | System.currentTimeMillis()); 393 | notification.flags |= Notification.FLAG_ONGOING_EVENT; 394 | 395 | //Intent intent = new Intent(this, NativeService.class); 396 | //intent.setAction(ACTION_NOTIFICATION); 397 | Intent intent = new Intent(this, IPsecToolsActivity.class); 398 | 399 | // The PendingIntent to launch our activity if the user selects this notification 400 | PendingIntent contentIntent = PendingIntent.getActivity(this, 0, 401 | intent, 0); 402 | 403 | // Set the info for the views that show in the notification panel. 404 | notification.setLatestEventInfo(this, getText(R.string.native_service_label), 405 | text, contentIntent); 406 | 407 | return notification; 408 | 409 | // Send the notification. 410 | //mNM.notify(NOTIFICATION, notification); 411 | } 412 | 413 | private void runRacoon() { 414 | Process process = null; 415 | 416 | try { 417 | Log.i("ipsec-tools", "Start process"); 418 | File binDir = this.getDir("bin", 0); 419 | 420 | // Kill old racoon instances 421 | NativeCommand.system("killall " + RACOON_BIN_NAME); 422 | 423 | // Remove racoon socket since we need to 424 | // detect when the socket is created 425 | File socketFile = new File(mSocketPath); 426 | if (socketFile.exists()) 427 | socketFile.delete(); 428 | 429 | // Remove racoon pid file 430 | File pidFile = new File(binDir, ConfigManager.PIDFILE); 431 | if (pidFile.exists()) 432 | pidFile.delete(); 433 | 434 | // TODO check getExternalStorageState() 435 | File ipsecDir = new File(Environment.getExternalStorageDirectory(), "ipsec"); 436 | if (!ipsecDir.exists()) { 437 | ipsecDir.mkdir(); 438 | } 439 | process = new ProcessBuilder() 440 | .command(new File(binDir, "racoon.sh").getAbsolutePath(), 441 | "-v", 442 | "-f", new File(binDir, ConfigManager.PEERS_CONFIG).getAbsolutePath(), 443 | "-l", new File(ipsecDir, "racoon.log").getAbsolutePath()) 444 | .redirectErrorStream(true) 445 | .start(); 446 | 447 | Log.i("ipsec-tools", "Process " + process.toString()); 448 | 449 | InputStream in = process.getInputStream(); 450 | OutputStream out = process.getOutputStream(); 451 | 452 | BufferedReader reader = new BufferedReader( 453 | new InputStreamReader(in), 8192); 454 | int read; 455 | char[] buffer = new char[4096]; 456 | while ((read = reader.read(buffer)) > 0) { 457 | } 458 | reader.close(); 459 | in.close(); 460 | out.close(); 461 | 462 | // Wait for racoon to create local unix domain socket and pid file 463 | for (int i=0; i < 10; i++) { 464 | if (socketFile.exists() && pidFile.exists()) 465 | break; 466 | try { 467 | Thread.sleep(500); 468 | } catch (InterruptedException e) { 469 | break; 470 | } 471 | } 472 | 473 | if (!socketFile.exists() || !pidFile.exists()) { 474 | throw new RuntimeException("Failed to start racoon, check racoon.log"); 475 | } 476 | 477 | // Read PID 478 | BufferedReader pidReader = 479 | new BufferedReader(new FileReader(pidFile), 128); 480 | String pidStr = pidReader.readLine(); 481 | pidReader.close(); 482 | if (pidStr == null) 483 | throw new RuntimeException("Failed to start racoon, check racoon.log"); 484 | mPid = Integer.parseInt(pidStr); 485 | Log.i("ipsec-tools", "racoon pid '" + mPid + "'"); 486 | 487 | Message.obtain(mHandler, HANDLER_RACOON_STARTED).sendToTarget(); 488 | } catch (IOException e) { 489 | throw new RuntimeException(e); 490 | } 491 | finally { 492 | if (process != null) 493 | process.destroy(); 494 | } 495 | } 496 | 497 | private Handler mHandler = new Handler() { 498 | @Override 499 | public void handleMessage(Message msg) { 500 | switch (msg.what) { 501 | case HANDLER_RACOON_STARTED: 502 | onRacoonStarted(); 503 | break; 504 | } 505 | } 506 | }; 507 | 508 | private Admin.OnCommandListener mListener = new Admin.OnCommandListener() { 509 | public void onCommand(Command cmd) { 510 | if (cmd instanceof Event) { 511 | Event evt = (Event)cmd; 512 | String action = null; 513 | switch (evt.getType()) { 514 | case Event.EVT_PHASE1_UP: 515 | action = ACTION_PHASE1_UP; 516 | break; 517 | case Event.EVT_PHASE1_DOWN: 518 | action = ACTION_PHASE1_DOWN; 519 | break; 520 | default: 521 | Log.i("ipsec-tools", "Unhandled event type"); 522 | break; 523 | } 524 | if (action != null) { 525 | Intent broadcastIntent = new Intent(); 526 | broadcastIntent.setAction(action); 527 | broadcastIntent.putExtra("remote_addr", evt.getPh1dst()); 528 | if (evt.getSynthetic()) { 529 | broadcastIntent.putExtra("synthetic", true); 530 | } 531 | sendBroadcast(broadcastIntent); 532 | } 533 | } else { 534 | Log.i("ipsec-tools", "Unhandled command " + cmd); 535 | } 536 | 537 | Log.i("ipsec-tools", "Command received " + cmd); 538 | } 539 | }; 540 | 541 | private void onRacoonStarted() { 542 | try { 543 | mAdmin.setOnCommandListener(mListener); 544 | mAdmin.showEvt(); 545 | 546 | Intent broadcastIntent = new Intent(); 547 | broadcastIntent.setAction(ACTION_SERVICE_READY); 548 | sendBroadcast(broadcastIntent); 549 | } catch (IOException e) { 550 | throw new RuntimeException(e); 551 | } 552 | } 553 | 554 | public void flushAllSAD() { 555 | File binDir = getDir("bin", Context.MODE_PRIVATE); 556 | NativeCommand.system(new File(binDir, SETKEY_EXEC_NAME).getAbsolutePath() + 557 | " -F"); 558 | } 559 | 560 | public void flushAllSPD() { 561 | File binDir = getDir("bin", Context.MODE_PRIVATE); 562 | NativeCommand.system(new File(binDir, SETKEY_EXEC_NAME).getAbsolutePath() + 563 | " -FP"); 564 | flushAllSAD(); 565 | } 566 | 567 | public void runSetKey() { 568 | File binDir = getDir("bin", Context.MODE_PRIVATE); 569 | NativeCommand.system(new File(binDir, SETKEY_EXEC_NAME).getAbsolutePath() + 570 | " -f " + new File(binDir, ConfigManager.SETKEY_CONFIG).getAbsolutePath()); 571 | } 572 | 573 | } 574 | -------------------------------------------------------------------------------- /src/org/za/hem/ipsec_tools/service/PolicyFile.java: -------------------------------------------------------------------------------- 1 | package org.za.hem.ipsec_tools.service; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.jar.Attributes; 7 | import java.util.jar.JarFile; 8 | 9 | /** 10 | * Policy jar file containing setkey.conf and racoon.conf 11 | * @author mikael 12 | * 13 | */ 14 | public class PolicyFile extends JarFile { 15 | public static final String ATTR_NAME = "Name"; 16 | 17 | public static final String SETKEY_NAME = "setkey.conf"; 18 | public static final String RACOON_NAME = "racoon.conf"; 19 | 20 | public PolicyFile(File file) throws IOException { 21 | super(file); 22 | } 23 | 24 | public PolicyFile(String filename) throws IOException { 25 | super(filename); 26 | } 27 | 28 | public PolicyFile(File file, boolean verify) throws IOException { 29 | super(file, verify); 30 | } 31 | 32 | public PolicyFile(String filename, boolean verify) throws IOException { 33 | super(filename, verify); 34 | } 35 | 36 | public PolicyFile(File file, boolean verify, int mode) throws IOException { 37 | super(file, verify, mode); 38 | } 39 | 40 | public String getPolicyName() { 41 | try { 42 | Attributes attrs = getManifest().getMainAttributes(); 43 | if (attrs.containsKey(ATTR_NAME)) 44 | return attrs.getValue(ATTR_NAME); 45 | } catch (IOException e) { 46 | } 47 | // No Name attr found, return file name 48 | return getName(); 49 | } 50 | 51 | public InputStream getRacoonConfStream() throws IOException { 52 | return getInputStream(getJarEntry(RACOON_NAME)); 53 | } 54 | 55 | public InputStream getSetkeyConfStream() throws IOException { 56 | return getInputStream(getJarEntry(SETKEY_NAME)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tools/mkpolicy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | jar -c -f policy.zip -m manifest.mf racoon.conf setkey.conf 4 | 5 | --------------------------------------------------------------------------------