diff options
Diffstat (limited to 'app/src/main')
334 files changed, 60950 insertions, 0 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5fbee63 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright 2007 Kenny Root, Jeffrey Sharkey + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.connectbot" + android:versionName="1.8.2" + android:versionCode="372" + android:installLocation="auto"> + + <uses-sdk android:targetSdkVersion="15" android:minSdkVersion="4" /> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.VIBRATE" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + + <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> + + <supports-screens /> + + <application + android:icon="@drawable/icon" + android:label="@string/app_name" + android:description="@string/app_desc" + android:allowBackup="true" + android:backupAgent=".service.BackupAgent" + android:killAfterRestore="true"> + + <activity android:name=".HostListActivity" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.CREATE_SHORTCUT" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.PICK" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="ssh" /> + <data android:scheme="telnet" /> + <data android:scheme="local" /> + </intent-filter> + </activity> + + <activity android:name=".PubkeyListActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".GeneratePubkeyActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".HostEditorActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".PortForwardListActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".SettingsActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".WizardActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".HelpActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".HelpTopicActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".ColorsActivity" android:configChanges="keyboardHidden|orientation" /> + + <service android:name="org.connectbot.service.TerminalManager" + android:configChanges="keyboardHidden|orientation" + android:description="@string/service_desc" /> + + <activity android:name=".ConsoleActivity" android:configChanges="keyboardHidden|orientation" + android:theme="@style/NoTitle" android:windowSoftInputMode="stateAlwaysVisible|adjustResize" + android:launchMode="singleTop" android:hardwareAccelerated="false"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="ssh" /> + <data android:scheme="telnet" /> + <data android:scheme="local" /> + <!-- format: ssh://user@host:port/#nickname --> + <!-- format: telnet://host:port/#nickname --> + <!-- format: local:// --> + </intent-filter> + </activity> + + <meta-data android:name="com.google.android.backup.api_key" + android:value="AEdPqrEAAAAIDlFz9nSUr2g0gSytW0t2cNnYAGHDkptlVohsBA" /> + + </application> +</manifest> diff --git a/app/src/main/assets/help/Hints.html b/app/src/main/assets/help/Hints.html new file mode 100644 index 0000000..37583db --- /dev/null +++ b/app/src/main/assets/help/Hints.html @@ -0,0 +1,13 @@ +<html> +<body style="background-color: #000; color: #fff"> + +<h2>Helpful hints</h2> + +<p>When you have multiple sessions open, you can 'pan' between them by swiping your finger left-to-right or right-to-left over the screen.</p> + +<p>Long-press on your Android desktop to create direct shortcuts to frequently-used SSH hosts.</p> + +<p>Slide your finger up/down on the right-half of the terminal screen to look at the scrollback history. Slide up/down on the left-half to send the page up/down keys.</p> + +</body> +</html>
\ No newline at end of file diff --git a/app/src/main/assets/help/PhysicalKeyboard.html b/app/src/main/assets/help/PhysicalKeyboard.html new file mode 100644 index 0000000..4ff3753 --- /dev/null +++ b/app/src/main/assets/help/PhysicalKeyboard.html @@ -0,0 +1,62 @@ +<html> +<body style="background-color: #000; color: #fff"> + +<p><img + src="http://connectbot.googlecode.com/svn/trunk/www/keyboard.jpg" /></p> +<p>Here are some keyboard shortcuts available when a <strong>hardware +keyboard</strong> is present. If you're using a phone where the main input +type is a <strong>virtual keyboard</strong>, please see the VirtualKeyboard help topic. +</p> +<p><strong>Note:</strong> the side that <strong>shift</strong>, <strong>alt</strong>, +<strong>slash</strong>, and <strong>tab</strong> uses can be changed in +preferences between left, right, and disabled.</p> +<ul> + <li>Control key (CTRL)</li> + <blockquote>Pressing once on the trackball will toggle on + <strong>control</strong> for the next character typed. The cursor will + indicate this state with a < symbol. Note that pressing the + trackball again will send an <strong>escape</strong> key.</blockquote> +</ul> +<ul> + <li>Escape (ESC)</li> + <blockquote>Pressing twice on the trackball will send <strong>escape</strong> + key. Note that some other terminal emulators map pressing <strong>ALT-<i>key</i></strong> + to <strong>escape + <i>key</i></strong>.</blockquote> +</ul> +<ul> + <li>Shift</li> + <blockquote>Pressing the <strong>shift</strong> (up arrow) + key once will make the next key typed its uppercase variant according + to the keyboard layout. This state is indicated with an outline of a + triangle on the top of the cursor. Pressing it twice will turn on <strong>shift + lock</strong> which is indicated by a solid triangle on the top of the cursor.</blockquote> +</ul> +<ul> + <li>Alt</li> + <blockquote>Pressing the <strong>Alt</strong> key once + will make the next key typed its symbol as indicated on the keyboard. + This state is indicated with the outline of a triangle on the bottom of + the cursor. Pressing it twice will turn on <strong>alt lock</strong> + which is indicated by a solid triangle on the bottom of the cursor.</blockquote> +</ul> +<ul> + <li>Slash (opposite side Alt)</li> + <blockquote>The opposite side <strong>alt</strong> key can + be used as a shortcut for the forward slash / character. This aids in + quickly typing directories on the G1.</blockquote> +</ul> +<ul> + <li>Tab (opposite side Shift)</li> + <blockquote>The opposite side <strong>shift</strong> key + can be used as a shortcut for the <strong>tab</strong> key (CTRL-i) for + quick completion in many shells.</blockquote> +</ul> +<ul> + <li>Function keys (F1 through F10)</li> + <blockquote>Hold down the shift key and press numbers 1 + through 10 to send F1 through F10 respectively.</blockquote> +</ul> + + +</body> +</html> diff --git a/app/src/main/assets/help/ScreenGestures.html b/app/src/main/assets/help/ScreenGestures.html new file mode 100644 index 0000000..9332821 --- /dev/null +++ b/app/src/main/assets/help/ScreenGestures.html @@ -0,0 +1,38 @@ +<html> +<body style="background-color: #000; color: #fff"> + +<p>Gestures in ConnectBot allow a user to do several things for +which there's no keyboard equivalent. If the gestures seem +backward, then imagine that you're grabbing the text and moving it +with your finger.</p> +<h1><a name="Page_Up_/_Page_Down" />Page Up / Page Down</h1> +<p>Swiping your finger up and down on the left third of the screen +will send a page up and page down key to the remote host. Many programs +map this to scrolling back into history such as irssi or tinyfugue.</p> +<p><img + src="http://connectbot.googlecode.com/svn/trunk/www/gesture-pgup.png" /> +Page Up gesture</p> +<p><img + src="http://connectbot.googlecode.com/svn/trunk/www/gesture-pgdn.png" /> +Page Down gesture</p> +<h1><a name="Scroll_back_/_Scroll_forward" />Scroll back / Scroll +forward</h1> +<p>Swiping your finger up on the right side of the screen allows you +to scroll backward and forward in the local terminal buffer history.</p> +<p><img + src="http://connectbot.googlecode.com/svn/trunk/www/gesture-scrollback.png" /> +Scroll back gesture</p> +<p><img + src="http://connectbot.googlecode.com/svn/trunk/www/gesture-scrollforward.png" /> +Scroll forward gesture</p> +<h1><a name="Switching_hosts" />Switching hosts</h1> +<p>Swiping your finger from one side of the screen to the other will +switch between currently connected hosts.</p> +<p><img + src="http://connectbot.googlecode.com/svn/trunk/www/gesture-hostprev.png" /> +Previous host gesture</p> +<p><img + src="http://connectbot.googlecode.com/svn/trunk/www/gesture-hostnext.png" /> +Next host gesture</p> +</body> +</html>
\ No newline at end of file diff --git a/app/src/main/assets/help/VirtualKeyboard.html b/app/src/main/assets/help/VirtualKeyboard.html new file mode 100644 index 0000000..9788161 --- /dev/null +++ b/app/src/main/assets/help/VirtualKeyboard.html @@ -0,0 +1,49 @@ +<html> +<body style="background-color: #000; color: #fff"> + +<p><img src="http://connectbot.googlecode.com/svn/trunk/www/magic-cb-screen.png" width="100%" /></p> + +<h2>Caveats</h2> +<p>Since ConnectBot doesn't use any of the normal TextView +widgets, Android's IME structure isn't designed to directly +support it.</p> +<p>The best way to use Android with a virtual keyboard is in <strong>Portrait</strong> +mode. By default, ConnectBot is set to use <strong>Portrait</strong> +mode when no hardware keyboard is present. To change this setting, go to +<strong>Preferences</strong> from the <strong>Host List</strong>.</p> +<p>In <strong>Landscape</strong> mode, the Android virtual keyboard +(or other IMEs) will take up the entire screen. Android provides no way +for ConnectBot to resize the terminal view in <strong>Landscape</strong>. +However, you may use a <i>work-around</i>: <strong>Force Resize</strong> +to fit above the virtual keyboard if desired.</p> +<p>On devices without a hardware keyboard, you may press and hold +the <strong>MENU</strong> button to bring up the virtual keyboard. NOTE: +This applies to any program on the Android platform; it is not +ConnectBot specific.</p> +<h2>How to Enter Control, Alt, Escape, and Function Keys</h2> +<p>You can enter any key combination with ConnectBot and the virtual +keyboard, but you must know how keys are mapped on a normal console. For +instance, usually combinations of ALT+letter on a PC keyboard are +actually mapped to sending, sequentially, ESC key then the letter.</p> +<p>Note there are also screen gestures: see the ScreenGestures help topic.</a> +for <strong>Page Up</strong> and <strong>Page Down</strong>.</p> +<ul> + <li>Trackball: 1 press is <strong>CTRL</strong>, 2 presses sends <strong>ESC</strong> + </li> + <li><strong>Tab key</strong> = <strong>CTRL + i</strong></li> + <li><strong>Function key</strong> = <strong>CTRL + number</strong> + </li> +</ul> +<h2>Examples</h2> +<ul> + <li><strong>ESC</strong> = Press the trackball twice.</li> + <li><strong>ALT + Right Arrow</strong> = Press trackball twice + then move trackball to right.</li> + <li><strong>CTRL + A</strong> = Press trackball once then tap the + "A" key on the soft keyboard.</li> + <li><strong>F3</strong> = Press trackball once then tap the + "3" key on the soft keyboard.</li> +</ul> + +</body> +</html> diff --git a/app/src/main/java/com/google/ase/Exec.java b/app/src/main/java/com/google/ase/Exec.java new file mode 100644 index 0000000..016fdf3 --- /dev/null +++ b/app/src/main/java/com/google/ase/Exec.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.ase; + +import java.io.FileDescriptor; + +/** + * Tools for executing commands. + */ +public class Exec { + /** + * @param cmd + * The command to execute + * @param arg0 + * The first argument to the command, may be null + * @param arg1 + * the second argument to the command, may be null + * @return the file descriptor of the started process. + * + */ + public static FileDescriptor createSubprocess(String cmd, String arg0, String arg1) { + return createSubprocess(cmd, arg0, arg1, null); + } + + /** + * @param cmd + * The command to execute + * @param arg0 + * The first argument to the command, may be null + * @param arg1 + * the second argument to the command, may be null + * @param processId + * A one-element array to which the process ID of the started process will be written. + * @return the file descriptor of the started process. + * + */ + public static native FileDescriptor createSubprocess(String cmd, String arg0, String arg1, + int[] processId); + + public static native void setPtyWindowSize(FileDescriptor fd, int row, int col, int xpixel, + int ypixel); + + /** + * Causes the calling thread to wait for the process associated with the receiver to finish + * executing. + * + * @return The exit value of the Process being waited on + * + */ + public static native int waitFor(int processId); + + static { + System.loadLibrary("com_google_ase_Exec"); + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/Adler32.java b/app/src/main/java/com/jcraft/jzlib/Adler32.java new file mode 100644 index 0000000..d8b6ef8 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/Adler32.java @@ -0,0 +1,94 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class Adler32{ + + // largest prime smaller than 65536 + static final private int BASE=65521; + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + static final private int NMAX=5552; + + long adler32(long adler, byte[] buf, int index, int len){ + if(buf == null){ return 1L; } + + long s1=adler&0xffff; + long s2=(adler>>16)&0xffff; + int k; + + while(len > 0) { + k=len<NMAX?len:NMAX; + len-=k; + while(k>=16){ + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + s1+=buf[index++]&0xff; s2+=s1; + k-=16; + } + if(k!=0){ + do{ + s1+=buf[index++]&0xff; s2+=s1; + } + while(--k!=0); + } + s1%=BASE; + s2%=BASE; + } + return (s2<<16)|s1; + } + + /* + private java.util.zip.Adler32 adler=new java.util.zip.Adler32(); + long adler32(long value, byte[] buf, int index, int len){ + if(value==1) {adler.reset();} + if(buf==null) {adler.reset();} + else{adler.update(buf, index, len);} + return adler.getValue(); + } + */ +} diff --git a/app/src/main/java/com/jcraft/jzlib/Deflate.java b/app/src/main/java/com/jcraft/jzlib/Deflate.java new file mode 100644 index 0000000..9978802 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/Deflate.java @@ -0,0 +1,1623 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public +final class Deflate{ + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_DEFAULT_COMPRESSION=-1; + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_MEM_LEVEL=8; + + static class Config{ + int good_length; // reduce lazy search above this match length + int max_lazy; // do not perform lazy search above this match length + int nice_length; // quit search above this match length + int max_chain; + int func; + Config(int good_length, int max_lazy, + int nice_length, int max_chain, int func){ + this.good_length=good_length; + this.max_lazy=max_lazy; + this.nice_length=nice_length; + this.max_chain=max_chain; + this.func=func; + } + } + + static final private int STORED=0; + static final private int FAST=1; + static final private int SLOW=2; + static final private Config[] config_table; + static{ + config_table=new Config[10]; + // good lazy nice chain + config_table[0]=new Config(0, 0, 0, 0, STORED); + config_table[1]=new Config(4, 4, 8, 4, FAST); + config_table[2]=new Config(4, 5, 16, 8, FAST); + config_table[3]=new Config(4, 6, 32, 32, FAST); + + config_table[4]=new Config(4, 4, 16, 16, SLOW); + config_table[5]=new Config(8, 16, 32, 32, SLOW); + config_table[6]=new Config(8, 16, 128, 128, SLOW); + config_table[7]=new Config(8, 32, 128, 256, SLOW); + config_table[8]=new Config(32, 128, 258, 1024, SLOW); + config_table[9]=new Config(32, 258, 258, 4096, SLOW); + } + + static final private String[] z_errmsg = { + "need dictionary", // Z_NEED_DICT 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "file error", // Z_ERRNO (-1) + "stream error", // Z_STREAM_ERROR (-2) + "data error", // Z_DATA_ERROR (-3) + "insufficient memory", // Z_MEM_ERROR (-4) + "buffer error", // Z_BUF_ERROR (-5) + "incompatible version",// Z_VERSION_ERROR (-6) + "" + }; + + // block not completed, need more input or more output + static final private int NeedMore=0; + + // block flush performed + static final private int BlockDone=1; + + // finish started, need only more output at next deflate + static final private int FinishStarted=2; + + // finish done, accept no more input or output + static final private int FinishDone=3; + + // preset dictionary flag in zlib header + static final private int PRESET_DICT=0x20; + + static final private int Z_FILTERED=1; + static final private int Z_HUFFMAN_ONLY=2; + static final private int Z_DEFAULT_STRATEGY=0; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final private int INIT_STATE=42; + static final private int BUSY_STATE=113; + static final private int FINISH_STATE=666; + + // The deflate compression method + static final private int Z_DEFLATED=8; + + static final private int STORED_BLOCK=0; + static final private int STATIC_TREES=1; + static final private int DYN_TREES=2; + + // The three kinds of block type + static final private int Z_BINARY=0; + static final private int Z_ASCII=1; + static final private int Z_UNKNOWN=2; + + static final private int Buf_size=8*2; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + static final private int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + static final private int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + static final private int REPZ_11_138=18; + + static final private int MIN_MATCH=3; + static final private int MAX_MATCH=258; + static final private int MIN_LOOKAHEAD=(MAX_MATCH+MIN_MATCH+1); + + static final private int MAX_BITS=15; + static final private int D_CODES=30; + static final private int BL_CODES=19; + static final private int LENGTH_CODES=29; + static final private int LITERALS=256; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + static final private int HEAP_SIZE=(2*L_CODES+1); + + static final private int END_BLOCK=256; + + ZStream strm; // pointer back to this zlib stream + int status; // as the name implies + byte[] pending_buf; // output still pending + int pending_buf_size; // size of pending_buf + int pending_out; // next pending byte to output to the stream + int pending; // nb of bytes in the pending buffer + int noheader; // suppress zlib header and adler32 + byte data_type; // UNKNOWN, BINARY or ASCII + byte method; // STORED (for zip only) or DEFLATED + int last_flush; // value of flush param for previous deflate call + + int w_size; // LZ77 window size (32K by default) + int w_bits; // log2(w_size) (8..16) + int w_mask; // w_size - 1 + + byte[] window; + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + + int window_size; + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + short[] prev; + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + short[] head; // Heads of the hash chains or NIL. + + int ins_h; // hash index of string to be inserted + int hash_size; // number of elements in hash table + int hash_bits; // log2(hash_size) + int hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + int hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + int block_start; + + int match_length; // length of best match + int prev_match; // previous match + int match_available; // set if previous match exists + int strstart; // start of string to insert + int match_start; // start of matching string + int lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + int prev_length; + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + int max_chain_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression + // levels >= 4. + int max_lazy_match; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + int level; // compression level (1..9) + int strategy; // favor or force Huffman coding + + // Use a faster search when the previous match is longer than this + int good_match; + + // Stop searching when current match exceeds this + int nice_match; + + short[] dyn_ltree; // literal and length tree + short[] dyn_dtree; // distance tree + short[] bl_tree; // Huffman tree for bit lengths + + Tree l_desc=new Tree(); // desc for literal tree + Tree d_desc=new Tree(); // desc for distance tree + Tree bl_desc=new Tree(); // desc for bit length tree + + // number of codes at each bit length for an optimal tree + short[] bl_count=new short[MAX_BITS+1]; + + // heap used to build the Huffman trees + int[] heap=new int[2*L_CODES+1]; + + int heap_len; // number of elements in the heap + int heap_max; // element of largest frequency + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + byte[] depth=new byte[2*L_CODES+1]; + + int l_buf; // index for literals or lengths */ + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + // - I can't count above 4 + int lit_bufsize; + + int last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + int d_buf; // index of pendig_buf + + int opt_len; // bit length of current block with optimal trees + int static_len; // bit length of current block with static trees + int matches; // number of string matches in current block + int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + short bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + int bi_valid; + + Deflate(){ + dyn_ltree=new short[HEAP_SIZE*2]; + dyn_dtree=new short[(2*D_CODES+1)*2]; // distance tree + bl_tree=new short[(2*BL_CODES+1)*2]; // Huffman tree for bit lengths + } + + void lm_init() { + window_size=2*w_size; + + head[hash_size-1]=0; + for(int i=0; i<hash_size-1; i++){ + head[i]=0; + } + + // Set the default configuration parameters: + max_lazy_match = Deflate.config_table[level].max_lazy; + good_match = Deflate.config_table[level].good_length; + nice_match = Deflate.config_table[level].nice_length; + max_chain_length = Deflate.config_table[level].max_chain; + + strstart = 0; + block_start = 0; + lookahead = 0; + match_length = prev_length = MIN_MATCH-1; + match_available = 0; + ins_h = 0; + } + + // Initialize the tree data structures for a new zlib stream. + void tr_init(){ + + l_desc.dyn_tree = dyn_ltree; + l_desc.stat_desc = StaticTree.static_l_desc; + + d_desc.dyn_tree = dyn_dtree; + d_desc.stat_desc = StaticTree.static_d_desc; + + bl_desc.dyn_tree = bl_tree; + bl_desc.stat_desc = StaticTree.static_bl_desc; + + bi_buf = 0; + bi_valid = 0; + last_eob_len = 8; // enough lookahead for inflate + + // Initialize the first block of the first file: + init_block(); + } + + void init_block(){ + // Initialize the trees. + for(int i = 0; i < L_CODES; i++) dyn_ltree[i*2] = 0; + for(int i= 0; i < D_CODES; i++) dyn_dtree[i*2] = 0; + for(int i= 0; i < BL_CODES; i++) bl_tree[i*2] = 0; + + dyn_ltree[END_BLOCK*2] = 1; + opt_len = static_len = 0; + last_lit = matches = 0; + } + + // Restore the heap property by moving down the tree starting at node k, + // exchanging a node with the smallest of its two sons if necessary, stopping + // when the heap property is re-established (each father smaller than its + // two sons). + void pqdownheap(short[] tree, // the tree to restore + int k // node to move down + ){ + int v = heap[k]; + int j = k << 1; // left son of k + while (j <= heap_len) { + // Set j to the smallest of the two sons: + if (j < heap_len && + smaller(tree, heap[j+1], heap[j], depth)){ + j++; + } + // Exit if v is smaller than both sons + if(smaller(tree, v, heap[j], depth)) break; + + // Exchange v with the smallest son + heap[k]=heap[j]; k = j; + // And continue down the tree, setting j to the left son of k + j <<= 1; + } + heap[k] = v; + } + + static boolean smaller(short[] tree, int n, int m, byte[] depth){ + short tn2=tree[n*2]; + short tm2=tree[m*2]; + return (tn2<tm2 || + (tn2==tm2 && depth[n] <= depth[m])); + } + + // Scan a literal or distance tree to determine the frequencies of the codes + // in the bit length tree. + void scan_tree (short[] tree,// the tree to be scanned + int max_code // and its largest code of non zero frequency + ){ + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[0*2+1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0){ max_count = 138; min_count = 3; } + tree[(max_code+1)*2+1] = (short)0xffff; // guard + + for(n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[(n+1)*2+1]; + if(++count < max_count && curlen == nextlen) { + continue; + } + else if(count < min_count) { + bl_tree[curlen*2] += count; + } + else if(curlen != 0) { + if(curlen != prevlen) bl_tree[curlen*2]++; + bl_tree[REP_3_6*2]++; + } + else if(count <= 10) { + bl_tree[REPZ_3_10*2]++; + } + else{ + bl_tree[REPZ_11_138*2]++; + } + count = 0; prevlen = curlen; + if(nextlen == 0) { + max_count = 138; min_count = 3; + } + else if(curlen == nextlen) { + max_count = 6; min_count = 3; + } + else{ + max_count = 7; min_count = 4; + } + } + } + + // Construct the Huffman tree for the bit lengths and return the index in + // bl_order of the last bit length code to send. + int build_bl_tree(){ + int max_blindex; // index of last bit length code of non zero freq + + // Determine the bit length frequencies for literal and distance trees + scan_tree(dyn_ltree, l_desc.max_code); + scan_tree(dyn_dtree, d_desc.max_code); + + // Build the bit length tree: + bl_desc.build_tree(this); + // opt_len now includes the length of the tree representations, except + // the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + + // Determine the number of bit length codes to send. The pkzip format + // requires that at least 4 bit length codes be sent. (appnote.txt says + // 3 but the actual value used is 4.) + for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { + if (bl_tree[Tree.bl_order[max_blindex]*2+1] != 0) break; + } + // Update opt_len to include the bit length tree and counts + opt_len += 3*(max_blindex+1) + 5+5+4; + + return max_blindex; + } + + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + void send_all_trees(int lcodes, int dcodes, int blcodes){ + int rank; // index in bl_order + + send_bits(lcodes-257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes-1, 5); + send_bits(blcodes-4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) { + send_bits(bl_tree[Tree.bl_order[rank]*2+1], 3); + } + send_tree(dyn_ltree, lcodes-1); // literal tree + send_tree(dyn_dtree, dcodes-1); // distance tree + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + void send_tree (short[] tree,// the tree to be sent + int max_code // and its largest code of non zero frequency + ){ + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[0*2+1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0){ max_count = 138; min_count = 3; } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[(n+1)*2+1]; + if(++count < max_count && curlen == nextlen) { + continue; + } + else if(count < min_count) { + do { send_code(curlen, bl_tree); } while (--count != 0); + } + else if(curlen != 0){ + if(curlen != prevlen){ + send_code(curlen, bl_tree); count--; + } + send_code(REP_3_6, bl_tree); + send_bits(count-3, 2); + } + else if(count <= 10){ + send_code(REPZ_3_10, bl_tree); + send_bits(count-3, 3); + } + else{ + send_code(REPZ_11_138, bl_tree); + send_bits(count-11, 7); + } + count = 0; prevlen = curlen; + if(nextlen == 0){ + max_count = 138; min_count = 3; + } + else if(curlen == nextlen){ + max_count = 6; min_count = 3; + } + else{ + max_count = 7; min_count = 4; + } + } + } + + // Output a byte on the stream. + // IN assertion: there is enough room in pending_buf. + final void put_byte(byte[] p, int start, int len){ + System.arraycopy(p, start, pending_buf, pending, len); + pending+=len; + } + + final void put_byte(byte c){ + pending_buf[pending++]=c; + } + final void put_short(int w) { + put_byte((byte)(w/*&0xff*/)); + put_byte((byte)(w>>>8)); + } + final void putShortMSB(int b){ + put_byte((byte)(b>>8)); + put_byte((byte)(b/*&0xff*/)); + } + + final void send_code(int c, short[] tree){ + int c2=c*2; + send_bits((tree[c2]&0xffff), (tree[c2+1]&0xffff)); + } + + void send_bits(int value, int length){ + int len = length; + if (bi_valid > (int)Buf_size - len) { + int val = value; +// bi_buf |= (val << bi_valid); + bi_buf |= ((val << bi_valid)&0xffff); + put_short(bi_buf); + bi_buf = (short)(val >>> (Buf_size - bi_valid)); + bi_valid += len - Buf_size; + } else { +// bi_buf |= (value) << bi_valid; + bi_buf |= (((value) << bi_valid)&0xffff); + bi_valid += len; + } + } + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + void _tr_align(){ + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) { + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + bi_flush(); + } + last_eob_len = 7; + } + + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + boolean _tr_tally (int dist, // distance of matched string + int lc // match length-MIN_MATCH or unmatched char (if dist==0) + ){ + + pending_buf[d_buf+last_lit*2] = (byte)(dist>>>8); + pending_buf[d_buf+last_lit*2+1] = (byte)dist; + + pending_buf[l_buf+last_lit] = (byte)lc; last_lit++; + + if (dist == 0) { + // lc is the unmatched char + dyn_ltree[lc*2]++; + } + else { + matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree._length_code[lc]+LITERALS+1)*2]++; + dyn_dtree[Tree.d_code(dist)*2]++; + } + + if ((last_lit & 0x1fff) == 0 && level > 2) { + // Compute an upper bound for the compressed length + int out_length = last_lit*8; + int in_length = strstart - block_start; + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (int)dyn_dtree[dcode*2] * + (5L+Tree.extra_dbits[dcode]); + } + out_length >>>= 3; + if ((matches < (last_lit/2)) && out_length < in_length/2) return true; + } + + return (last_lit == lit_bufsize-1); + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // Send the block data compressed using the given Huffman trees + void compress_block(short[] ltree, short[] dtree){ + int dist; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + int lx = 0; // running index in l_buf + int code; // the code to send + int extra; // number of extra bits to send + + if (last_lit != 0){ + do{ + dist=((pending_buf[d_buf+lx*2]<<8)&0xff00)| + (pending_buf[d_buf+lx*2+1]&0xff); + lc=(pending_buf[l_buf+lx])&0xff; lx++; + + if(dist == 0){ + send_code(lc, ltree); // send a literal byte + } + else{ + // Here, lc is the match length - MIN_MATCH + code = Tree._length_code[lc]; + + send_code(code+LITERALS+1, ltree); // send the length code + extra = Tree.extra_lbits[code]; + if(extra != 0){ + lc -= Tree.base_length[code]; + send_bits(lc, extra); // send the extra length bits + } + dist--; // dist is now the match distance - 1 + code = Tree.d_code(dist); + + send_code(code, dtree); // send the distance code + extra = Tree.extra_dbits[code]; + if (extra != 0) { + dist -= Tree.base_dist[code]; + send_bits(dist, extra); // send the extra distance bits + } + } // literal or match pair ? + + // Check that the overlay between pending_buf and d_buf+l_buf is ok: + } + while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[END_BLOCK*2+1]; + } + + // Set the data type to ASCII or BINARY, using a crude approximation: + // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + // IN assertion: the fields freq of dyn_ltree are set and the total of all + // frequencies does not exceed 64K (to fit in an int on 16 bit machines). + void set_data_type(){ + int n = 0; + int ascii_freq = 0; + int bin_freq = 0; + while(n<7){ bin_freq += dyn_ltree[n*2]; n++;} + while(n<128){ ascii_freq += dyn_ltree[n*2]; n++;} + while(n<LITERALS){ bin_freq += dyn_ltree[n*2]; n++;} + data_type=(byte)(bin_freq > (ascii_freq >>> 2) ? Z_BINARY : Z_ASCII); + } + + // Flush the bit buffer, keeping at most 7 bits in it. + void bi_flush(){ + if (bi_valid == 16) { + put_short(bi_buf); + bi_buf=0; + bi_valid=0; + } + else if (bi_valid >= 8) { + put_byte((byte)bi_buf); + bi_buf>>>=8; + bi_valid-=8; + } + } + + // Flush the bit buffer and align the output on a byte boundary + void bi_windup(){ + if (bi_valid > 8) { + put_short(bi_buf); + } else if (bi_valid > 0) { + put_byte((byte)bi_buf); + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + void copy_block(int buf, // the input data + int len, // its length + boolean header // true if block header must be written + ){ + int index=0; + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) { + put_short((short)len); + put_short((short)~len); + } + + // while(len--!=0) { + // put_byte(window[buf+index]); + // index++; + // } + put_byte(window, buf, len); + } + + void flush_block_only(boolean eof){ + _tr_flush_block(block_start>=0 ? block_start : -1, + strstart-block_start, + eof); + block_start=strstart; + strm.flush_pending(); + } + + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + int deflate_stored(int flush){ + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + int max_block_size = 0xffff; + int max_start; + + if(max_block_size > pending_buf_size - 5) { + max_block_size = pending_buf_size - 5; + } + + // Copy as much as possible from input to output: + while(true){ + // Fill the window as much as possible: + if(lookahead<=1){ + fill_window(); + if(lookahead==0 && flush==Z_NO_FLUSH) return NeedMore; + if(lookahead==0) break; // flush the current block + } + + strstart+=lookahead; + lookahead=0; + + // Emit a stored block if pending_buf will be full: + max_start=block_start+max_block_size; + if(strstart==0|| strstart>=max_start) { + // strstart == 0 is possible when wraparound on 16-bit machine + lookahead = (int)(strstart-max_start); + strstart = (int)max_start; + + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if(strstart-block_start >= w_size-MIN_LOOKAHEAD) { + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if(strm.avail_out==0) + return (flush == Z_FINISH) ? FinishStarted : NeedMore; + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + // Send a stored block + void _tr_stored_block(int buf, // input block + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ){ + send_bits((STORED_BLOCK<<1)+(eof?1:0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + void _tr_flush_block(int buf, // input block, or NULL if too old + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ) { + int opt_lenb, static_lenb;// opt_len and static_len in bytes + int max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if(level > 0) { + // Check if the file is ascii or binary + if(data_type == Z_UNKNOWN) set_data_type(); + + // Construct the literal and distance trees + l_desc.build_tree(this); + + d_desc.build_tree(this); + + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex=build_bl_tree(); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb=(opt_len+3+7)>>>3; + static_lenb=(static_len+3+7)>>>3; + + if(static_lenb<=opt_lenb) opt_lenb=static_lenb; + } + else { + opt_lenb=static_lenb=stored_len+5; // force a stored block + } + + if(stored_len+4<=opt_lenb && buf != -1){ + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } + else if(static_lenb == opt_lenb){ + send_bits((STATIC_TREES<<1)+(eof?1:0), 3); + compress_block(StaticTree.static_ltree, StaticTree.static_dtree); + } + else{ + send_bits((DYN_TREES<<1)+(eof?1:0), 3); + send_all_trees(l_desc.max_code+1, d_desc.max_code+1, max_blindex+1); + compress_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + init_block(); + + if(eof){ + bi_windup(); + } + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in == 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + void fill_window(){ + int n, m; + int p; + int more; // Amount of free space at the end of the window. + + do{ + more = (window_size-lookahead-strstart); + + // Deal with !@#$% 64K limit: + if(more==0 && strstart==0 && lookahead==0){ + more = w_size; + } + else if(more==-1) { + // Very unlikely, but possible on 16 bit machine if strstart == 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + } + else if(strstart >= w_size+ w_size-MIN_LOOKAHEAD) { + System.arraycopy(window, w_size, window, 0, w_size); + match_start-=w_size; + strstart-=w_size; // we now have strstart >= MAX_DIST + block_start-=w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p=n; + do { + m = (head[--p]&0xffff); + head[p]=(m>=w_size ? (short)(m-w_size) : 0); + } + while (--n != 0); + + n = w_size; + p = n; + do { + m = (prev[--p]&0xffff); + prev[p] = (m >= w_size ? (short)(m-w_size) : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } + while (--n!=0); + more += w_size; + } + + if (strm.avail_in == 0) return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = strm.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if(lookahead >= MIN_MATCH) { + ins_h = window[strstart]&0xff; + ins_h=(((ins_h)<<hash_shift)^(window[strstart+1]&0xff))&hash_mask; + } + // If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + // but this is not important since only literal bytes will be emitted. + } + while (lookahead < MIN_LOOKAHEAD && strm.avail_in != 0); + } + + // Compress as much as possible from the input stream, return the current + // block state. + // This function does not perform lazy evaluation of matches and inserts + // new strings in the dictionary only for unmatched strings or for short + // matches. It is used only for the fast compression options. + int deflate_fast(int flush){ +// short hash_head = 0; // head of the hash chain + int hash_head = 0; // head of the hash chain + boolean bflush; // set if current block must be flushed + + while(true){ + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + if(lookahead < MIN_LOOKAHEAD){ + fill_window(); + if(lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH){ + return NeedMore; + } + if(lookahead == 0) break; // flush the current block + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + if(lookahead >= MIN_MATCH){ + ins_h=(((ins_h)<<hash_shift)^(window[(strstart)+(MIN_MATCH-1)]&0xff))&hash_mask; + +// prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head=(head[ins_h]&0xffff); + prev[strstart&w_mask]=head[ins_h]; + head[ins_h]=(short)strstart; + } + + // Find the longest match, discarding those <= prev_length. + // At this point we have always match_length < MIN_MATCH + + if(hash_head!=0L && + ((strstart-hash_head)&0xffff) <= w_size-MIN_LOOKAHEAD + ){ + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + if(strategy != Z_HUFFMAN_ONLY){ + match_length=longest_match (hash_head); + } + // longest_match() sets match_start + } + if(match_length>=MIN_MATCH){ + // check_match(strstart, match_start, match_length); + + bflush=_tr_tally(strstart-match_start, match_length-MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if(match_length <= max_lazy_match && + lookahead >= MIN_MATCH) { + match_length--; // string at strstart already in hash table + do{ + strstart++; + + ins_h=((ins_h<<hash_shift)^(window[(strstart)+(MIN_MATCH-1)]&0xff))&hash_mask; +// prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head=(head[ins_h]&0xffff); + prev[strstart&w_mask]=head[ins_h]; + head[ins_h]=(short)strstart; + + // strstart never exceeds WSIZE-MAX_MATCH, so there are + // always MIN_MATCH bytes ahead. + } + while (--match_length != 0); + strstart++; + } + else{ + strstart += match_length; + match_length = 0; + ins_h = window[strstart]&0xff; + + ins_h=(((ins_h)<<hash_shift)^(window[strstart+1]&0xff))&hash_mask; + // If lookahead < MIN_MATCH, ins_h is garbage, but it does not + // matter since it will be recomputed at next deflate call. + } + } + else { + // No match, output a literal byte + + bflush=_tr_tally(0, window[strstart]&0xff); + lookahead--; + strstart++; + } + if (bflush){ + + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if(strm.avail_out==0){ + if(flush == Z_FINISH) return FinishStarted; + else return NeedMore; + } + return flush==Z_FINISH ? FinishDone : BlockDone; + } + + // Same as above, but achieves better compression. We use a lazy + // evaluation for matches: a match is finally adopted only if there is + // no better match at the next window position. + int deflate_slow(int flush){ +// short hash_head = 0; // head of hash chain + int hash_head = 0; // head of hash chain + boolean bflush; // set if current block must be flushed + + // Process the input block. + while(true){ + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + + if (lookahead < MIN_LOOKAHEAD) { + fill_window(); + if(lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return NeedMore; + } + if(lookahead == 0) break; // flush the current block + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + + if(lookahead >= MIN_MATCH) { + ins_h=(((ins_h)<<hash_shift)^(window[(strstart)+(MIN_MATCH-1)]&0xff)) & hash_mask; +// prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head=(head[ins_h]&0xffff); + prev[strstart&w_mask]=head[ins_h]; + head[ins_h]=(short)strstart; + } + + // Find the longest match, discarding those <= prev_length. + prev_length = match_length; prev_match = match_start; + match_length = MIN_MATCH-1; + + if (hash_head != 0 && prev_length < max_lazy_match && + ((strstart-hash_head)&0xffff) <= w_size-MIN_LOOKAHEAD + ){ + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + + if(strategy != Z_HUFFMAN_ONLY) { + match_length = longest_match(hash_head); + } + // longest_match() sets match_start + + if (match_length <= 5 && (strategy == Z_FILTERED || + (match_length == MIN_MATCH && + strstart - match_start > 4096))) { + + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH-1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if(prev_length >= MIN_MATCH && match_length <= prev_length) { + int max_insert = strstart + lookahead - MIN_MATCH; + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush=_tr_tally(strstart-1-prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= prev_length-1; + prev_length -= 2; + do{ + if(++strstart <= max_insert) { + ins_h=(((ins_h)<<hash_shift)^(window[(strstart)+(MIN_MATCH-1)]&0xff))&hash_mask; + //prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head=(head[ins_h]&0xffff); + prev[strstart&w_mask]=head[ins_h]; + head[ins_h]=(short)strstart; + } + } + while(--prev_length != 0); + match_available = 0; + match_length = MIN_MATCH-1; + strstart++; + + if (bflush){ + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + } + } else if (match_available!=0) { + + // If there was no match at the previous position, output a + // single literal. If there was a match but the current match + // is longer, truncate the previous match to a single literal. + + bflush=_tr_tally(0, window[strstart-1]&0xff); + + if (bflush) { + flush_block_only(false); + } + strstart++; + lookahead--; + if(strm.avail_out == 0) return NeedMore; + } else { + // There is no previous match to compare with, wait for + // the next step to decide. + + match_available = 1; + strstart++; + lookahead--; + } + } + + if(match_available!=0) { + bflush=_tr_tally(0, window[strstart-1]&0xff); + match_available = 0; + } + flush_block_only(flush == Z_FINISH); + + if(strm.avail_out==0){ + if(flush == Z_FINISH) return FinishStarted; + else return NeedMore; + } + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + int longest_match(int cur_match){ + int chain_length = max_chain_length; // max hash chain length + int scan = strstart; // current string + int match; // matched string + int len; // length of current match + int best_len = prev_length; // best match length so far + int limit = strstart>(w_size-MIN_LOOKAHEAD) ? + strstart-(w_size-MIN_LOOKAHEAD) : 0; + int nice_match=this.nice_match; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + int wmask = w_mask; + + int strend = strstart + MAX_MATCH; + byte scan_end1 = window[scan+best_len-1]; + byte scan_end = window[scan+best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= good_match) { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if (nice_match > lookahead) nice_match = lookahead; + + do { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if (window[match+best_len] != scan_end || + window[match+best_len-1] != scan_end1 || + window[match] != window[scan] || + window[++match] != window[scan+1]) continue; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do { + } while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + scan < strend); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + + if(len>best_len) { + match_start = cur_match; + best_len = len; + if (len >= nice_match) break; + scan_end1 = window[scan+best_len-1]; + scan_end = window[scan+best_len]; + } + + } while ((cur_match = (prev[cur_match & wmask]&0xffff)) > limit + && --chain_length != 0); + + if (best_len <= lookahead) return best_len; + return lookahead; + } + + int deflateInit(ZStream strm, int level, int bits){ + return deflateInit2(strm, level, Z_DEFLATED, bits, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY); + } + int deflateInit(ZStream strm, int level){ + return deflateInit(strm, level, MAX_WBITS); + } + int deflateInit2(ZStream strm, int level, int method, int windowBits, + int memLevel, int strategy){ + int noheader = 0; + // byte[] my_version=ZLIB_VERSION; + + // + // if (version == null || version[0] != my_version[0] + // || stream_size != sizeof(z_stream)) { + // return Z_VERSION_ERROR; + // } + + strm.msg = null; + + if (level == Z_DEFAULT_COMPRESSION) level = 6; + + if (windowBits < 0) { // undocumented feature: suppress zlib header + noheader = 1; + windowBits = -windowBits; + } + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || + method != Z_DEFLATED || + windowBits < 9 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + strm.dstate = (Deflate)this; + + this.noheader = noheader; + w_bits = windowBits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = ((hash_bits+MIN_MATCH-1)/MIN_MATCH); + + window = new byte[w_size*2]; + prev = new short[w_size]; + head = new short[hash_size]; + + lit_bufsize = 1 << (memLevel + 6); // 16K elements by default + + // We overlay pending_buf and d_buf+l_buf. This works since the average + // output size for (length,distance) codes is <= 24 bits. + pending_buf = new byte[lit_bufsize*4]; + pending_buf_size = lit_bufsize*4; + + d_buf = lit_bufsize/2; + l_buf = (1+2)*lit_bufsize; + + this.level = level; + +//System.out.println("level="+level); + + this.strategy = strategy; + this.method = (byte)method; + + return deflateReset(strm); + } + + int deflateReset(ZStream strm){ + strm.total_in = strm.total_out = 0; + strm.msg = null; // + strm.data_type = Z_UNKNOWN; + + pending = 0; + pending_out = 0; + + if(noheader < 0) { + noheader = 0; // was set to -1 by deflate(..., Z_FINISH); + } + status = (noheader!=0) ? BUSY_STATE : INIT_STATE; + strm.adler=strm._adler.adler32(0, null, 0, 0); + + last_flush = Z_NO_FLUSH; + + tr_init(); + lm_init(); + return Z_OK; + } + + int deflateEnd(){ + if(status!=INIT_STATE && status!=BUSY_STATE && status!=FINISH_STATE){ + return Z_STREAM_ERROR; + } + // Deallocate in reverse order of allocations: + pending_buf=null; + head=null; + prev=null; + window=null; + // free + // dstate=null; + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; + } + + int deflateParams(ZStream strm, int _level, int _strategy){ + int err=Z_OK; + + if(_level == Z_DEFAULT_COMPRESSION){ + _level = 6; + } + if(_level < 0 || _level > 9 || + _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + if(config_table[level].func!=config_table[_level].func && + strm.total_in != 0) { + // Flush the last buffer: + err = strm.deflate(Z_PARTIAL_FLUSH); + } + + if(level != _level) { + level = _level; + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; + } + strategy = _strategy; + return err; + } + + int deflateSetDictionary (ZStream strm, byte[] dictionary, int dictLength){ + int length = dictLength; + int index=0; + + if(dictionary == null || status != INIT_STATE) + return Z_STREAM_ERROR; + + strm.adler=strm._adler.adler32(strm.adler, dictionary, 0, dictLength); + + if(length < MIN_MATCH) return Z_OK; + if(length > w_size-MIN_LOOKAHEAD){ + length = w_size-MIN_LOOKAHEAD; + index=dictLength-length; // use the tail of the dictionary + } + System.arraycopy(dictionary, index, window, 0, length); + strstart = length; + block_start = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0]&0xff; + ins_h=(((ins_h)<<hash_shift)^(window[1]&0xff))&hash_mask; + + for(int n=0; n<=length-MIN_MATCH; n++){ + ins_h=(((ins_h)<<hash_shift)^(window[(n)+(MIN_MATCH-1)]&0xff))&hash_mask; + prev[n&w_mask]=head[ins_h]; + head[ins_h]=(short)n; + } + return Z_OK; + } + + int deflate(ZStream strm, int flush){ + int old_flush; + + if(flush>Z_FINISH || flush<0){ + return Z_STREAM_ERROR; + } + + if(strm.next_out == null || + (strm.next_in == null && strm.avail_in != 0) || + (status == FINISH_STATE && flush != Z_FINISH)) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_STREAM_ERROR)]; + return Z_STREAM_ERROR; + } + if(strm.avail_out == 0){ + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + this.strm = strm; // just in case + old_flush = last_flush; + last_flush = flush; + + // Write the zlib header + if(status == INIT_STATE) { + int header = (Z_DEFLATED+((w_bits-8)<<4))<<8; + int level_flags=((level-1)&0xff)>>1; + + if(level_flags>3) level_flags=3; + header |= (level_flags<<6); + if(strstart!=0) header |= PRESET_DICT; + header+=31-(header % 31); + + status=BUSY_STATE; + putShortMSB(header); + + + // Save the adler32 of the preset dictionary: + if(strstart!=0){ + putShortMSB((int)(strm.adler>>>16)); + putShortMSB((int)(strm.adler&0xffff)); + } + strm.adler=strm._adler.adler32(0, null, 0, 0); + } + + // Flush as much pending output as possible + if(pending != 0) { + strm.flush_pending(); + if(strm.avail_out == 0) { + //System.out.println(" avail_out==0"); + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if(strm.avail_in==0 && flush <= old_flush && + flush != Z_FINISH) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // User must not provide more input after the first FINISH: + if(status == FINISH_STATE && strm.avail_in != 0) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // Start a new block or continue the current one. + if(strm.avail_in!=0 || lookahead!=0 || + (flush != Z_NO_FLUSH && status != FINISH_STATE)) { + int bstate=-1; + switch(config_table[level].func){ + case STORED: + bstate = deflate_stored(flush); + break; + case FAST: + bstate = deflate_fast(flush); + break; + case SLOW: + bstate = deflate_slow(flush); + break; + default: + } + + if (bstate==FinishStarted || bstate==FinishDone) { + status = FINISH_STATE; + } + if (bstate==NeedMore || bstate==FinishStarted) { + if(strm.avail_out == 0) { + last_flush = -1; // avoid BUF_ERROR next call, see above + } + return Z_OK; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + + if (bstate==BlockDone) { + if(flush == Z_PARTIAL_FLUSH) { + _tr_align(); + } + else { // FULL_FLUSH or SYNC_FLUSH + _tr_stored_block(0, 0, false); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if(flush == Z_FULL_FLUSH) { + //state.head[s.hash_size-1]=0; + for(int i=0; i<hash_size/*-1*/; i++) // forget history + head[i]=0; + } + } + strm.flush_pending(); + if(strm.avail_out == 0) { + last_flush = -1; // avoid BUF_ERROR at next call, see above + return Z_OK; + } + } + } + + if(flush!=Z_FINISH) return Z_OK; + if(noheader!=0) return Z_STREAM_END; + + // Write the zlib trailer (adler32) + putShortMSB((int)(strm.adler>>>16)); + putShortMSB((int)(strm.adler&0xffff)); + strm.flush_pending(); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. + noheader = -1; // write the trailer only once! + return pending != 0 ? Z_OK : Z_STREAM_END; + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/InfBlocks.java b/app/src/main/java/com/jcraft/jzlib/InfBlocks.java new file mode 100644 index 0000000..f6997fc --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/InfBlocks.java @@ -0,0 +1,614 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfBlocks{ + static final private int MANY=1440; + + // And'ing with mask[n] masks the lower n bits + static final private int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + // Table for deflate from PKZIP's appnote.txt. + static final int[] border = { // Order of the bit length code lengths + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final private int TYPE=0; // get type bits (3, including end bit) + static final private int LENS=1; // get lengths for stored + static final private int STORED=2;// processing stored block + static final private int TABLE=3; // get table lengths + static final private int BTREE=4; // get bit lengths tree for a dynamic block + static final private int DTREE=5; // get length, distance trees for a dynamic block + static final private int CODES=6; // processing fixed or dynamic block + static final private int DRY=7; // output remaining window bytes + static final private int DONE=8; // finished last block, done + static final private int BAD=9; // ot a data error--stuck here + + int mode; // current inflate_block mode + + int left; // if STORED, bytes left to copy + + int table; // table lengths (14 bits) + int index; // index into blens (or border) + int[] blens; // bit lengths of codes + int[] bb=new int[1]; // bit length tree depth + int[] tb=new int[1]; // bit length decoding tree + + InfCodes codes=new InfCodes(); // if CODES, current state + + int last; // true if this block is the last block + + // mode independent information + int bitk; // bits in bit buffer + int bitb; // bit buffer + int[] hufts; // single malloc for tree space + byte[] window; // sliding window + int end; // one byte after sliding window + int read; // window read pointer + int write; // window write pointer + Object checkfn; // check function + long check; // check on output + + InfTree inftree=new InfTree(); + + InfBlocks(ZStream z, Object checkfn, int w){ + hufts=new int[MANY*3]; + window=new byte[w]; + end=w; + this.checkfn = checkfn; + mode = TYPE; + reset(z, null); + } + + void reset(ZStream z, long[] c){ + if(c!=null) c[0]=check; + if(mode==BTREE || mode==DTREE){ + } + if(mode==CODES){ + codes.free(z); + } + mode=TYPE; + bitk=0; + bitb=0; + read=write=0; + + if(checkfn != null) + z.adler=check=z._adler.adler32(0L, null, 0, 0); + } + + int proc(ZStream z, int r){ + int t; // temporary storage + int b; // bit buffer + int k; // bits in bit buffer + int p; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + + // copy input/output information to locals (UPDATE macro restores) + {p=z.next_in_index;n=z.avail_in;b=bitb;k=bitk;} + {q=write;m=(int)(q<read?read-q-1:end-q);} + + // process input based on current state + while(true){ + switch (mode){ + case TYPE: + + while(k<(3)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + t = (int)(b & 7); + last = t & 1; + + switch (t >>> 1){ + case 0: // stored + {b>>>=(3);k-=(3);} + t = k & 7; // go to byte boundary + + {b>>>=(t);k-=(t);} + mode = LENS; // get length of stored block + break; + case 1: // fixed + { + int[] bl=new int[1]; + int[] bd=new int[1]; + int[][] tl=new int[1][]; + int[][] td=new int[1][]; + + InfTree.inflate_trees_fixed(bl, bd, tl, td, z); + codes.init(bl[0], bd[0], tl[0], 0, td[0], 0, z); + } + + {b>>>=(3);k-=(3);} + + mode = CODES; + break; + case 2: // dynamic + + {b>>>=(3);k-=(3);} + + mode = TABLE; + break; + case 3: // illegal + + {b>>>=(3);k-=(3);} + mode = BAD; + z.msg = "invalid block type"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + break; + case LENS: + + while(k<(32)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + if ((((~b) >>> 16) & 0xffff) != (b & 0xffff)){ + mode = BAD; + z.msg = "invalid stored block lengths"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + left = (b & 0xffff); + b = k = 0; // dump bits + mode = left!=0 ? STORED : (last!=0 ? DRY : TYPE); + break; + case STORED: + if (n == 0){ + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + if(m==0){ + if(q==end&&read!=0){ + q=0; m=(int)(q<read?read-q-1:end-q); + } + if(m==0){ + write=q; + r=inflate_flush(z,r); + q=write;m=(int)(q<read?read-q-1:end-q); + if(q==end&&read!=0){ + q=0; m=(int)(q<read?read-q-1:end-q); + } + if(m==0){ + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + } + } + r=Z_OK; + + t = left; + if(t>n) t = n; + if(t>m) t = m; + System.arraycopy(z.next_in, p, window, q, t); + p += t; n -= t; + q += t; m -= t; + if ((left -= t) != 0) + break; + mode = last!=0 ? DRY : TYPE; + break; + case TABLE: + + while(k<(14)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + table = t = (b & 0x3fff); + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) + { + mode = BAD; + z.msg = "too many length or distance symbols"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if(blens==null || blens.length<t){ + blens=new int[t]; + } + else{ + for(int i=0; i<t; i++){blens[i]=0;} + } + + {b>>>=(14);k-=(14);} + + index = 0; + mode = BTREE; + case BTREE: + while (index < 4 + (table >>> 10)){ + while(k<(3)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + blens[border[index++]] = b&7; + + {b>>>=(3);k-=(3);} + } + + while(index < 19){ + blens[border[index++]] = 0; + } + + bb[0] = 7; + t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z); + if (t != Z_OK){ + r = t; + if (r == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + index = 0; + mode = DTREE; + case DTREE: + while (true){ + t = table; + if(!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))){ + break; + } + + int[] h; + int i, j, c; + + t = bb[0]; + + while(k<(t)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + if(tb[0]==-1){ + //System.err.println("null..."); + } + + t=hufts[(tb[0]+(b&inflate_mask[t]))*3+1]; + c=hufts[(tb[0]+(b&inflate_mask[t]))*3+2]; + + if (c < 16){ + b>>>=(t);k-=(t); + blens[index++] = c; + } + else { // c == 16..18 + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + + while(k<(t+i)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + }; + n--; + b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + b>>>=(t);k-=(t); + + j += (b & inflate_mask[i]); + + b>>>=(i);k-=(i); + + i = index; + t = table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)){ + blens=null; + mode = BAD; + z.msg = "invalid bit length repeat"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + + c = c == 16 ? blens[i-1] : 0; + do{ + blens[i++] = c; + } + while (--j!=0); + index = i; + } + } + + tb[0]=-1; + { + int[] bl=new int[1]; + int[] bd=new int[1]; + int[] tl=new int[1]; + int[] td=new int[1]; + bl[0] = 9; // must be <= 9 for lookahead assumptions + bd[0] = 6; // must be <= 9 for lookahead assumptions + + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), + 1 + ((t >> 5) & 0x1f), + blens, bl, bd, tl, td, hufts, z); + + if (t != Z_OK){ + if (t == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + r = t; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z,r); + } + codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0], z); + } + mode = CODES; + case CODES: + bitb=b; bitk=k; + z.avail_in=n; z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + + if ((r = codes.proc(this, z, r)) != Z_STREAM_END){ + return inflate_flush(z, r); + } + r = Z_OK; + codes.free(z); + + p=z.next_in_index; n=z.avail_in;b=bitb;k=bitk; + q=write;m=(int)(q<read?read-q-1:end-q); + + if (last==0){ + mode = TYPE; + break; + } + mode = DRY; + case DRY: + write=q; + r=inflate_flush(z, r); + q=write; m=(int)(q<read?read-q-1:end-q); + if (read != write){ + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z, r); + } + mode = DONE; + case DONE: + r = Z_STREAM_END; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z, r); + case BAD: + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z, r); + + default: + r = Z_STREAM_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(z, r); + } + } + } + + void free(ZStream z){ + reset(z, null); + window=null; + hufts=null; + //ZFREE(z, s); + } + + void set_dictionary(byte[] d, int start, int n){ + System.arraycopy(d, start, window, 0, n); + read = write = n; + } + + // Returns true if inflate is currently at the end of a block generated + // by Z_SYNC_FLUSH or Z_FULL_FLUSH. + int sync_point(){ + return mode == LENS ? 1 : 0; + } + + // copy as much as possible from the sliding window to the output area + int inflate_flush(ZStream z, int r){ + int n; + int p; + int q; + + // local copies of source and destination pointers + p = z.next_out_index; + q = read; + + // compute number of bytes to copy as far as end of window + n = (int)((q <= write ? write : end) - q); + if (n > z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(checkfn != null) + z.adler=check=z._adler.adler32(check, window, q, n); + + // copy as far as end of window + System.arraycopy(window, q, z.next_out, p, n); + p += n; + q += n; + + // see if more to copy at beginning of window + if (q == end){ + // wrap pointers + q = 0; + if (write == end) + write = 0; + + // compute bytes to copy + n = write - q; + if (n > z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(checkfn != null) + z.adler=check=z._adler.adler32(check, window, q, n); + + // copy + System.arraycopy(window, q, z.next_out, p, n); + p += n; + q += n; + } + + // update pointers + z.next_out_index = p; + read = q; + + // done + return r; + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/InfCodes.java b/app/src/main/java/com/jcraft/jzlib/InfCodes.java new file mode 100644 index 0000000..c768fb1 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/InfCodes.java @@ -0,0 +1,605 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfCodes{ + + static final private int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + // waiting for "i:"=input, + // "o:"=output, + // "x:"=nothing + static final private int START=0; // x: set up for LEN + static final private int LEN=1; // i: get length/literal/eob next + static final private int LENEXT=2; // i: getting length extra (have base) + static final private int DIST=3; // i: get distance next + static final private int DISTEXT=4;// i: getting distance extra + static final private int COPY=5; // o: copying bytes in window, waiting for space + static final private int LIT=6; // o: got literal, waiting for output space + static final private int WASH=7; // o: got eob, possibly still output waiting + static final private int END=8; // x: got eob and all data flushed + static final private int BADCODE=9;// x: got error + + int mode; // current inflate_codes mode + + // mode dependent information + int len; + + int[] tree; // pointer into tree + int tree_index=0; + int need; // bits needed + + int lit; + + // if EXT or COPY, where and how much + int get; // bits to get for extra + int dist; // distance back to copy from + + byte lbits; // ltree bits decoded per branch + byte dbits; // dtree bits decoder per branch + int[] ltree; // literal/length/eob tree + int ltree_index; // literal/length/eob tree + int[] dtree; // distance tree + int dtree_index; // distance tree + + InfCodes(){ + } + void init(int bl, int bd, + int[] tl, int tl_index, + int[] td, int td_index, ZStream z){ + mode=START; + lbits=(byte)bl; + dbits=(byte)bd; + ltree=tl; + ltree_index=tl_index; + dtree = td; + dtree_index=td_index; + tree=null; + } + + int proc(InfBlocks s, ZStream z, int r){ + int j; // temporary storage + int[] t; // temporary pointer + int tindex; // temporary pointer + int e; // extra bits or operation + int b=0; // bit buffer + int k=0; // bits in bit buffer + int p=0; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int f; // pointer to copy strings from + + // copy input/output information to locals (UPDATE macro restores) + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q<s.read?s.read-q-1:s.end-q; + + // process input and output based on current state + while (true){ + switch (mode){ + // waiting for "i:"=input, "o:"=output, "x:"=nothing + case START: // x: set up for LEN + if (m >= 258 && n >= 10){ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + r = inflate_fast(lbits, dbits, + ltree, ltree_index, + dtree, dtree_index, + s, z); + + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q<s.read?s.read-q-1:s.end-q; + + if (r != Z_OK){ + mode = r == Z_STREAM_END ? WASH : BADCODE; + break; + } + } + need = lbits; + tree = ltree; + tree_index=ltree_index; + + mode = LEN; + case LEN: // i: get length/literal/eob next + j = need; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; + b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + tindex=(tree_index+(b&inflate_mask[j]))*3; + + b>>>=(tree[tindex+1]); + k-=(tree[tindex+1]); + + e=tree[tindex]; + + if(e == 0){ // literal + lit = tree[tindex+2]; + mode = LIT; + break; + } + if((e & 16)!=0 ){ // length + get = e & 15; + len = tree[tindex+2]; + mode = LENEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3+tree[tindex+2]; + break; + } + if ((e & 32)!=0){ // end of block + mode = WASH; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid literal/length code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case LENEXT: // i: getting length extra (have base) + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + len += (b & inflate_mask[j]); + + b>>=j; + k-=j; + + need = dbits; + tree = dtree; + tree_index=dtree_index; + mode = DIST; + case DIST: // i: get distance next + j = need; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + tindex=(tree_index+(b & inflate_mask[j]))*3; + + b>>=tree[tindex+1]; + k-=tree[tindex+1]; + + e = (tree[tindex]); + if((e & 16)!=0){ // distance + get = e & 15; + dist = tree[tindex+2]; + mode = DISTEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3 + tree[tindex+2]; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid distance code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case DISTEXT: // i: getting distance extra + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + n--; b|=(z.next_in[p++]&0xff)<<k; + k+=8; + } + + dist += (b & inflate_mask[j]); + + b>>=j; + k-=j; + + mode = COPY; + case COPY: // o: copying bytes in window, waiting for space + f = q - dist; + while(f < 0){ // modulo window size-"while" instead + f += s.end; // of "if" handles invalid distances + } + while (len!=0){ + + if(m==0){ + if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;} + if(m==0){ + s.write=q; r=s.inflate_flush(z,r); + q=s.write;m=q<s.read?s.read-q-1:s.end-q; + + if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;} + + if(m==0){ + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + } + } + + s.window[q++]=s.window[f++]; m--; + + if (f == s.end) + f = 0; + len--; + } + mode = START; + break; + case LIT: // o: got literal, waiting for output space + if(m==0){ + if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;} + if(m==0){ + s.write=q; r=s.inflate_flush(z,r); + q=s.write;m=q<s.read?s.read-q-1:s.end-q; + + if(q==s.end&&s.read!=0){q=0;m=q<s.read?s.read-q-1:s.end-q;} + if(m==0){ + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + } + } + r=Z_OK; + + s.window[q++]=(byte)lit; m--; + + mode = START; + break; + case WASH: // o: got eob, possibly more output + if (k > 7){ // return unused byte, if any + k -= 8; + n++; + p--; // can always return one + } + + s.write=q; r=s.inflate_flush(z,r); + q=s.write;m=q<s.read?s.read-q-1:s.end-q; + + if (s.read != s.write){ + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + mode = END; + case END: + r = Z_STREAM_END; + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + case BADCODE: // x: got error + + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + + default: + r = Z_STREAM_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(z,r); + } + } + } + + void free(ZStream z){ + // ZFREE(z, c); + } + + // Called with number of bytes left to write in window at least 258 + // (the maximum string length) and number of input bytes available + // at least ten. The ten bytes are six bytes for the longest length/ + // distance pair plus four bytes for overloading the bit buffer. + + int inflate_fast(int bl, int bd, + int[] tl, int tl_index, + int[] td, int td_index, + InfBlocks s, ZStream z){ + int t; // temporary pointer + int[] tp; // temporary pointer + int tp_index; // temporary pointer + int e; // extra bits or operation + int b; // bit buffer + int k; // bits in bit buffer + int p; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int ml; // mask for literal/length tree + int md; // mask for distance tree + int c; // bytes to copy + int d; // distance back to copy from + int r; // copy source pointer + + int tp_index_t_3; // (tp_index+t)*3 + + // load input, output, bit values + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q<s.read?s.read-q-1:s.end-q; + + // initialize masks + ml = inflate_mask[bl]; + md = inflate_mask[bd]; + + // do until not enough input or output space for fast loop + do { // assume called with m >= 258 && n >= 10 + // get literal/length code + while(k<(20)){ // max bits for literal/length code + n--; + b|=(z.next_in[p++]&0xff)<<k;k+=8; + } + + t= b&ml; + tp=tl; + tp_index=tl_index; + tp_index_t_3=(tp_index+t)*3; + if ((e = tp[tp_index_t_3]) == 0){ + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++] = (byte)tp[tp_index_t_3+2]; + m--; + continue; + } + do { + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + e &= 15; + c = tp[tp_index_t_3+2] + ((int)b & inflate_mask[e]); + + b>>=e; k-=e; + + // decode distance base of block to copy + while(k<(15)){ // max bits for distance code + n--; + b|=(z.next_in[p++]&0xff)<<k;k+=8; + } + + t= b&md; + tp=td; + tp_index=td_index; + tp_index_t_3=(tp_index+t)*3; + e = tp[tp_index_t_3]; + + do { + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + // get extra bits to add to distance base + e &= 15; + while(k<(e)){ // get extra bits (up to 13) + n--; + b|=(z.next_in[p++]&0xff)<<k;k+=8; + } + + d = tp[tp_index_t_3+2] + (b&inflate_mask[e]); + + b>>=(e); k-=(e); + + // do the copy + m -= c; + if (q >= d){ // offset before dest + // just copy + r=q-d; + if(q-r>0 && 2>(q-r)){ + s.window[q++]=s.window[r++]; // minimum count is three, + s.window[q++]=s.window[r++]; // so unroll loop a little + c-=2; + } + else{ + System.arraycopy(s.window, r, s.window, q, 2); + q+=2; r+=2; c-=2; + } + } + else{ // else offset after destination + r=q-d; + do{ + r+=s.end; // force pointer in window + }while(r<0); // covers invalid distances + e=s.end-r; + if(c>e){ // if source crosses, + c-=e; // wrapped copy + if(q-r>0 && e>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--e!=0); + } + else{ + System.arraycopy(s.window, r, s.window, q, e); + q+=e; r+=e; e=0; + } + r = 0; // copy rest from start of window + } + + } + + // copy all or what's left + if(q-r>0 && c>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--c!=0); + } + else{ + System.arraycopy(s.window, r, s.window, q, c); + q+=c; r+=c; c=0; + } + break; + } + else if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + e=tp[tp_index_t_3]; + } + else{ + z.msg = "invalid distance code"; + + c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + break; + } + + if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + if((e=tp[tp_index_t_3])==0){ + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++]=(byte)tp[tp_index_t_3+2]; + m--; + break; + } + } + else if((e&32)!=0){ + + c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_STREAM_END; + } + else{ + z.msg="invalid literal/length code"; + + c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + } + while(m>=258 && n>= 10); + + // not enough input or output--restore pointers and return + c=z.avail_in-n;c=(k>>3)<c?k>>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_OK; + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/InfTree.java b/app/src/main/java/com/jcraft/jzlib/InfTree.java new file mode 100644 index 0000000..cbca436 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/InfTree.java @@ -0,0 +1,520 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfTree{ + + static final private int MANY=1440; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final int fixed_bl = 9; + static final int fixed_bd = 5; + + static final int[] fixed_tl = { + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,192, + 80,7,10, 0,8,96, 0,8,32, 0,9,160, + 0,8,0, 0,8,128, 0,8,64, 0,9,224, + 80,7,6, 0,8,88, 0,8,24, 0,9,144, + 83,7,59, 0,8,120, 0,8,56, 0,9,208, + 81,7,17, 0,8,104, 0,8,40, 0,9,176, + 0,8,8, 0,8,136, 0,8,72, 0,9,240, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,200, + 81,7,13, 0,8,100, 0,8,36, 0,9,168, + 0,8,4, 0,8,132, 0,8,68, 0,9,232, + 80,7,8, 0,8,92, 0,8,28, 0,9,152, + 84,7,83, 0,8,124, 0,8,60, 0,9,216, + 82,7,23, 0,8,108, 0,8,44, 0,9,184, + 0,8,12, 0,8,140, 0,8,76, 0,9,248, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,196, + 81,7,11, 0,8,98, 0,8,34, 0,9,164, + 0,8,2, 0,8,130, 0,8,66, 0,9,228, + 80,7,7, 0,8,90, 0,8,26, 0,9,148, + 84,7,67, 0,8,122, 0,8,58, 0,9,212, + 82,7,19, 0,8,106, 0,8,42, 0,9,180, + 0,8,10, 0,8,138, 0,8,74, 0,9,244, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,204, + 81,7,15, 0,8,102, 0,8,38, 0,9,172, + 0,8,6, 0,8,134, 0,8,70, 0,9,236, + 80,7,9, 0,8,94, 0,8,30, 0,9,156, + 84,7,99, 0,8,126, 0,8,62, 0,9,220, + 82,7,27, 0,8,110, 0,8,46, 0,9,188, + 0,8,14, 0,8,142, 0,8,78, 0,9,252, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,194, + 80,7,10, 0,8,97, 0,8,33, 0,9,162, + 0,8,1, 0,8,129, 0,8,65, 0,9,226, + 80,7,6, 0,8,89, 0,8,25, 0,9,146, + 83,7,59, 0,8,121, 0,8,57, 0,9,210, + 81,7,17, 0,8,105, 0,8,41, 0,9,178, + 0,8,9, 0,8,137, 0,8,73, 0,9,242, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,202, + 81,7,13, 0,8,101, 0,8,37, 0,9,170, + 0,8,5, 0,8,133, 0,8,69, 0,9,234, + 80,7,8, 0,8,93, 0,8,29, 0,9,154, + 84,7,83, 0,8,125, 0,8,61, 0,9,218, + 82,7,23, 0,8,109, 0,8,45, 0,9,186, + 0,8,13, 0,8,141, 0,8,77, 0,9,250, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,198, + 81,7,11, 0,8,99, 0,8,35, 0,9,166, + 0,8,3, 0,8,131, 0,8,67, 0,9,230, + 80,7,7, 0,8,91, 0,8,27, 0,9,150, + 84,7,67, 0,8,123, 0,8,59, 0,9,214, + 82,7,19, 0,8,107, 0,8,43, 0,9,182, + 0,8,11, 0,8,139, 0,8,75, 0,9,246, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,206, + 81,7,15, 0,8,103, 0,8,39, 0,9,174, + 0,8,7, 0,8,135, 0,8,71, 0,9,238, + 80,7,9, 0,8,95, 0,8,31, 0,9,158, + 84,7,99, 0,8,127, 0,8,63, 0,9,222, + 82,7,27, 0,8,111, 0,8,47, 0,9,190, + 0,8,15, 0,8,143, 0,8,79, 0,9,254, + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,193, + + 80,7,10, 0,8,96, 0,8,32, 0,9,161, + 0,8,0, 0,8,128, 0,8,64, 0,9,225, + 80,7,6, 0,8,88, 0,8,24, 0,9,145, + 83,7,59, 0,8,120, 0,8,56, 0,9,209, + 81,7,17, 0,8,104, 0,8,40, 0,9,177, + 0,8,8, 0,8,136, 0,8,72, 0,9,241, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,201, + 81,7,13, 0,8,100, 0,8,36, 0,9,169, + 0,8,4, 0,8,132, 0,8,68, 0,9,233, + 80,7,8, 0,8,92, 0,8,28, 0,9,153, + 84,7,83, 0,8,124, 0,8,60, 0,9,217, + 82,7,23, 0,8,108, 0,8,44, 0,9,185, + 0,8,12, 0,8,140, 0,8,76, 0,9,249, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,197, + 81,7,11, 0,8,98, 0,8,34, 0,9,165, + 0,8,2, 0,8,130, 0,8,66, 0,9,229, + 80,7,7, 0,8,90, 0,8,26, 0,9,149, + 84,7,67, 0,8,122, 0,8,58, 0,9,213, + 82,7,19, 0,8,106, 0,8,42, 0,9,181, + 0,8,10, 0,8,138, 0,8,74, 0,9,245, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,205, + 81,7,15, 0,8,102, 0,8,38, 0,9,173, + 0,8,6, 0,8,134, 0,8,70, 0,9,237, + 80,7,9, 0,8,94, 0,8,30, 0,9,157, + 84,7,99, 0,8,126, 0,8,62, 0,9,221, + 82,7,27, 0,8,110, 0,8,46, 0,9,189, + 0,8,14, 0,8,142, 0,8,78, 0,9,253, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,195, + 80,7,10, 0,8,97, 0,8,33, 0,9,163, + 0,8,1, 0,8,129, 0,8,65, 0,9,227, + 80,7,6, 0,8,89, 0,8,25, 0,9,147, + 83,7,59, 0,8,121, 0,8,57, 0,9,211, + 81,7,17, 0,8,105, 0,8,41, 0,9,179, + 0,8,9, 0,8,137, 0,8,73, 0,9,243, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,203, + 81,7,13, 0,8,101, 0,8,37, 0,9,171, + 0,8,5, 0,8,133, 0,8,69, 0,9,235, + 80,7,8, 0,8,93, 0,8,29, 0,9,155, + 84,7,83, 0,8,125, 0,8,61, 0,9,219, + 82,7,23, 0,8,109, 0,8,45, 0,9,187, + 0,8,13, 0,8,141, 0,8,77, 0,9,251, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,199, + 81,7,11, 0,8,99, 0,8,35, 0,9,167, + 0,8,3, 0,8,131, 0,8,67, 0,9,231, + 80,7,7, 0,8,91, 0,8,27, 0,9,151, + 84,7,67, 0,8,123, 0,8,59, 0,9,215, + 82,7,19, 0,8,107, 0,8,43, 0,9,183, + 0,8,11, 0,8,139, 0,8,75, 0,9,247, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,207, + 81,7,15, 0,8,103, 0,8,39, 0,9,175, + 0,8,7, 0,8,135, 0,8,71, 0,9,239, + 80,7,9, 0,8,95, 0,8,31, 0,9,159, + 84,7,99, 0,8,127, 0,8,63, 0,9,223, + 82,7,27, 0,8,111, 0,8,47, 0,9,191, + 0,8,15, 0,8,143, 0,8,79, 0,9,255 + }; + static final int[] fixed_td = { + 80,5,1, 87,5,257, 83,5,17, 91,5,4097, + 81,5,5, 89,5,1025, 85,5,65, 93,5,16385, + 80,5,3, 88,5,513, 84,5,33, 92,5,8193, + 82,5,9, 90,5,2049, 86,5,129, 192,5,24577, + 80,5,2, 87,5,385, 83,5,25, 91,5,6145, + 81,5,7, 89,5,1537, 85,5,97, 93,5,24577, + 80,5,4, 88,5,769, 84,5,49, 92,5,12289, + 82,5,13, 90,5,3073, 86,5,193, 192,5,24577 + }; + + // Tables for deflate from PKZIP's appnote.txt. + static final int[] cplens = { // Copy lengths for literal codes 257..285 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 + }; + + // see note #13 above about 258 + static final int[] cplext = { // Extra bits for literal codes 257..285 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid + }; + + static final int[] cpdist = { // Copy offsets for distance codes 0..29 + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + static final int[] cpdext = { // Extra bits for distance codes + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + // If BMAX needs to be larger than 16, then h and x[] should be uLong. + static final int BMAX=15; // maximum bit length of any code + + int[] hn = null; // hufts used in space + int[] v = null; // work area for huft_build + int[] c = null; // bit length count table + int[] r = null; // table entry for structure assignment + int[] u = null; // table stack + int[] x = null; // bit offsets, then code stack + + private int huft_build(int[] b, // code lengths in bits (all assumed <= BMAX) + int bindex, + int n, // number of codes (assumed <= 288) + int s, // number of simple-valued codes (0..s-1) + int[] d, // list of base values for non-simple codes + int[] e, // list of extra bits for non-simple codes + int[] t, // result: starting table + int[] m, // maximum lookup bits, returns actual + int[] hp,// space for trees + int[] hn,// hufts used in space + int[] v // working area: values in order of bit length + ){ + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in this + // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + // lengths), or Z_MEM_ERROR if not enough memory. + + int a; // counter for codes of length k + int f; // i repeats in table every f entries + int g; // maximum code length + int h; // table level + int i; // counter, current code + int j; // counter + int k; // number of bits in current code + int l; // bits per table (returned in m) + int mask; // (1 << w) - 1, to avoid cc -O bug on HP + int p; // pointer into c[], b[], or v[] + int q; // points to current table + int w; // bits before this table == (l * h) + int xp; // pointer into x + int y; // number of dummy codes added + int z; // number of entries in current table + + // Generate counts for each bit length + + p = 0; i = n; + do { + c[b[bindex+p]]++; p++; i--; // assume all entries <= BMAX + }while(i!=0); + + if(c[0] == n){ // null input--all zero length codes + t[0] = -1; + m[0] = 0; + return Z_OK; + } + + // Find minimum and maximum length, bound *m by those + l = m[0]; + for (j = 1; j <= BMAX; j++) + if(c[j]!=0) break; + k = j; // minimum code length + if(l < j){ + l = j; + } + for (i = BMAX; i!=0; i--){ + if(c[i]!=0) break; + } + g = i; // maximum code length + if(l > i){ + l = i; + } + m[0] = l; + + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1){ + if ((y -= c[j]) < 0){ + return Z_DATA_ERROR; + } + } + if ((y -= c[i]) < 0){ + return Z_DATA_ERROR; + } + c[i] += y; + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = 1; xp = 2; + while (--i!=0) { // note that i == g from above + x[xp] = (j += c[p]); + xp++; + p++; + } + + // Make a table of values in order of bit lengths + i = 0; p = 0; + do { + if ((j = b[bindex+p]) != 0){ + v[x[j]++] = i; + } + p++; + } + while (++i < n); + n = x[g]; // set n to length of v + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = -l; // bits decoded == (l * h) + u[0] = 0; // just to keep compilers happy + q = 0; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++){ + a = c[k]; + while (a--!=0){ + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l){ + h++; + w += l; // previous table always l bits + // compute minimum size table less than or equal to l bits + z = g - w; + z = (z > l) ? l : z; // table size upper limit + if((f=1<<(j=k-w))>a+1){ // try a k-w bit table + // too few codes for k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = k; + if(j < z){ + while (++j < z){ // try smaller tables up to z bits + if((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + } + z = 1 << j; // table entries for j-bit table + + // allocate new table + if (hn[0] + z > MANY){ // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY + } + u[h] = q = /*hp+*/ hn[0]; // DEBUG + hn[0] += z; + + // connect to last table, if there is one + if(h!=0){ + x[h]=i; // save pattern for backing up + r[0]=(byte)j; // bits in this table + r[1]=(byte)l; // bits to dump before this table + j=i>>>(w - l); + r[2] = (int)(q - u[h-1] - j); // offset to this table + System.arraycopy(r, 0, hp, (u[h-1]+j)*3, 3); // connect to last table + } + else{ + t[0] = q; // first table is returned result + } + } + + // set up table entry in r + r[1] = (byte)(k - w); + if (p >= n){ + r[0] = 128 + 64; // out of values--invalid code + } + else if (v[p] < s){ + r[0] = (byte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block + r[2] = v[p++]; // simple code is just the value + } + else{ + r[0]=(byte)(e[v[p]-s]+16+64); // non-simple--look up in lists + r[2]=d[v[p++] - s]; + } + + // fill code-like entries with r + f=1<<(k-w); + for (j=i>>>w;j<z;j+=f){ + System.arraycopy(r, 0, hp, (q+j)*3, 3); + } + + // backwards increment the k-bit code i + for (j = 1 << (k - 1); (i & j)!=0; j >>>= 1){ + i ^= j; + } + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]){ + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } + } + // Return Z_BUF_ERROR if we were given an incomplete table + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; + } + + int inflate_trees_bits(int[] c, // 19 code lengths + int[] bb, // bits tree desired/actual depth + int[] tb, // bits tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + initWorkArea(19); + hn[0]=0; + result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); + + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed dynamic bit lengths tree"; + } + else if(result == Z_BUF_ERROR || bb[0] == 0){ + z.msg = "incomplete dynamic bit lengths tree"; + result = Z_DATA_ERROR; + } + return result; + } + + int inflate_trees_dynamic(int nl, // number of literal/length codes + int nd, // number of distance codes + int[] c, // that many (total) code lengths + int[] bl, // literal desired/actual bit depth + int[] bd, // distance desired/actual bit depth + int[] tl, // literal/length tree result + int[] td, // distance tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + + // build literal/length tree + initWorkArea(288); + hn[0]=0; + result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); + if (result != Z_OK || bl[0] == 0){ + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed literal/length tree"; + } + else if (result != Z_MEM_ERROR){ + z.msg = "incomplete literal/length tree"; + result = Z_DATA_ERROR; + } + return result; + } + + // build distance tree + initWorkArea(288); + result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); + + if (result != Z_OK || (bd[0] == 0 && nl > 257)){ + if (result == Z_DATA_ERROR){ + z.msg = "oversubscribed distance tree"; + } + else if (result == Z_BUF_ERROR) { + z.msg = "incomplete distance tree"; + result = Z_DATA_ERROR; + } + else if (result != Z_MEM_ERROR){ + z.msg = "empty distance tree with lengths"; + result = Z_DATA_ERROR; + } + return result; + } + + return Z_OK; + } + + static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth + int[] bd, //distance desired/actual bit depth + int[][] tl,//literal/length tree result + int[][] td,//distance tree result + ZStream z //for memory allocation + ){ + bl[0]=fixed_bl; + bd[0]=fixed_bd; + tl[0]=fixed_tl; + td[0]=fixed_td; + return Z_OK; + } + + private void initWorkArea(int vsize){ + if(hn==null){ + hn=new int[1]; + v=new int[vsize]; + c=new int[BMAX+1]; + r=new int[3]; + u=new int[BMAX]; + x=new int[BMAX+1]; + } + if(v.length<vsize){ v=new int[vsize]; } + for(int i=0; i<vsize; i++){v[i]=0;} + for(int i=0; i<BMAX+1; i++){c[i]=0;} + for(int i=0; i<3; i++){r[i]=0;} +// for(int i=0; i<BMAX; i++){u[i]=0;} + System.arraycopy(c, 0, u, 0, BMAX); +// for(int i=0; i<BMAX+1; i++){x[i]=0;} + System.arraycopy(c, 0, x, 0, BMAX+1); + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/Inflate.java b/app/src/main/java/com/jcraft/jzlib/Inflate.java new file mode 100644 index 0000000..30310f6 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/Inflate.java @@ -0,0 +1,374 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class Inflate{ + + static final private int MAX_WBITS=15; // 32K LZ77 window + + // preset dictionary flag in zlib header + static final private int PRESET_DICT=0x20; + + static final int Z_NO_FLUSH=0; + static final int Z_PARTIAL_FLUSH=1; + static final int Z_SYNC_FLUSH=2; + static final int Z_FULL_FLUSH=3; + static final int Z_FINISH=4; + + static final private int Z_DEFLATED=8; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final private int METHOD=0; // waiting for method byte + static final private int FLAG=1; // waiting for flag byte + static final private int DICT4=2; // four dictionary check bytes to go + static final private int DICT3=3; // three dictionary check bytes to go + static final private int DICT2=4; // two dictionary check bytes to go + static final private int DICT1=5; // one dictionary check byte to go + static final private int DICT0=6; // waiting for inflateSetDictionary + static final private int BLOCKS=7; // decompressing blocks + static final private int CHECK4=8; // four check bytes to go + static final private int CHECK3=9; // three check bytes to go + static final private int CHECK2=10; // two check bytes to go + static final private int CHECK1=11; // one check byte to go + static final private int DONE=12; // finished check, done + static final private int BAD=13; // got an error--stay here + + int mode; // current inflate mode + + // mode dependent information + int method; // if FLAGS, method byte + + // if CHECK, check values to compare + long[] was=new long[1] ; // computed check value + long need; // stream check value + + // if BAD, inflateSync's marker bytes count + int marker; + + // mode independent information + int nowrap; // flag for no wrapper + int wbits; // log2(window size) (8..15, defaults to 15) + + InfBlocks blocks; // current inflate_blocks state + + int inflateReset(ZStream z){ + if(z == null || z.istate == null) return Z_STREAM_ERROR; + + z.total_in = z.total_out = 0; + z.msg = null; + z.istate.mode = z.istate.nowrap!=0 ? BLOCKS : METHOD; + z.istate.blocks.reset(z, null); + return Z_OK; + } + + int inflateEnd(ZStream z){ + if(blocks != null) + blocks.free(z); + blocks=null; + // ZFREE(z, z->state); + return Z_OK; + } + + int inflateInit(ZStream z, int w){ + z.msg = null; + blocks = null; + + // handle undocumented nowrap option (no zlib header or check) + nowrap = 0; + if(w < 0){ + w = - w; + nowrap = 1; + } + + // set window size + if(w<8 ||w>15){ + inflateEnd(z); + return Z_STREAM_ERROR; + } + wbits=w; + + z.istate.blocks=new InfBlocks(z, + z.istate.nowrap!=0 ? null : this, + 1<<w); + + // reset state + inflateReset(z); + return Z_OK; + } + + int inflate(ZStream z, int f){ + int r; + int b; + + if(z == null || z.istate == null || z.next_in == null) + return Z_STREAM_ERROR; + f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; + r = Z_BUF_ERROR; + while (true){ +//System.out.println("mode: "+z.istate.mode); + switch (z.istate.mode){ + case METHOD: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + if(((z.istate.method = z.next_in[z.next_in_index++])&0xf)!=Z_DEFLATED){ + z.istate.mode = BAD; + z.msg="unknown compression method"; + z.istate.marker = 5; // can't try inflateSync + break; + } + if((z.istate.method>>4)+8>z.istate.wbits){ + z.istate.mode = BAD; + z.msg="invalid window size"; + z.istate.marker = 5; // can't try inflateSync + break; + } + z.istate.mode=FLAG; + case FLAG: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + b = (z.next_in[z.next_in_index++])&0xff; + + if((((z.istate.method << 8)+b) % 31)!=0){ + z.istate.mode = BAD; + z.msg = "incorrect header check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + if((b&PRESET_DICT)==0){ + z.istate.mode = BLOCKS; + break; + } + z.istate.mode = DICT4; + case DICT4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + z.istate.mode=DICT3; + case DICT3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + z.istate.mode=DICT2; + case DICT2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + z.istate.mode=DICT1; + case DICT1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need += (z.next_in[z.next_in_index++]&0xffL); + z.adler = z.istate.need; + z.istate.mode = DICT0; + return Z_NEED_DICT; + case DICT0: + z.istate.mode = BAD; + z.msg = "need dictionary"; + z.istate.marker = 0; // can try inflateSync + return Z_STREAM_ERROR; + case BLOCKS: + + r = z.istate.blocks.proc(z, r); + if(r == Z_DATA_ERROR){ + z.istate.mode = BAD; + z.istate.marker = 0; // can try inflateSync + break; + } + if(r == Z_OK){ + r = f; + } + if(r != Z_STREAM_END){ + return r; + } + r = f; + z.istate.blocks.reset(z, z.istate.was); + if(z.istate.nowrap!=0){ + z.istate.mode=DONE; + break; + } + z.istate.mode=CHECK4; + case CHECK4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + z.istate.mode=CHECK3; + case CHECK3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + z.istate.mode = CHECK2; + case CHECK2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + z.istate.mode = CHECK1; + case CHECK1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + z.istate.need+=(z.next_in[z.next_in_index++]&0xffL); + + if(((int)(z.istate.was[0])) != ((int)(z.istate.need))){ + z.istate.mode = BAD; + z.msg = "incorrect data check"; + z.istate.marker = 5; // can't try inflateSync + break; + } + + z.istate.mode = DONE; + case DONE: + return Z_STREAM_END; + case BAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } + } + } + + + int inflateSetDictionary(ZStream z, byte[] dictionary, int dictLength){ + int index=0; + int length = dictLength; + if(z==null || z.istate == null|| z.istate.mode != DICT0) + return Z_STREAM_ERROR; + + if(z._adler.adler32(1L, dictionary, 0, dictLength)!=z.adler){ + return Z_DATA_ERROR; + } + + z.adler = z._adler.adler32(0, null, 0, 0); + + if(length >= (1<<z.istate.wbits)){ + length = (1<<z.istate.wbits)-1; + index=dictLength - length; + } + z.istate.blocks.set_dictionary(dictionary, index, length); + z.istate.mode = BLOCKS; + return Z_OK; + } + + static private byte[] mark = {(byte)0, (byte)0, (byte)0xff, (byte)0xff}; + + int inflateSync(ZStream z){ + int n; // number of bytes to look at + int p; // pointer to bytes + int m; // number of marker bytes found in a row + long r, w; // temporaries to save total_in and total_out + + // set up + if(z == null || z.istate == null) + return Z_STREAM_ERROR; + if(z.istate.mode != BAD){ + z.istate.mode = BAD; + z.istate.marker = 0; + } + if((n=z.avail_in)==0) + return Z_BUF_ERROR; + p=z.next_in_index; + m=z.istate.marker; + + // search + while (n!=0 && m < 4){ + if(z.next_in[p] == mark[m]){ + m++; + } + else if(z.next_in[p]!=0){ + m = 0; + } + else{ + m = 4 - m; + } + p++; n--; + } + + // restore + z.total_in += p-z.next_in_index; + z.next_in_index = p; + z.avail_in = n; + z.istate.marker = m; + + // return no joy or set up to restart on a new block + if(m != 4){ + return Z_DATA_ERROR; + } + r=z.total_in; w=z.total_out; + inflateReset(z); + z.total_in=r; z.total_out = w; + z.istate.mode = BLOCKS; + return Z_OK; + } + + // Returns true if inflate is currently at the end of a block generated + // by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + // implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH + // but removes the length bytes of the resulting empty stored block. When + // decompressing, PPP checks that at the end of input packet, inflate is + // waiting for these length bytes. + int inflateSyncPoint(ZStream z){ + if(z == null || z.istate == null || z.istate.blocks == null) + return Z_STREAM_ERROR; + return z.istate.blocks.sync_point(); + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/JZlib.java b/app/src/main/java/com/jcraft/jzlib/JZlib.java new file mode 100644 index 0000000..b84d7a1 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/JZlib.java @@ -0,0 +1,67 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class JZlib{ + private static final String version="1.0.2"; + public static String version(){return version;} + + // compression levels + static final public int Z_NO_COMPRESSION=0; + static final public int Z_BEST_SPEED=1; + static final public int Z_BEST_COMPRESSION=9; + static final public int Z_DEFAULT_COMPRESSION=(-1); + + // compression strategy + static final public int Z_FILTERED=1; + static final public int Z_HUFFMAN_ONLY=2; + static final public int Z_DEFAULT_STRATEGY=0; + + static final public int Z_NO_FLUSH=0; + static final public int Z_PARTIAL_FLUSH=1; + static final public int Z_SYNC_FLUSH=2; + static final public int Z_FULL_FLUSH=3; + static final public int Z_FINISH=4; + + static final public int Z_OK=0; + static final public int Z_STREAM_END=1; + static final public int Z_NEED_DICT=2; + static final public int Z_ERRNO=-1; + static final public int Z_STREAM_ERROR=-2; + static final public int Z_DATA_ERROR=-3; + static final public int Z_MEM_ERROR=-4; + static final public int Z_BUF_ERROR=-5; + static final public int Z_VERSION_ERROR=-6; +} diff --git a/app/src/main/java/com/jcraft/jzlib/StaticTree.java b/app/src/main/java/com/jcraft/jzlib/StaticTree.java new file mode 100644 index 0000000..0f7f577 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/StaticTree.java @@ -0,0 +1,149 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class StaticTree{ + static final private int MAX_BITS=15; + + static final private int BL_CODES=19; + static final private int D_CODES=30; + static final private int LITERALS=256; + static final private int LENGTH_CODES=29; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + + // Bit length codes must not exceed MAX_BL_BITS bits + static final int MAX_BL_BITS=7; + + static final short[] static_ltree = { + 12, 8, 140, 8, 76, 8, 204, 8, 44, 8, + 172, 8, 108, 8, 236, 8, 28, 8, 156, 8, + 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, + 252, 8, 2, 8, 130, 8, 66, 8, 194, 8, + 34, 8, 162, 8, 98, 8, 226, 8, 18, 8, + 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, + 114, 8, 242, 8, 10, 8, 138, 8, 74, 8, + 202, 8, 42, 8, 170, 8, 106, 8, 234, 8, + 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, + 186, 8, 122, 8, 250, 8, 6, 8, 134, 8, + 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, + 230, 8, 22, 8, 150, 8, 86, 8, 214, 8, + 54, 8, 182, 8, 118, 8, 246, 8, 14, 8, + 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, + 110, 8, 238, 8, 30, 8, 158, 8, 94, 8, + 222, 8, 62, 8, 190, 8, 126, 8, 254, 8, + 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, + 161, 8, 97, 8, 225, 8, 17, 8, 145, 8, + 81, 8, 209, 8, 49, 8, 177, 8, 113, 8, + 241, 8, 9, 8, 137, 8, 73, 8, 201, 8, + 41, 8, 169, 8, 105, 8, 233, 8, 25, 8, + 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, + 121, 8, 249, 8, 5, 8, 133, 8, 69, 8, + 197, 8, 37, 8, 165, 8, 101, 8, 229, 8, + 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, + 181, 8, 117, 8, 245, 8, 13, 8, 141, 8, + 77, 8, 205, 8, 45, 8, 173, 8, 109, 8, + 237, 8, 29, 8, 157, 8, 93, 8, 221, 8, + 61, 8, 189, 8, 125, 8, 253, 8, 19, 9, + 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, + 211, 9, 467, 9, 51, 9, 307, 9, 179, 9, + 435, 9, 115, 9, 371, 9, 243, 9, 499, 9, + 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, + 331, 9, 203, 9, 459, 9, 43, 9, 299, 9, + 171, 9, 427, 9, 107, 9, 363, 9, 235, 9, + 491, 9, 27, 9, 283, 9, 155, 9, 411, 9, + 91, 9, 347, 9, 219, 9, 475, 9, 59, 9, + 315, 9, 187, 9, 443, 9, 123, 9, 379, 9, + 251, 9, 507, 9, 7, 9, 263, 9, 135, 9, + 391, 9, 71, 9, 327, 9, 199, 9, 455, 9, + 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, + 359, 9, 231, 9, 487, 9, 23, 9, 279, 9, + 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, + 471, 9, 55, 9, 311, 9, 183, 9, 439, 9, + 119, 9, 375, 9, 247, 9, 503, 9, 15, 9, + 271, 9, 143, 9, 399, 9, 79, 9, 335, 9, + 207, 9, 463, 9, 47, 9, 303, 9, 175, 9, + 431, 9, 111, 9, 367, 9, 239, 9, 495, 9, + 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, + 351, 9, 223, 9, 479, 9, 63, 9, 319, 9, + 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, + 511, 9, 0, 7, 64, 7, 32, 7, 96, 7, + 16, 7, 80, 7, 48, 7, 112, 7, 8, 7, + 72, 7, 40, 7, 104, 7, 24, 7, 88, 7, + 56, 7, 120, 7, 4, 7, 68, 7, 36, 7, + 100, 7, 20, 7, 84, 7, 52, 7, 116, 7, + 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, + 163, 8, 99, 8, 227, 8 + }; + + static final short[] static_dtree = { + 0, 5, 16, 5, 8, 5, 24, 5, 4, 5, + 20, 5, 12, 5, 28, 5, 2, 5, 18, 5, + 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, + 30, 5, 1, 5, 17, 5, 9, 5, 25, 5, + 5, 5, 21, 5, 13, 5, 29, 5, 3, 5, + 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 + }; + + static StaticTree static_l_desc = + new StaticTree(static_ltree, Tree.extra_lbits, + LITERALS+1, L_CODES, MAX_BITS); + + static StaticTree static_d_desc = + new StaticTree(static_dtree, Tree.extra_dbits, + 0, D_CODES, MAX_BITS); + + static StaticTree static_bl_desc = + new StaticTree(null, Tree.extra_blbits, + 0, BL_CODES, MAX_BL_BITS); + + short[] static_tree; // static tree or null + int[] extra_bits; // extra bits for each code or null + int extra_base; // base index for extra_bits + int elems; // max number of elements in the tree + int max_length; // max bit length for the codes + + StaticTree(short[] static_tree, + int[] extra_bits, + int extra_base, + int elems, + int max_length + ){ + this.static_tree=static_tree; + this.extra_bits=extra_bits; + this.extra_base=extra_base; + this.elems=elems; + this.max_length=max_length; + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/Tree.java b/app/src/main/java/com/jcraft/jzlib/Tree.java new file mode 100644 index 0000000..8103897 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/Tree.java @@ -0,0 +1,365 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class Tree{ + static final private int MAX_BITS=15; + static final private int BL_CODES=19; + static final private int D_CODES=30; + static final private int LITERALS=256; + static final private int LENGTH_CODES=29; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + static final private int HEAP_SIZE=(2*L_CODES+1); + + // Bit length codes must not exceed MAX_BL_BITS bits + static final int MAX_BL_BITS=7; + + // end of block literal code + static final int END_BLOCK=256; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + static final int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + static final int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + static final int REPZ_11_138=18; + + // extra bits for each length code + static final int[] extra_lbits={ + 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0 + }; + + // extra bits for each distance code + static final int[] extra_dbits={ + 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 + }; + + // extra bits for each bit length code + static final int[] extra_blbits={ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7 + }; + + static final byte[] bl_order={ + 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; + + + // The lengths of the bit length codes are sent in order of decreasing + // probability, to avoid transmitting the lengths for unused bit + // length codes. + + static final int Buf_size=8*2; + + // see definition of array dist_code below + static final int DIST_CODE_LEN=512; + + static final byte[] _dist_code = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, + 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 + }; + + static final byte[] _length_code={ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, + 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, + 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 + }; + + static final int[] base_length = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, + 64, 80, 96, 112, 128, 160, 192, 224, 0 + }; + + static final int[] base_dist = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 + }; + + // Mapping from a distance to a distance code. dist is the distance - 1 and + // must not have side effects. _dist_code[256] and _dist_code[257] are never + // used. + static int d_code(int dist){ + return ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>>7)]); + } + + short[] dyn_tree; // the dynamic tree + int max_code; // largest code with non zero frequency + StaticTree stat_desc; // the corresponding static tree + + // Compute the optimal bit lengths for a tree and update the total bit length + // for the current block. + // IN assertion: the fields freq and dad are set, heap[heap_max] and + // above are the tree nodes sorted by increasing frequency. + // OUT assertions: the field len is set to the optimal bit length, the + // array bl_count contains the frequencies for each bit length. + // The length opt_len is updated; static_len is also updated if stree is + // not null. + void gen_bitlen(Deflate s){ + short[] tree = dyn_tree; + short[] stree = stat_desc.static_tree; + int[] extra = stat_desc.extra_bits; + int base = stat_desc.extra_base; + int max_length = stat_desc.max_length; + int h; // heap index + int n, m; // iterate over the tree elements + int bits; // bit length + int xbits; // extra bits + short f; // frequency + int overflow = 0; // number of elements with bit length too large + + for (bits = 0; bits <= MAX_BITS; bits++) s.bl_count[bits] = 0; + + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + tree[s.heap[s.heap_max]*2+1] = 0; // root of the heap + + for(h=s.heap_max+1; h<HEAP_SIZE; h++){ + n = s.heap[h]; + bits = tree[tree[n*2+1]*2+1] + 1; + if (bits > max_length){ bits = max_length; overflow++; } + tree[n*2+1] = (short)bits; + // We overwrite tree[n*2+1] which is no longer needed + + if (n > max_code) continue; // not a leaf node + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n*2]; + s.opt_len += f * (bits + xbits); + if (stree!=null) s.static_len += f * (stree[n*2+1] + xbits); + } + if (overflow == 0) return; + + // This happens for example on obj2 and pic of the Calgary corpus + // Find the first bit length which could increase: + do { + bits = max_length-1; + while(s.bl_count[bits]==0) bits--; + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits+1]+=2; // move one overflow item as its brother + s.bl_count[max_length]--; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + overflow -= 2; + } + while (overflow > 0); + + for (bits = max_length; bits != 0; bits--) { + n = s.bl_count[bits]; + while (n != 0) { + m = s.heap[--h]; + if (m > max_code) continue; + if (tree[m*2+1] != bits) { + s.opt_len += ((long)bits - (long)tree[m*2+1])*(long)tree[m*2]; + tree[m*2+1] = (short)bits; + } + n--; + } + } + } + + // Construct one Huffman tree and assigns the code bit strings and lengths. + // Update the total bit length for the current block. + // IN assertion: the field freq is set for all tree elements. + // OUT assertions: the fields len and code are set to the optimal bit length + // and corresponding code. The length opt_len is updated; static_len is + // also updated if stree is not null. The field max_code is set. + void build_tree(Deflate s){ + short[] tree=dyn_tree; + short[] stree=stat_desc.static_tree; + int elems=stat_desc.elems; + int n, m; // iterate over heap elements + int max_code=-1; // largest code with non zero frequency + int node; // new node being created + + // Construct the initial heap, with least frequent element in + // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + // heap[0] is not used. + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for(n=0; n<elems; n++) { + if(tree[n*2] != 0) { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + } + else{ + tree[n*2+1] = 0; + } + } + + // The pkzip format requires that at least one distance code exists, + // and that at least one bit should be sent even if there is only one + // possible code. So to avoid special checks later on we force at least + // two codes of non zero frequency. + while (s.heap_len < 2) { + node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); + tree[node*2] = 1; + s.depth[node] = 0; + s.opt_len--; if (stree!=null) s.static_len -= stree[node*2+1]; + // node is 0 or 1 so it does not have extra bits + } + this.max_code = max_code; + + // The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + // establish sub-heaps of increasing lengths: + + for(n=s.heap_len/2;n>=1; n--) + s.pqdownheap(tree, n); + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + node=elems; // next internal node of the tree + do{ + // n = node of least frequency + n=s.heap[1]; + s.heap[1]=s.heap[s.heap_len--]; + s.pqdownheap(tree, 1); + m=s.heap[1]; // m = node of next least frequency + + s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency + s.heap[--s.heap_max] = m; + + // Create a new node father of n and m + tree[node*2] = (short)(tree[n*2] + tree[m*2]); + s.depth[node] = (byte)(Math.max(s.depth[n],s.depth[m])+1); + tree[n*2+1] = tree[m*2+1] = (short)node; + + // and insert the new node in the heap + s.heap[1] = node++; + s.pqdownheap(tree, 1); + } + while(s.heap_len>=2); + + s.heap[--s.heap_max] = s.heap[1]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + + gen_bitlen(s); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, max_code, s.bl_count); + } + + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + static void gen_codes(short[] tree, // the tree to decorate + int max_code, // largest code with non zero frequency + short[] bl_count // number of codes at each bit length + ){ + short[] next_code=new short[MAX_BITS+1]; // next code value for each bit length + short code = 0; // running code value + int bits; // bit index + int n; // code index + + // The distribution counts are first used to generate the code values + // without bit reversal. + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (short)((code + bl_count[bits-1]) << 1); + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1, + // "inconsistent bit counts"); + //Tracev((stderr,"\ngen_codes: max_code %d ", max_code)); + + for (n = 0; n <= max_code; n++) { + int len = tree[n*2+1]; + if (len == 0) continue; + // Now reverse the bits + tree[n*2] = (short)(bi_reverse(next_code[len]++, len)); + } + } + + // Reverse the first len bits of a code, using straightforward code (a faster + // method would use a table) + // IN assertion: 1 <= len <= 15 + static int bi_reverse(int code, // the value to invert + int len // its bit length + ){ + int res = 0; + do{ + res|=code&1; + code>>>=1; + res<<=1; + } + while(--len>0); + return res>>>1; + } +} + diff --git a/app/src/main/java/com/jcraft/jzlib/ZInputStream.java b/app/src/main/java/com/jcraft/jzlib/ZInputStream.java new file mode 100644 index 0000000..3f97c44 --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/ZInputStream.java @@ -0,0 +1,149 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2001 Lapo Luchini. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 AUTHORS +OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; +import java.io.*; + +public class ZInputStream extends FilterInputStream { + + protected ZStream z=new ZStream(); + protected int bufsize=512; + protected int flush=JZlib.Z_NO_FLUSH; + protected byte[] buf=new byte[bufsize], + buf1=new byte[1]; + protected boolean compress; + + protected InputStream in=null; + + public ZInputStream(InputStream in) { + this(in, false); + } + public ZInputStream(InputStream in, boolean nowrap) { + super(in); + this.in=in; + z.inflateInit(nowrap); + compress=false; + z.next_in=buf; + z.next_in_index=0; + z.avail_in=0; + } + + public ZInputStream(InputStream in, int level) { + super(in); + this.in=in; + z.deflateInit(level); + compress=true; + z.next_in=buf; + z.next_in_index=0; + z.avail_in=0; + } + + /*public int available() throws IOException { + return inf.finished() ? 0 : 1; + }*/ + + public int read() throws IOException { + if(read(buf1, 0, 1)==-1) + return(-1); + return(buf1[0]&0xFF); + } + + private boolean nomoreinput=false; + + public int read(byte[] b, int off, int len) throws IOException { + if(len==0) + return(0); + int err; + z.next_out=b; + z.next_out_index=off; + z.avail_out=len; + do { + if((z.avail_in==0)&&(!nomoreinput)) { // if buffer is empty and more input is avaiable, refill it + z.next_in_index=0; + z.avail_in=in.read(buf, 0, bufsize);//(bufsize<z.avail_out ? bufsize : z.avail_out)); + if(z.avail_in==-1) { + z.avail_in=0; + nomoreinput=true; + } + } + if(compress) + err=z.deflate(flush); + else + err=z.inflate(flush); + if(nomoreinput&&(err==JZlib.Z_BUF_ERROR)) + return(-1); + if(err!=JZlib.Z_OK && err!=JZlib.Z_STREAM_END) + throw new ZStreamException((compress ? "de" : "in")+"flating: "+z.msg); + if((nomoreinput||err==JZlib.Z_STREAM_END)&&(z.avail_out==len)) + return(-1); + } + while(z.avail_out==len&&err==JZlib.Z_OK); + //System.err.print("("+(len-z.avail_out)+")"); + return(len-z.avail_out); + } + + public long skip(long n) throws IOException { + int len=512; + if(n<len) + len=(int)n; + byte[] tmp=new byte[len]; + return((long)read(tmp)); + } + + public int getFlushMode() { + return(flush); + } + + public void setFlushMode(int flush) { + this.flush=flush; + } + + /** + * Returns the total number of bytes input so far. + */ + public long getTotalIn() { + return z.total_in; + } + + /** + * Returns the total number of bytes output so far. + */ + public long getTotalOut() { + return z.total_out; + } + + public void close() throws IOException{ + in.close(); + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/ZOutputStream.java b/app/src/main/java/com/jcraft/jzlib/ZOutputStream.java new file mode 100644 index 0000000..afee65b --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/ZOutputStream.java @@ -0,0 +1,156 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2001 Lapo Luchini. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 AUTHORS +OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; +import java.io.*; + +public class ZOutputStream extends OutputStream { + + protected ZStream z=new ZStream(); + protected int bufsize=512; + protected int flush=JZlib.Z_NO_FLUSH; + protected byte[] buf=new byte[bufsize], + buf1=new byte[1]; + protected boolean compress; + + protected OutputStream out; + + public ZOutputStream(OutputStream out) { + super(); + this.out=out; + z.inflateInit(); + compress=false; + } + + public ZOutputStream(OutputStream out, int level) { + this(out, level, false); + } + public ZOutputStream(OutputStream out, int level, boolean nowrap) { + super(); + this.out=out; + z.deflateInit(level, nowrap); + compress=true; + } + + public void write(int b) throws IOException { + buf1[0]=(byte)b; + write(buf1, 0, 1); + } + + public void write(byte b[], int off, int len) throws IOException { + if(len==0) + return; + int err; + z.next_in=b; + z.next_in_index=off; + z.avail_in=len; + do{ + z.next_out=buf; + z.next_out_index=0; + z.avail_out=bufsize; + if(compress) + err=z.deflate(flush); + else + err=z.inflate(flush); + if(err!=JZlib.Z_OK) + throw new ZStreamException((compress?"de":"in")+"flating: "+z.msg); + out.write(buf, 0, bufsize-z.avail_out); + } + while(z.avail_in>0 || z.avail_out==0); + } + + public int getFlushMode() { + return(flush); + } + + public void setFlushMode(int flush) { + this.flush=flush; + } + + public void finish() throws IOException { + int err; + do{ + z.next_out=buf; + z.next_out_index=0; + z.avail_out=bufsize; + if(compress){ err=z.deflate(JZlib.Z_FINISH); } + else{ err=z.inflate(JZlib.Z_FINISH); } + if(err!=JZlib.Z_STREAM_END && err != JZlib.Z_OK) + throw new ZStreamException((compress?"de":"in")+"flating: "+z.msg); + if(bufsize-z.avail_out>0){ + out.write(buf, 0, bufsize-z.avail_out); + } + } + while(z.avail_in>0 || z.avail_out==0); + flush(); + } + public void end() { + if(z==null) + return; + if(compress){ z.deflateEnd(); } + else{ z.inflateEnd(); } + z.free(); + z=null; + } + public void close() throws IOException { + try{ + try{finish();} + catch (IOException ignored) {} + } + finally{ + end(); + out.close(); + out=null; + } + } + + /** + * Returns the total number of bytes input so far. + */ + public long getTotalIn() { + return z.total_in; + } + + /** + * Returns the total number of bytes output so far. + */ + public long getTotalOut() { + return z.total_out; + } + + public void flush() throws IOException { + out.flush(); + } + +} diff --git a/app/src/main/java/com/jcraft/jzlib/ZStream.java b/app/src/main/java/com/jcraft/jzlib/ZStream.java new file mode 100644 index 0000000..334475e --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/ZStream.java @@ -0,0 +1,211 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class ZStream{ + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_WBITS=MAX_WBITS; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + public byte[] next_in; // next input byte + public int next_in_index; + public int avail_in; // number of bytes available at next_in + public long total_in; // total nb of input bytes read so far + + public byte[] next_out; // next output byte should be put there + public int next_out_index; + public int avail_out; // remaining free space at next_out + public long total_out; // total nb of bytes output so far + + public String msg; + + Deflate dstate; + Inflate istate; + + int data_type; // best guess about the data type: ascii or binary + + public long adler; + Adler32 _adler=new Adler32(); + + public int inflateInit(){ + return inflateInit(DEF_WBITS); + } + public int inflateInit(boolean nowrap){ + return inflateInit(DEF_WBITS, nowrap); + } + public int inflateInit(int w){ + return inflateInit(w, false); + } + + public int inflateInit(int w, boolean nowrap){ + istate=new Inflate(); + return istate.inflateInit(this, nowrap?-w:w); + } + + public int inflate(int f){ + if(istate==null) return Z_STREAM_ERROR; + return istate.inflate(this, f); + } + public int inflateEnd(){ + if(istate==null) return Z_STREAM_ERROR; + int ret=istate.inflateEnd(this); + istate = null; + return ret; + } + public int inflateSync(){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSync(this); + } + public int inflateSetDictionary(byte[] dictionary, int dictLength){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSetDictionary(this, dictionary, dictLength); + } + + public int deflateInit(int level){ + return deflateInit(level, MAX_WBITS); + } + public int deflateInit(int level, boolean nowrap){ + return deflateInit(level, MAX_WBITS, nowrap); + } + public int deflateInit(int level, int bits){ + return deflateInit(level, bits, false); + } + public int deflateInit(int level, int bits, boolean nowrap){ + dstate=new Deflate(); + return dstate.deflateInit(this, level, nowrap?-bits:bits); + } + public int deflate(int flush){ + if(dstate==null){ + return Z_STREAM_ERROR; + } + return dstate.deflate(this, flush); + } + public int deflateEnd(){ + if(dstate==null) return Z_STREAM_ERROR; + int ret=dstate.deflateEnd(); + dstate=null; + return ret; + } + public int deflateParams(int level, int strategy){ + if(dstate==null) return Z_STREAM_ERROR; + return dstate.deflateParams(this, level, strategy); + } + public int deflateSetDictionary (byte[] dictionary, int dictLength){ + if(dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateSetDictionary(this, dictionary, dictLength); + } + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + void flush_pending(){ + int len=dstate.pending; + + if(len>avail_out) len=avail_out; + if(len==0) return; + + if(dstate.pending_buf.length<=dstate.pending_out || + next_out.length<=next_out_index || + dstate.pending_buf.length<(dstate.pending_out+len) || + next_out.length<(next_out_index+len)){ + System.out.println(dstate.pending_buf.length+", "+dstate.pending_out+ + ", "+next_out.length+", "+next_out_index+", "+len); + System.out.println("avail_out="+avail_out); + } + + System.arraycopy(dstate.pending_buf, dstate.pending_out, + next_out, next_out_index, len); + + next_out_index+=len; + dstate.pending_out+=len; + total_out+=len; + avail_out-=len; + dstate.pending-=len; + if(dstate.pending==0){ + dstate.pending_out=0; + } + } + + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + int read_buf(byte[] buf, int start, int size) { + int len=avail_in; + + if(len>size) len=size; + if(len==0) return 0; + + avail_in-=len; + + if(dstate.noheader==0) { + adler=_adler.adler32(adler, next_in, next_in_index, len); + } + System.arraycopy(next_in, next_in_index, buf, start, len); + next_in_index += len; + total_in += len; + return len; + } + + public void free(){ + next_in=null; + next_out=null; + msg=null; + _adler=null; + } +} diff --git a/app/src/main/java/com/jcraft/jzlib/ZStreamException.java b/app/src/main/java/com/jcraft/jzlib/ZStreamException.java new file mode 100644 index 0000000..308bb8a --- /dev/null +++ b/app/src/main/java/com/jcraft/jzlib/ZStreamException.java @@ -0,0 +1,44 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 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. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public class ZStreamException extends java.io.IOException { + public ZStreamException() { + super(); + } + public ZStreamException(String s) { + super(s); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/AuthAgentCallback.java b/app/src/main/java/com/trilead/ssh2/AuthAgentCallback.java new file mode 100644 index 0000000..7fe270b --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/AuthAgentCallback.java @@ -0,0 +1,64 @@ +package com.trilead.ssh2; + +import java.security.KeyPair; +import java.util.Map; + +/** + * AuthAgentCallback. + * + * @author Kenny Root + * @version $Id$ + */ +public interface AuthAgentCallback { + + /** + * @return array of blobs containing the OpenSSH-format encoded public keys + */ + Map<String,byte[]> retrieveIdentities(); + + /** + * @param key A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code> + * containing a DSA or RSA private key of + * the user in Trilead object format. + * @param comment comment associated with this key + * @param confirmUse whether to prompt before using this key + * @param lifetime lifetime in seconds for key to be remembered + * @return success or failure + */ + boolean addIdentity(KeyPair pair, String comment, boolean confirmUse, int lifetime); + + /** + * @param publicKey byte blob containing the OpenSSH-format encoded public key + * @return success or failure + */ + boolean removeIdentity(byte[] publicKey); + + /** + * @return success or failure + */ + boolean removeAllIdentities(); + + /** + * @param publicKey byte blob containing the OpenSSH-format encoded public key + * @return A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code> + * containing a DSA or RSA private key of + * the user in Trilead object format. + */ + KeyPair getKeyPair(byte[] publicKey); + + /** + * @return + */ + boolean isAgentLocked(); + + /** + * @param lockPassphrase + */ + boolean setAgentLock(String lockPassphrase); + + /** + * @param unlockPassphrase + * @return + */ + boolean requestAgentUnlock(String unlockPassphrase); +} diff --git a/app/src/main/java/com/trilead/ssh2/ChannelCondition.java b/app/src/main/java/com/trilead/ssh2/ChannelCondition.java new file mode 100644 index 0000000..df1ad50 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/ChannelCondition.java @@ -0,0 +1,61 @@ + +package com.trilead.ssh2; + +/** + * Contains constants that can be used to specify what conditions to wait for on + * a SSH-2 channel (e.g., represented by a {@link Session}). + * + * @see Session#waitForCondition(int, long) + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ChannelCondition.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public abstract interface ChannelCondition +{ + /** + * A timeout has occurred, none of your requested conditions is fulfilled. + * However, other conditions may be true - therefore, NEVER use the "==" + * operator to test for this (or any other) condition. Always use + * something like <code>((cond & ChannelCondition.CLOSED) != 0)</code>. + */ + public static final int TIMEOUT = 1; + + /** + * The underlying SSH-2 channel, however not necessarily the whole connection, + * has been closed. This implies <code>EOF</code>. Note that there may still + * be unread stdout or stderr data in the local window, i.e, <code>STDOUT_DATA</code> + * or/and <code>STDERR_DATA</code> may be set at the same time. + */ + public static final int CLOSED = 2; + + /** + * There is stdout data available that is ready to be consumed. + */ + public static final int STDOUT_DATA = 4; + + /** + * There is stderr data available that is ready to be consumed. + */ + public static final int STDERR_DATA = 8; + + /** + * EOF on has been reached, no more _new_ stdout or stderr data will arrive + * from the remote server. However, there may be unread stdout or stderr + * data, i.e, <code>STDOUT_DATA</code> or/and <code>STDERR_DATA</code> + * may be set at the same time. + */ + public static final int EOF = 16; + + /** + * The exit status of the remote process is available. + * Some servers never send the exist status, or occasionally "forget" to do so. + */ + public static final int EXIT_STATUS = 32; + + /** + * The exit signal of the remote process is available. + */ + public static final int EXIT_SIGNAL = 64; + +} diff --git a/app/src/main/java/com/trilead/ssh2/Connection.java b/app/src/main/java/com/trilead/ssh2/Connection.java new file mode 100644 index 0000000..163fdb5 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/Connection.java @@ -0,0 +1,1628 @@ + +package com.trilead.ssh2; + +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.security.KeyPair; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Set; +import java.util.Vector; + +import com.trilead.ssh2.auth.AuthenticationManager; +import com.trilead.ssh2.channel.ChannelManager; +import com.trilead.ssh2.crypto.CryptoWishList; +import com.trilead.ssh2.crypto.cipher.BlockCipherFactory; +import com.trilead.ssh2.crypto.digest.MAC; +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.PacketIgnore; +import com.trilead.ssh2.transport.KexManager; +import com.trilead.ssh2.transport.TransportManager; +import com.trilead.ssh2.util.TimeoutService; +import com.trilead.ssh2.util.TimeoutService.TimeoutToken; + +/** + * A <code>Connection</code> is used to establish an encrypted TCP/IP + * connection to a SSH-2 server. + * <p> + * Typically, one + * <ol> + * <li>creates a {@link #Connection(String) Connection} object.</li> + * <li>calls the {@link #connect() connect()} method.</li> + * <li>calls some of the authentication methods (e.g., + * {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).</li> + * <li>calls one or several times the {@link #openSession() openSession()} + * method.</li> + * <li>finally, one must close the connection and release resources with the + * {@link #close() close()} method.</li> + * </ol> + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Connection.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $ + */ + +public class Connection +{ + /** + * The identifier presented to the SSH-2 server. + */ + public final static String identification = "TrileadSSH2Java_213"; + + /** + * Will be used to generate all random data needed for the current + * connection. Note: SecureRandom.nextBytes() is thread safe. + */ + private SecureRandom generator; + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported cipher algorithms by this implementation. + */ + public static synchronized String[] getAvailableCiphers() + { + return BlockCipherFactory.getDefaultCipherList(); + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported MAC algorthims by this implementation. + */ + public static synchronized String[] getAvailableMACs() + { + return MAC.getMacList(); + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported server host key algorthims by this + * implementation. + */ + public static synchronized String[] getAvailableServerHostKeyAlgorithms() + { + return KexManager.getDefaultServerHostkeyAlgorithmList(); + } + + private AuthenticationManager am; + + private boolean authenticated = false; + private boolean compression = false; + private ChannelManager cm; + + private CryptoWishList cryptoWishList = new CryptoWishList(); + + private DHGexParameters dhgexpara = new DHGexParameters(); + + private final String hostname; + + private final int port; + + private TransportManager tm; + + private boolean tcpNoDelay = false; + + private ProxyData proxyData = null; + + private Vector<ConnectionMonitor> connectionMonitors = new Vector<ConnectionMonitor>(); + + /** + * Prepares a fresh <code>Connection</code> object which can then be used + * to establish a connection to the specified SSH-2 server. + * <p> + * Same as {@link #Connection(String, int) Connection(hostname, 22)}. + * + * @param hostname + * the hostname of the SSH-2 server. + */ + public Connection(String hostname) + { + this(hostname, 22); + } + + /** + * Prepares a fresh <code>Connection</code> object which can then be used + * to establish a connection to the specified SSH-2 server. + * + * @param hostname + * the host where we later want to connect to. + * @param port + * port on the server, normally 22. + */ + public Connection(String hostname, int port) + { + this.hostname = hostname; + this.port = port; + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * is based on DSA (it uses DSA to sign a challenge sent by the server). + * <p> + * If the authentication phase is complete, <code>true</code> will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), <code>false</code> is returned and + * one can retry either by using this or any other authentication method + * (use the <code>getRemainingAuthMethods</code> method to get a list of + * the remaining possible methods). + * + * @param user + * A <code>String</code> holding the username. + * @param pem + * A <code>String</code> containing the DSA private key of the + * user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain + * linefeeds. + * @param password + * If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you + * must specify the password. Otherwise, this argument will be + * ignored and can be set to <code>null</code>. + * + * @return whether the connection is now authenticated. + * @throws IOException + * + * @deprecated You should use one of the + * {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()} + * methods, this method is just a wrapper for it and will + * disappear in future builds. + * + */ + public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException + { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (pem == null) + throw new IllegalArgumentException("pem argument is null"); + + authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND()); + + return authenticated; + } + + /** + * A wrapper that calls + * {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback) + * authenticateWithKeyboardInteractivewith} a <code>null</code> submethod + * list. + * + * @param user + * A <code>String</code> holding the username. + * @param cb + * An <code>InteractiveCallback</code> which will be used to + * determine the responses to the questions asked by the server. + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb) + throws IOException + { + return authenticateWithKeyboardInteractive(user, null, cb); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * is based on "keyboard-interactive", specified in + * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a + * callback object which will be feeded with challenges generated by the + * server. Answers are then sent back to the server. It is possible that the + * callback will be called several times during the invocation of this + * method (e.g., if the server replies to the callback's answer(s) with + * another challenge...) + * <p> + * If the authentication phase is complete, <code>true</code> will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), <code>false</code> is returned and + * one can retry either by using this or any other authentication method + * (use the <code>getRemainingAuthMethods</code> method to get a list of + * the remaining possible methods). + * <p> + * Note: some SSH servers advertise "keyboard-interactive", however, any + * interactive request will be denied (without having sent any challenge to + * the client). + * + * @param user + * A <code>String</code> holding the username. + * @param submethods + * An array of submethod names, see + * draft-ietf-secsh-auth-kbdinteract-XX. May be <code>null</code> + * to indicate an empty list. + * @param cb + * An <code>InteractiveCallback</code> which will be used to + * determine the responses to the questions asked by the server. + * + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods, + InteractiveCallback cb) throws IOException + { + if (cb == null) + throw new IllegalArgumentException("Callback may not ne NULL!"); + + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + authenticated = am.authenticateInteractive(user, submethods, cb); + + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * sends username and password to the server. + * <p> + * If the authentication phase is complete, <code>true</code> will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), <code>false</code> is returned and + * one can retry either by using this or any other authentication method + * (use the <code>getRemainingAuthMethods</code> method to get a list of + * the remaining possible methods). + * <p> + * Note: if this method fails, then please double-check that it is actually + * offered by the server (use + * {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}. + * <p> + * Often, password authentication is disabled, but users are not aware of + * it. Many servers only offer "publickey" and "keyboard-interactive". + * However, even though "keyboard-interactive" *feels* like password + * authentication (e.g., when using the putty or openssh clients) it is + * *not* the same mechanism. + * + * @param user + * @param password + * @return if the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithPassword(String user, String password) throws IOException + { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (password == null) + throw new IllegalArgumentException("password argument is null"); + + authenticated = am.authenticatePassword(user, password); + + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * can be used to explicitly use the special "none" authentication method + * (where only a username has to be specified). + * <p> + * Note 1: The "none" method may always be tried by clients, however as by + * the specs, the server will not explicitly announce it. In other words, + * the "none" token will never show up in the list returned by + * {@link #getRemainingAuthMethods(String)}. + * <p> + * Note 2: no matter which one of the authenticateWithXXX() methods you + * call, the library will always issue exactly one initial "none" + * authentication request to retrieve the initially allowed list of + * authentication methods by the server. Please read RFC 4252 for the + * details. + * <p> + * If the authentication phase is complete, <code>true</code> will be + * returned. If further authentication steps are needed, <code>false</code> + * is returned and one can retry by any other authentication method (use the + * <code>getRemainingAuthMethods</code> method to get a list of the + * remaining possible methods). + * + * @param user + * @return if the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithNone(String user) throws IOException + { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + /* Trigger the sending of the PacketUserauthRequestNone packet */ + /* (if not already done) */ + + authenticated = am.authenticateNone(user); + + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. The + * authentication method "publickey" works by signing a challenge sent by + * the server. The signature is either DSA or RSA based - it just depends on + * the type of private key you specify, either a DSA or RSA private key in + * PEM format. And yes, this is may seem to be a little confusing, the + * method is called "publickey" in the SSH-2 protocol specification, however + * since we need to generate a signature, you actually have to supply a + * private key =). + * <p> + * The private key contained in the PEM file may also be encrypted + * ("Proc-Type: 4,ENCRYPTED"). The library supports DES-CBC and DES-EDE3-CBC + * encryption, as well as the more exotic PEM encrpytions AES-128-CBC, + * AES-192-CBC and AES-256-CBC. + * <p> + * If the authentication phase is complete, <code>true</code> will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), <code>false</code> is returned and + * one can retry either by using this or any other authentication method + * (use the <code>getRemainingAuthMethods</code> method to get a list of + * the remaining possible methods). + * <p> + * NOTE PUTTY USERS: Event though your key file may start with + * "-----BEGIN..." it is not in the expected format. You have to convert it + * to the OpenSSH key format by using the "puttygen" tool (can be downloaded + * from the Putty website). Simply load your key and then use the + * "Conversions/Export OpenSSH key" functionality to get a proper PEM file. + * + * @param user + * A <code>String</code> holding the username. + * @param pemPrivateKey + * A <code>char[]</code> containing a DSA or RSA private key of + * the user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE + * KEY-----" tag). The char array may contain + * linebreaks/linefeeds. + * @param password + * If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") + * then you must specify a password. Otherwise, this argument + * will be ignored and can be set to <code>null</code>. + * + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password) + throws IOException + { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (pemPrivateKey == null) + throw new IllegalArgumentException("pemPrivateKey argument is null"); + + authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND()); + + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. The + * authentication method "publickey" works by signing a challenge sent by + * the server. The signature is either DSA or RSA based - it just depends on + * the type of private key you specify, either a DSA or RSA private key in + * PEM format. And yes, this is may seem to be a little confusing, the + * method is called "publickey" in the SSH-2 protocol specification, however + * since we need to generate a signature, you actually have to supply a + * private key =). + * <p> + * If the authentication phase is complete, <code>true</code> will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), <code>false</code> is returned and + * one can retry either by using this or any other authentication method + * (use the <code>getRemainingAuthMethods</code> method to get a list of + * the remaining possible methods). + * + * @param user + * A <code>String</code> holding the username. + * @param key + * A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code> + * containing a DSA or RSA private key of + * the user in Trilead object format. + * + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithPublicKey(String user, KeyPair pair) + throws IOException + { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (pair == null) + throw new IllegalArgumentException("Key pair argument is null"); + + authenticated = am.authenticatePublicKey(user, pair, getOrCreateSecureRND()); + + return authenticated; + } + /** + * A convenience wrapper function which reads in a private key (PEM format, + * either DSA or RSA) and then calls + * <code>authenticateWithPublicKey(String, char[], String)</code>. + * <p> + * NOTE PUTTY USERS: Event though your key file may start with + * "-----BEGIN..." it is not in the expected format. You have to convert it + * to the OpenSSH key format by using the "puttygen" tool (can be downloaded + * from the Putty website). Simply load your key and then use the + * "Conversions/Export OpenSSH key" functionality to get a proper PEM file. + * + * @param user + * A <code>String</code> holding the username. + * @param pemFile + * A <code>File</code> object pointing to a file containing a + * DSA or RSA private key of the user in OpenSSH key format (PEM, + * you can't miss the "-----BEGIN DSA PRIVATE KEY-----" or + * "-----BEGIN RSA PRIVATE KEY-----" tag). + * @param password + * If the PEM file is encrypted then you must specify the + * password. Otherwise, this argument will be ignored and can be + * set to <code>null</code>. + * + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password) + throws IOException + { + if (pemFile == null) + throw new IllegalArgumentException("pemFile argument is null"); + + char[] buff = new char[256]; + + CharArrayWriter cw = new CharArrayWriter(); + + FileReader fr = new FileReader(pemFile); + + while (true) + { + int len = fr.read(buff); + if (len < 0) + break; + cw.write(buff, 0, len); + } + + fr.close(); + + return authenticateWithPublicKey(user, cw.toCharArray(), password); + } + + /** + * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any + * time, but it is best to add connection monitors before invoking + * <code>connect()</code> to avoid glitches (e.g., you add a connection + * monitor after a successful connect(), but the connection has died in the + * mean time. Then, your connection monitor won't be notified.) + * <p> + * You can add as many monitors as you like. + * + * @see ConnectionMonitor + * + * @param cmon + * An object implementing the <code>ConnectionMonitor</code> + * interface. + */ + public synchronized void addConnectionMonitor(ConnectionMonitor cmon) + { + if (cmon == null) + throw new IllegalArgumentException("cmon argument is null"); + + connectionMonitors.addElement(cmon); + + if (tm != null) + tm.setConnectionMonitors(connectionMonitors); + } + + /** + * Controls whether compression is used on the link or not. + * <p> + * Note: This can only be called before connect() + * @param enabled whether to enable compression + * @throws IOException + */ + public synchronized void setCompression(boolean enabled) throws IOException { + if (tm != null) + throw new IOException("Connection to " + hostname + " is already in connected state!"); + + compression = enabled; + } + + /** + * Close the connection to the SSH-2 server. All assigned sessions will be + * closed, too. Can be called at any time. Don't forget to call this once + * you don't need a connection anymore - otherwise the receiver thread may + * run forever. + */ + public synchronized void close() + { + Throwable t = new Throwable("Closed due to user request."); + close(t, false); + } + + private void close(Throwable t, boolean hard) + { + if (cm != null) + cm.closeAllChannels(); + + if (tm != null) + { + tm.close(t, hard == false); + tm = null; + } + am = null; + cm = null; + authenticated = false; + } + + /** + * Same as + * {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}. + * + * @return see comments for the + * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} + * method. + * @throws IOException + */ + public synchronized ConnectionInfo connect() throws IOException + { + return connect(null, 0, 0); + } + + /** + * Same as + * {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}. + * + * @return see comments for the + * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} + * method. + * @throws IOException + */ + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException + { + return connect(verifier, 0, 0); + } + + /** + * Connect to the SSH-2 server and, as soon as the server has presented its + * host key, use the + * {@link ServerHostKeyVerifier#verifyServerHostKey(String, int, String, + * byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method of the + * <code>verifier</code> to ask for permission to proceed. If + * <code>verifier</code> is <code>null</code>, then any host key will + * be accepted - this is NOT recommended, since it makes man-in-the-middle + * attackes VERY easy (somebody could put a proxy SSH server between you and + * the real server). + * <p> + * Note: The verifier will be called before doing any crypto calculations + * (i.e., diffie-hellman). Therefore, if you don't like the presented host + * key then no CPU cycles are wasted (and the evil server has less + * information about us). + * <p> + * However, it is still possible that the server presented a fake host key: + * the server cheated (typically a sign for a man-in-the-middle attack) and + * is not able to generate a signature that matches its host key. Don't + * worry, the library will detect such a scenario later when checking the + * signature (the signature cannot be checked before having completed the + * diffie-hellman exchange). + * <p> + * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, int, + * String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method will + * *NOT* be called from the current thread, the call is being made from a + * background thread (there is a background dispatcher thread for every + * established connection). + * <p> + * Note 3: This method will block as long as the key exchange of the + * underlying connection has not been completed (and you have not specified + * any timeouts). + * <p> + * Note 4: If you want to re-use a connection object that was successfully + * connected, then you must call the {@link #close()} method before invoking + * <code>connect()</code> again. + * + * @param verifier + * An object that implements the {@link ServerHostKeyVerifier} + * interface. Pass <code>null</code> to accept any server host + * key - NOT recommended. + * + * @param connectTimeout + * Connect the underlying TCP socket to the server with the given + * timeout value (non-negative, in milliseconds). Zero means no + * timeout. If a proxy is being used (see + * {@link #setProxyData(ProxyData)}), then this timeout is used + * for the connection establishment to the proxy. + * + * @param kexTimeout + * Timeout for complete connection establishment (non-negative, + * in milliseconds). Zero means no timeout. The timeout counts + * from the moment you invoke the connect() method and is + * cancelled as soon as the first key-exchange round has + * finished. It is possible that the timeout event will be fired + * during the invocation of the <code>verifier</code> callback, + * but it will only have an effect after the + * <code>verifier</code> returns. + * + * @return A {@link ConnectionInfo} object containing the details of the + * established connection. + * + * @throws IOException + * If any problem occurs, e.g., the server's host key is not + * accepted by the <code>verifier</code> or there is problem + * during the initial crypto setup (e.g., the signature sent by + * the server is wrong). + * <p> + * In case of a timeout (either connectTimeout or kexTimeout) a + * SocketTimeoutException is thrown. + * <p> + * An exception may also be thrown if the connection was already + * successfully connected (no matter if the connection broke in + * the mean time) and you invoke <code>connect()</code> again + * without having called {@link #close()} first. + * <p> + * If a HTTP proxy is being used and the proxy refuses the + * connection, then a {@link HTTPProxyException} may be thrown, + * which contains the details returned by the proxy. If the + * proxy is buggy and does not return a proper HTTP response, + * then a normal IOException is thrown instead. + */ + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) + throws IOException + { + final class TimeoutState + { + boolean isCancelled = false; + boolean timeoutSocketClosed = false; + } + + if (tm != null) + throw new IOException("Connection to " + hostname + " is already in connected state!"); + + if (connectTimeout < 0) + throw new IllegalArgumentException("connectTimeout must be non-negative!"); + + if (kexTimeout < 0) + throw new IllegalArgumentException("kexTimeout must be non-negative!"); + + final TimeoutState state = new TimeoutState(); + + tm = new TransportManager(hostname, port); + + tm.setConnectionMonitors(connectionMonitors); + + // Don't offer compression if not requested + if (!compression) { + cryptoWishList.c2s_comp_algos = new String[] { "none" }; + cryptoWishList.s2c_comp_algos = new String[] { "none" }; + } + + /* + * Make sure that the runnable below will observe the new value of "tm" + * and "state" (the runnable will be executed in a different thread, + * which may be already running, that is why we need a memory barrier + * here). See also the comment in Channel.java if you are interested in + * the details. + * + * OKOK, this is paranoid since adding the runnable to the todo list of + * the TimeoutService will ensure that all writes have been flushed + * before the Runnable reads anything (there is a synchronized block in + * TimeoutService.addTimeoutHandler). + */ + + synchronized (tm) + { + /* We could actually synchronize on anything. */ + } + + try + { + TimeoutToken token = null; + + if (kexTimeout > 0) + { + final Runnable timeoutHandler = new Runnable() + { + public void run() + { + synchronized (state) + { + if (state.isCancelled) + return; + state.timeoutSocketClosed = true; + tm.close(new SocketTimeoutException("The connect timeout expired"), false); + } + } + }; + + long timeoutHorizont = System.currentTimeMillis() + kexTimeout; + + token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler); + } + + try + { + tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, getOrCreateSecureRND(), proxyData); + } + catch (SocketTimeoutException se) + { + throw (SocketTimeoutException) new SocketTimeoutException( + "The connect() operation on the socket timed out.").initCause(se); + } + + tm.setTcpNoDelay(tcpNoDelay); + + /* Wait until first KEX has finished */ + + ConnectionInfo ci = tm.getConnectionInfo(1); + + /* Now try to cancel the timeout, if needed */ + + if (token != null) + { + TimeoutService.cancelTimeoutHandler(token); + + /* Were we too late? */ + + synchronized (state) + { + if (state.timeoutSocketClosed) + throw new IOException("This exception will be replaced by the one below =)"); + /* + * Just in case the "cancelTimeoutHandler" invocation came + * just a little bit too late but the handler did not enter + * the semaphore yet - we can still stop it. + */ + state.isCancelled = true; + } + } + + return ci; + } + catch (SocketTimeoutException ste) + { + throw ste; + } + catch (IOException e1) + { + /* This will also invoke any registered connection monitors */ + close(new Throwable("There was a problem during connect."), false); + + synchronized (state) + { + /* + * Show a clean exception, not something like "the socket is + * closed!?!" + */ + if (state.timeoutSocketClosed) + throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired."); + } + + /* Do not wrap a HTTPProxyException */ + if (e1 instanceof HTTPProxyException) + throw e1; + + throw (IOException) new IOException("There was a problem while connecting to " + hostname + ":" + port) + .initCause(e1); + } + } + + /** + * Creates a new {@link LocalPortForwarder}. A + * <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host (which may or may + * not be identical to the remote SSH-2 server). + * <p> + * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param local_port + * the local port the LocalPortForwarder shall bind to. + * @param host_to_connect + * target address (IP or hostname) + * @param port_to_connect + * target port + * @return A {@link LocalPortForwarder} object. + * @throws IOException + */ + public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect, + int port_to_connect) throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link LocalPortForwarder}. A + * <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host (which may or may + * not be identical to the remote SSH-2 server). + * <p> + * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param addr + * specifies the InetSocketAddress where the local socket shall + * be bound to. + * @param host_to_connect + * target address (IP or hostname) + * @param port_to_connect + * target port + * @return A {@link LocalPortForwarder} object. + * @throws IOException + */ + public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect, + int port_to_connect) throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link LocalStreamForwarder}. A + * <code>LocalStreamForwarder</code> manages an Input/Outputstream pair + * that is being forwarded via the secure tunnel into a TCP/IP connection to + * another host (which may or may not be identical to the remote SSH-2 + * server). + * + * @param host_to_connect + * @param port_to_connect + * @return A {@link LocalStreamForwarder} object. + * @throws IOException + */ + public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect) + throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot forward, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward, connection is not authenticated."); + + return new LocalStreamForwarder(cm, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link DynamicPortForwarder}. A + * <code>DynamicPortForwarder</code> forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host that is chosen via + * the SOCKS protocol. + * <p> + * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param local_port + * @return A {@link DynamicPortForwarder} object. + * @throws IOException + */ + public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new DynamicPortForwarder(cm, local_port); + } + + /** + * Creates a new {@link DynamicPortForwarder}. A + * <code>DynamicPortForwarder</code> forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host that is chosen via + * the SOCKS protocol. + * <p> + * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param addr + * specifies the InetSocketAddress where the local socket shall + * be bound to. + * @return A {@link DynamicPortForwarder} object. + * @throws IOException + */ + public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new DynamicPortForwarder(cm, addr); + } + + /** + * Create a very basic {@link SCPClient} that can be used to copy files + * from/to the SSH-2 server. + * <p> + * Works only after one has passed successfully the authentication step. + * There is no limit on the number of concurrent SCP clients. + * <p> + * Note: This factory method will probably disappear in the future. + * + * @return A {@link SCPClient} object. + * @throws IOException + */ + public synchronized SCPClient createSCPClient() throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot create SCP client, connection is not authenticated."); + + return new SCPClient(this); + } + + /** + * Force an asynchronous key re-exchange (the call does not block). The + * latest values set for MAC, Cipher and DH group exchange parameters will + * be used. If a key exchange is currently in progress, then this method has + * the only effect that the so far specified parameters will be used for the + * next (server driven) key exchange. + * <p> + * Note: This implementation will never start a key exchange (other than the + * initial one) unless you or the SSH-2 server ask for it. + * + * @throws IOException + * In case of any failure behind the scenes. + */ + public synchronized void forceKeyExchange() throws IOException + { + if (tm == null) + throw new IllegalStateException("You need to establish a connection first."); + + tm.forceKeyExchange(cryptoWishList, dhgexpara); + } + + /** + * Returns the hostname that was passed to the constructor. + * + * @return the hostname + */ + public synchronized String getHostname() + { + return hostname; + } + + /** + * Returns the port that was passed to the constructor. + * + * @return the TCP port + */ + public synchronized int getPort() + { + return port; + } + + /** + * Returns a {@link ConnectionInfo} object containing the details of the + * connection. Can be called as soon as the connection has been established + * (successfully connected). + * + * @return A {@link ConnectionInfo} object. + * @throws IOException + * In case of any failure behind the scenes. + */ + public synchronized ConnectionInfo getConnectionInfo() throws IOException + { + if (tm == null) + throw new IllegalStateException( + "Cannot get details of connection, you need to establish a connection first."); + return tm.getConnectionInfo(1); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * can be used to tell which authentication methods are supported by the + * server at a certain stage of the authentication process (for the given + * username). + * <p> + * Note 1: the username will only be used if no authentication step was done + * so far (it will be used to ask the server for a list of possible + * authentication methods by sending the initial "none" request). Otherwise, + * this method ignores the user name and returns a cached method list (which + * is based on the information contained in the last negative server + * response). + * <p> + * Note 2: the server may return method names that are not supported by this + * implementation. + * <p> + * After a successful authentication, this method must not be called + * anymore. + * + * @param user + * A <code>String</code> holding the username. + * + * @return a (possibly emtpy) array holding authentication method names. + * @throws IOException + */ + public synchronized String[] getRemainingAuthMethods(String user) throws IOException + { + if (user == null) + throw new IllegalArgumentException("user argument may not be NULL!"); + + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + return am.getRemainingMethods(user); + } + + /** + * Determines if the authentication phase is complete. Can be called at any + * time. + * + * @return <code>true</code> if no further authentication steps are + * needed. + */ + public synchronized boolean isAuthenticationComplete() + { + return authenticated; + } + + /** + * Returns true if there was at least one failed authentication request and + * the last failed authentication request was marked with "partial success" + * by the server. This is only needed in the rare case of SSH-2 server + * setups that cannot be satisfied with a single successful authentication + * request (i.e., multiple authentication steps are needed.) + * <p> + * If you are interested in the details, then have a look at RFC4252. + * + * @return if the there was a failed authentication step and the last one + * was marked as a "partial success". + */ + public synchronized boolean isAuthenticationPartialSuccess() + { + if (am == null) + return false; + + return am.getPartialSuccess(); + } + + /** + * Checks if a specified authentication method is available. This method is + * actually just a wrapper for {@link #getRemainingAuthMethods(String) + * getRemainingAuthMethods()}. + * + * @param user + * A <code>String</code> holding the username. + * @param method + * An authentication method name (e.g., "publickey", "password", + * "keyboard-interactive") as specified by the SSH-2 standard. + * @return if the specified authentication method is currently available. + * @throws IOException + */ + public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException + { + if (method == null) + throw new IllegalArgumentException("method argument may not be NULL!"); + + String methods[] = getRemainingAuthMethods(user); + + for (int i = 0; i < methods.length; i++) + { + if (methods[i].compareTo(method) == 0) + return true; + } + + return false; + } + + private final SecureRandom getOrCreateSecureRND() + { + if (generator == null) + generator = new SecureRandom(); + + return generator; + } + + /** + * Open a new {@link Session} on this connection. Works only after one has + * passed successfully the authentication step. There is no limit on the + * number of concurrent sessions. + * + * @return A {@link Session} object. + * @throws IOException + */ + public synchronized Session openSession() throws IOException + { + if (tm == null) + throw new IllegalStateException("Cannot open session, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot open session, connection is not authenticated."); + + return new Session(cm, getOrCreateSecureRND()); + } + + /** + * Send an SSH_MSG_IGNORE packet. This method will generate a random data + * attribute (length between 0 (invlusive) and 16 (exclusive) bytes, + * contents are random bytes). + * <p> + * This method must only be called once the connection is established. + * + * @throws IOException + */ + public synchronized void sendIgnorePacket() throws IOException + { + SecureRandom rnd = getOrCreateSecureRND(); + + byte[] data = new byte[rnd.nextInt(16)]; + rnd.nextBytes(data); + + sendIgnorePacket(data); + } + + /** + * Send an SSH_MSG_IGNORE packet with the given data attribute. + * <p> + * This method must only be called once the connection is established. + * + * @throws IOException + */ + public synchronized void sendIgnorePacket(byte[] data) throws IOException + { + if (data == null) + throw new IllegalArgumentException("data argument must not be null."); + + if (tm == null) + throw new IllegalStateException( + "Cannot send SSH_MSG_IGNORE packet, you need to establish a connection first."); + + PacketIgnore pi = new PacketIgnore(); + pi.setData(data); + + tm.sendMessage(pi.getPayload()); + } + + /** + * Removes duplicates from a String array, keeps only first occurence of + * each element. Does not destroy order of elements; can handle nulls. Uses + * a very efficient O(N^2) algorithm =) + * + * @param list + * a String array. + * @return a cleaned String array. + */ + private String[] removeDuplicates(String[] list) + { + if ((list == null) || (list.length < 2)) + return list; + + String[] list2 = new String[list.length]; + + int count = 0; + + for (int i = 0; i < list.length; i++) + { + boolean duplicate = false; + + String element = list[i]; + + for (int j = 0; j < count; j++) + { + if (((element == null) && (list2[j] == null)) || ((element != null) && (element.equals(list2[j])))) + { + duplicate = true; + break; + } + } + + if (duplicate) + continue; + + list2[count++] = list[i]; + } + + if (count == list2.length) + return list2; + + String[] tmp = new String[count]; + System.arraycopy(list2, 0, tmp, 0, count); + + return tmp; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @param ciphers + */ + public synchronized void setClient2ServerCiphers(String[] ciphers) + { + if ((ciphers == null) || (ciphers.length == 0)) + throw new IllegalArgumentException(); + ciphers = removeDuplicates(ciphers); + BlockCipherFactory.checkCipherList(ciphers); + cryptoWishList.c2s_enc_algos = ciphers; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @param macs + */ + public synchronized void setClient2ServerMACs(String[] macs) + { + if ((macs == null) || (macs.length == 0)) + throw new IllegalArgumentException(); + macs = removeDuplicates(macs); + MAC.checkMacList(macs); + cryptoWishList.c2s_mac_algos = macs; + } + + /** + * Sets the parameters for the diffie-hellman group exchange. Unless you + * know what you are doing, you will never need this. Default values are + * defined in the {@link DHGexParameters} class. + * + * @param dgp + * {@link DHGexParameters}, non null. + * + */ + public synchronized void setDHGexParameters(DHGexParameters dgp) + { + if (dgp == null) + throw new IllegalArgumentException(); + + dhgexpara = dgp; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @param ciphers + */ + public synchronized void setServer2ClientCiphers(String[] ciphers) + { + if ((ciphers == null) || (ciphers.length == 0)) + throw new IllegalArgumentException(); + ciphers = removeDuplicates(ciphers); + BlockCipherFactory.checkCipherList(ciphers); + cryptoWishList.s2c_enc_algos = ciphers; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @param macs + */ + public synchronized void setServer2ClientMACs(String[] macs) + { + if ((macs == null) || (macs.length == 0)) + throw new IllegalArgumentException(); + + macs = removeDuplicates(macs); + MAC.checkMacList(macs); + cryptoWishList.s2c_mac_algos = macs; + } + + /** + * Define the set of allowed server host key algorithms to be used for the + * following key exchange operations. + * <p> + * Unless you know what you are doing, you will never need this. + * + * @param algos + * An array of allowed server host key algorithms. SSH-2 defines + * <code>ssh-dss</code> and <code>ssh-rsa</code>. The + * entries of the array must be ordered after preference, i.e., + * the entry at index 0 is the most preferred one. You must + * specify at least one entry. + */ + public synchronized void setServerHostKeyAlgorithms(String[] algos) + { + if ((algos == null) || (algos.length == 0)) + throw new IllegalArgumentException(); + + algos = removeDuplicates(algos); + KexManager.checkServerHostkeyAlgorithmsList(algos); + cryptoWishList.serverHostKeyAlgorithms = algos; + } + + /** + * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the + * underlying socket. + * <p> + * Can be called at any time. If the connection has not yet been established + * then the passed value will be stored and set after the socket has been + * set up. The default value that will be used is <code>false</code>. + * + * @param enable + * the argument passed to the <code>Socket.setTCPNoDelay()</code> + * method. + * @throws IOException + */ + public synchronized void setTCPNoDelay(boolean enable) throws IOException + { + tcpNoDelay = enable; + + if (tm != null) + tm.setTcpNoDelay(enable); + } + + /** + * Used to tell the library that the connection shall be established through + * a proxy server. It only makes sense to call this method before calling + * the {@link #connect() connect()} method. + * <p> + * At the moment, only HTTP proxies are supported. + * <p> + * Note: This method can be called any number of times. The + * {@link #connect() connect()} method will use the value set in the last + * preceding invocation of this method. + * + * @see HTTPProxyData + * + * @param proxyData + * Connection information about the proxy. If <code>null</code>, + * then no proxy will be used (non surprisingly, this is also the + * default). + */ + public synchronized void setProxyData(ProxyData proxyData) + { + this.proxyData = proxyData; + } + + /** + * Request a remote port forwarding. If successful, then forwarded + * connections will be redirected to the given target address. You can + * cancle a requested remote port forwarding by calling + * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}. + * <p> + * A call of this method will block until the peer either agreed or + * disagreed to your request- + * <p> + * Note 1: this method typically fails if you + * <ul> + * <li>pass a port number for which the used remote user has not enough + * permissions (i.e., port < 1024)</li> + * <li>or pass a port number that is already in use on the remote server</li> + * <li>or if remote port forwarding is disabled on the server.</li> + * </ul> + * <p> + * Note 2: (from the openssh man page): By default, the listening socket on + * the server will be bound to the loopback interface only. This may be + * overriden by specifying a bind address. Specifying a remote bind address + * will only succeed if the server's <b>GatewayPorts</b> option is enabled + * (see sshd_config(5)). + * + * @param bindAddress + * address to bind to on the server: + * <ul> + * <li>"" means that connections are to be accepted on all + * protocol families supported by the SSH implementation</li> + * <li>"0.0.0.0" means to listen on all IPv4 addresses</li> + * <li>"::" means to listen on all IPv6 addresses</li> + * <li>"localhost" means to listen on all protocol families + * supported by the SSH implementation on loopback addresses + * only, [RFC3330] and RFC3513]</li> + * <li>"127.0.0.1" and "::1" indicate listening on the loopback + * interfaces for IPv4 and IPv6 respectively</li> + * </ul> + * @param bindPort + * port number to bind on the server (must be > 0) + * @param targetAddress + * the target address (IP or hostname) + * @param targetPort + * the target port + * @throws IOException + */ + public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress, + int targetPort) throws IOException + { + if (tm == null) + throw new IllegalStateException("You need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("The connection is not authenticated."); + + if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0)) + throw new IllegalArgumentException(); + + cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort); + } + + /** + * Cancel an earlier requested remote port forwarding. Currently active + * forwardings will not be affected (e.g., disrupted). Note that further + * connection forwarding requests may be received until this method has + * returned. + * + * @param bindPort + * the allocated port number on the server + * @throws IOException + * if the remote side refuses the cancel request or another low + * level error occurs (e.g., the underlying connection is + * closed) + */ + public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException + { + if (tm == null) + throw new IllegalStateException("You need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("The connection is not authenticated."); + + cm.requestCancelGlobalForward(bindPort); + } + + /** + * Provide your own instance of SecureRandom. Can be used, e.g., if you want + * to seed the used SecureRandom generator manually. + * <p> + * The SecureRandom instance is used during key exchanges, public key + * authentication, x11 cookie generation and the like. + * + * @param rnd + * a SecureRandom instance + */ + public synchronized void setSecureRandom(SecureRandom rnd) + { + if (rnd == null) + throw new IllegalArgumentException(); + + this.generator = rnd; + } + + /** + * Enable/disable debug logging. <b>Only do this when requested by Trilead + * support.</b> + * <p> + * For speed reasons, some static variables used to check whether debugging + * is enabled are not protected with locks. In other words, if you + * dynamicaly enable/disable debug logging, then some threads may still use + * the old setting. To be on the safe side, enable debugging before doing + * the <code>connect()</code> call. + * + * @param enable + * on/off + * @param logger + * a {@link DebugLogger DebugLogger} instance, <code>null</code> + * means logging using the simple logger which logs all messages + * to to stderr. Ignored if enabled is <code>false</code> + */ + public synchronized void enableDebugging(boolean enable, DebugLogger logger) + { + Logger.enabled = enable; + + if (enable == false) + { + Logger.logger = null; + } + else + { + if (logger == null) + { + logger = new DebugLogger() + { + + public void log(int level, String className, String message) + { + long now = System.currentTimeMillis(); + System.err.println(now + " : " + className + ": " + message); + } + }; + } + + Logger.logger = logger; + } + } + + /** + * This method can be used to perform end-to-end connection testing. It + * sends a 'ping' message to the server and waits for the 'pong' from the + * server. + * <p> + * When this method throws an exception, then you can assume that the + * connection should be abandoned. + * <p> + * Note: Works only after one has passed successfully the authentication + * step. + * <p> + * Implementation details: this method sends a SSH_MSG_GLOBAL_REQUEST + * request ('trilead-ping') to the server and waits for the + * SSH_MSG_REQUEST_FAILURE reply packet from the server. + * + * @throws IOException + * in case of any problem + */ + public synchronized void ping() throws IOException + { + if (tm == null) + throw new IllegalStateException("You need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("The connection is not authenticated."); + + cm.requestGlobalTrileadPing(); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/ConnectionInfo.java b/app/src/main/java/com/trilead/ssh2/ConnectionInfo.java new file mode 100644 index 0000000..b0ddbcf --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/ConnectionInfo.java @@ -0,0 +1,53 @@ + +package com.trilead.ssh2; + +/** + * In most cases you probably do not need the information contained in here. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ConnectionInfo.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class ConnectionInfo +{ + /** + * The used key exchange (KEX) algorithm in the latest key exchange. + */ + public String keyExchangeAlgorithm; + + /** + * The currently used crypto algorithm for packets from to the client to the + * server. + */ + public String clientToServerCryptoAlgorithm; + /** + * The currently used crypto algorithm for packets from to the server to the + * client. + */ + public String serverToClientCryptoAlgorithm; + + /** + * The currently used MAC algorithm for packets from to the client to the + * server. + */ + public String clientToServerMACAlgorithm; + /** + * The currently used MAC algorithm for packets from to the server to the + * client. + */ + public String serverToClientMACAlgorithm; + + /** + * The type of the server host key (currently either "ssh-dss" or + * "ssh-rsa"). + */ + public String serverHostKeyAlgorithm; + /** + * The server host key that was sent during the latest key exchange. + */ + public byte[] serverHostKey; + + /** + * Number of kex exchanges performed on this connection so far. + */ + public int keyExchangeCounter = 0; +} diff --git a/app/src/main/java/com/trilead/ssh2/ConnectionMonitor.java b/app/src/main/java/com/trilead/ssh2/ConnectionMonitor.java new file mode 100644 index 0000000..2f96d25 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/ConnectionMonitor.java @@ -0,0 +1,34 @@ + +package com.trilead.ssh2; + +/** + * A <code>ConnectionMonitor</code> is used to get notified when the + * underlying socket of a connection is closed. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ConnectionMonitor.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public interface ConnectionMonitor +{ + /** + * This method is called after the connection's underlying + * socket has been closed. E.g., due to the {@link Connection#close()} request of the + * user, if the peer closed the connection, due to a fatal error during connect() + * (also if the socket cannot be established) or if a fatal error occured on + * an established connection. + * <p> + * This is an experimental feature. + * <p> + * You MUST NOT make any assumption about the thread that invokes this method. + * <p> + * <b>Please note: if the connection is not connected (e.g., there was no successful + * connect() call), then the invocation of {@link Connection#close()} will NOT trigger + * this method.</b> + * + * @see Connection#addConnectionMonitor(ConnectionMonitor) + * + * @param reason Includes an indication why the socket was closed. + */ + public void connectionLost(Throwable reason); +}
\ No newline at end of file diff --git a/app/src/main/java/com/trilead/ssh2/DHGexParameters.java b/app/src/main/java/com/trilead/ssh2/DHGexParameters.java new file mode 100644 index 0000000..a2945ee --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/DHGexParameters.java @@ -0,0 +1,121 @@ + +package com.trilead.ssh2; + +/** + * A <code>DHGexParameters</code> object can be used to specify parameters for + * the diffie-hellman group exchange. + * <p> + * Depending on which constructor is used, either the use of a + * <code>SSH_MSG_KEX_DH_GEX_REQUEST</code> or <code>SSH_MSG_KEX_DH_GEX_REQUEST_OLD</code> + * can be forced. + * + * @see Connection#setDHGexParameters(DHGexParameters) + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DHGexParameters.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public class DHGexParameters +{ + private final int min_group_len; + private final int pref_group_len; + private final int max_group_len; + + private static final int MIN_ALLOWED = 1024; + private static final int MAX_ALLOWED = 8192; + + /** + * Same as calling {@link #DHGexParameters(int, int, int) DHGexParameters(1024, 1024, 4096)}. + * This is also the default used by the Connection class. + * + */ + public DHGexParameters() + { + this(1024, 1024, 4096); + } + + /** + * This constructor can be used to force the sending of a + * <code>SSH_MSG_KEX_DH_GEX_REQUEST_OLD</code> request. + * Internally, the minimum and maximum group lengths will + * be set to zero. + * + * @param pref_group_len has to be >= 1024 and <= 8192 + */ + public DHGexParameters(int pref_group_len) + { + if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("pref_group_len out of range!"); + + this.pref_group_len = pref_group_len; + this.min_group_len = 0; + this.max_group_len = 0; + } + + /** + * This constructor can be used to force the sending of a + * <code>SSH_MSG_KEX_DH_GEX_REQUEST</code> request. + * <p> + * Note: older OpenSSH servers don't understand this request, in which + * case you should use the {@link #DHGexParameters(int)} constructor. + * <p> + * All values have to be >= 1024 and <= 8192. Furthermore, + * min_group_len <= pref_group_len <= max_group_len. + * + * @param min_group_len + * @param pref_group_len + * @param max_group_len + */ + public DHGexParameters(int min_group_len, int pref_group_len, int max_group_len) + { + if ((min_group_len < MIN_ALLOWED) || (min_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("min_group_len out of range!"); + + if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("pref_group_len out of range!"); + + if ((max_group_len < MIN_ALLOWED) || (max_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("max_group_len out of range!"); + + if ((pref_group_len < min_group_len) || (pref_group_len > max_group_len)) + throw new IllegalArgumentException("pref_group_len is incompatible with min and max!"); + + if (max_group_len < min_group_len) + throw new IllegalArgumentException("max_group_len must not be smaller than min_group_len!"); + + this.min_group_len = min_group_len; + this.pref_group_len = pref_group_len; + this.max_group_len = max_group_len; + } + + /** + * Get the maximum group length. + * + * @return the maximum group length, may be <code>zero</code> if + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested + */ + public int getMax_group_len() + { + return max_group_len; + } + + /** + * Get the minimum group length. + * + * @return minimum group length, may be <code>zero</code> if + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested + */ + public int getMin_group_len() + { + return min_group_len; + } + + /** + * Get the preferred group length. + * + * @return the preferred group length + */ + public int getPref_group_len() + { + return pref_group_len; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/DebugLogger.java b/app/src/main/java/com/trilead/ssh2/DebugLogger.java new file mode 100644 index 0000000..adf2c8e --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/DebugLogger.java @@ -0,0 +1,23 @@ +package com.trilead.ssh2; + +/** + * An interface which needs to be implemented if you + * want to capture debugging messages. + * + * @see Connection#enableDebugging(boolean, DebugLogger) + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DebugLogger.java,v 1.1 2008/03/03 07:01:36 cplattne Exp $ + */ +public interface DebugLogger +{ + +/** + * Log a debug message. + * + * @param level 0-99, 99 is a the most verbose level + * @param className the class that generated the message + * @param message the debug message + */ + public void log(int level, String className, String message); +} diff --git a/app/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java b/app/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java new file mode 100644 index 0000000..a15efe7 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java @@ -0,0 +1,67 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.trilead.ssh2; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import com.trilead.ssh2.channel.ChannelManager; +import com.trilead.ssh2.channel.DynamicAcceptThread; + +/** + * A <code>DynamicPortForwarder</code> forwards TCP/IP connections to a local + * port via the secure tunnel to another host which is selected via the + * SOCKS protocol. Checkout {@link Connection#createDynamicPortForwarder(int)} + * on how to create one. + * + * @author Kenny Root + * @version $Id: $ + */ +public class DynamicPortForwarder { + ChannelManager cm; + + DynamicAcceptThread dat; + + DynamicPortForwarder(ChannelManager cm, int local_port) + throws IOException + { + this.cm = cm; + + dat = new DynamicAcceptThread(cm, local_port); + dat.setDaemon(true); + dat.start(); + } + + DynamicPortForwarder(ChannelManager cm, InetSocketAddress addr) throws IOException { + this.cm = cm; + + dat = new DynamicAcceptThread(cm, addr); + dat.setDaemon(true); + dat.start(); + } + + /** + * Stop TCP/IP forwarding of newly arriving connections. + * + * @throws IOException + */ + public void close() throws IOException + { + dat.stopWorking(); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/HTTPProxyData.java b/app/src/main/java/com/trilead/ssh2/HTTPProxyData.java new file mode 100644 index 0000000..2edffe6 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/HTTPProxyData.java @@ -0,0 +1,83 @@ + +package com.trilead.ssh2; + +/** + * A <code>HTTPProxyData</code> object is used to specify the needed connection data + * to connect through a HTTP proxy. + * + * @see Connection#setProxyData(ProxyData) + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: HTTPProxyData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public class HTTPProxyData implements ProxyData +{ + public final String proxyHost; + public final int proxyPort; + public final String proxyUser; + public final String proxyPass; + public final String[] requestHeaderLines; + + /** + * Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, <code>null</code>, <code>null</code>)} + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + */ + public HTTPProxyData(String proxyHost, int proxyPort) + { + this(proxyHost, proxyPort, null, null); + } + + /** + * Same as calling {@link #HTTPProxyData(String, int, String, String, String[]) HTTPProxyData(proxyHost, proxyPort, <code>null</code>, <code>null</code>, <code>null</code>)} + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + * @param proxyUser Username for basic authentication (<code>null</code> if no authentication is needed). + * @param proxyPass Password for basic authentication (<code>null</code> if no authentication is needed). + */ + public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass) + { + this(proxyHost, proxyPort, proxyUser, proxyPass, null); + } + + /** + * Connection data for a HTTP proxy. It is possible to specify a username and password + * if the proxy requires basic authentication. Also, additional request header lines can + * be specified (e.g., "User-Agent: CERN-LineMode/2.15 libwww/2.17b3"). + * <p> + * Please note: if you want to use basic authentication, then both <code>proxyUser</code> + * and <code>proxyPass</code> must be non-null. + * <p> + * Here is an example: + * <p> + * <code> + * new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: TrileadBasedClient/1.0", "X-My-Proxy-Option: something"}); + * </code> + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + * @param proxyUser Username for basic authentication (<code>null</code> if no authentication is needed). + * @param proxyPass Password for basic authentication (<code>null</code> if no authentication is needed). + * @param requestHeaderLines An array with additional request header lines (without end-of-line markers) + * that have to be sent to the server. May be <code>null</code>. + */ + + public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass, + String[] requestHeaderLines) + { + if (proxyHost == null) + throw new IllegalArgumentException("proxyHost must be non-null"); + + if (proxyPort < 0) + throw new IllegalArgumentException("proxyPort must be non-negative"); + + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUser = proxyUser; + this.proxyPass = proxyPass; + this.requestHeaderLines = requestHeaderLines; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/HTTPProxyException.java b/app/src/main/java/com/trilead/ssh2/HTTPProxyException.java new file mode 100644 index 0000000..4687dd1 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/HTTPProxyException.java @@ -0,0 +1,29 @@ + +package com.trilead.ssh2; + +import java.io.IOException; + +/** + * May be thrown upon connect() if a HTTP proxy is being used. + * + * @see Connection#connect() + * @see Connection#setProxyData(ProxyData) + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: HTTPProxyException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public class HTTPProxyException extends IOException +{ + private static final long serialVersionUID = 2241537397104426186L; + + public final String httpResponse; + public final int httpErrorCode; + + public HTTPProxyException(String httpResponse, int httpErrorCode) + { + super("HTTP Proxy Error (" + httpErrorCode + " " + httpResponse + ")"); + this.httpResponse = httpResponse; + this.httpErrorCode = httpErrorCode; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/InteractiveCallback.java b/app/src/main/java/com/trilead/ssh2/InteractiveCallback.java new file mode 100644 index 0000000..3b83417 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/InteractiveCallback.java @@ -0,0 +1,55 @@ + +package com.trilead.ssh2; + +/** + * An <code>InteractiveCallback</code> is used to respond to challenges sent + * by the server if authentication mode "keyboard-interactive" is selected. + * + * @see Connection#authenticateWithKeyboardInteractive(String, + * String[], InteractiveCallback) + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: InteractiveCallback.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public interface InteractiveCallback +{ + /** + * This callback interface is used during a "keyboard-interactive" + * authentication. Every time the server sends a set of challenges (however, + * most often just one challenge at a time), this callback function will be + * called to give your application a chance to talk to the user and to + * determine the response(s). + * <p> + * Some copy-paste information from the standard: a command line interface + * (CLI) client SHOULD print the name and instruction (if non-empty), adding + * newlines. Then for each prompt in turn, the client SHOULD display the + * prompt and read the user input. The name and instruction fields MAY be + * empty strings, the client MUST be prepared to handle this correctly. The + * prompt field(s) MUST NOT be empty strings. + * <p> + * Please refer to draft-ietf-secsh-auth-kbdinteract-XX.txt for the details. + * <p> + * Note: clients SHOULD use control character filtering as discussed in + * RFC4251 to avoid attacks by including + * terminal control characters in the fields to be displayed. + * + * @param name + * the name String sent by the server. + * @param instruction + * the instruction String sent by the server. + * @param numPrompts + * number of prompts - may be zero (in this case, you should just + * return a String array of length zero). + * @param prompt + * an array (length <code>numPrompts</code>) of Strings + * @param echo + * an array (length <code>numPrompts</code>) of booleans. For + * each prompt, the corresponding echo field indicates whether or + * not the user input should be echoed as characters are typed. + * @return an array of reponses - the array size must match the parameter + * <code>numPrompts</code>. + */ + public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) + throws Exception; +} diff --git a/app/src/main/java/com/trilead/ssh2/KnownHosts.java b/app/src/main/java/com/trilead/ssh2/KnownHosts.java new file mode 100644 index 0000000..ec022ff --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/KnownHosts.java @@ -0,0 +1,844 @@ + +package com.trilead.ssh2; + +import java.io.BufferedReader; +import java.io.CharArrayReader; +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Vector; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import com.trilead.ssh2.crypto.Base64; +import com.trilead.ssh2.signature.DSASHA1Verify; +import com.trilead.ssh2.signature.ECDSASHA2Verify; +import com.trilead.ssh2.signature.RSASHA1Verify; + + +/** + * The <code>KnownHosts</code> class is a handy tool to verify received server hostkeys + * based on the information in <code>known_hosts</code> files (the ones used by OpenSSH). + * <p> + * It offers basically an in-memory database for known_hosts entries, as well as some + * helper functions. Entries from a <code>known_hosts</code> file can be loaded at construction time. + * It is also possible to add more keys later (e.g., one can parse different + * <code>known_hosts<code> files). + * <p> + * It is a thread safe implementation, therefore, you need only to instantiate one + * <code>KnownHosts</code> for your whole application. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: KnownHosts.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ + +public class KnownHosts +{ + public static final int HOSTKEY_IS_OK = 0; + public static final int HOSTKEY_IS_NEW = 1; + public static final int HOSTKEY_HAS_CHANGED = 2; + + private class KnownHostsEntry + { + String[] patterns; + PublicKey key; + + KnownHostsEntry(String[] patterns, PublicKey key) + { + this.patterns = patterns; + this.key = key; + } + } + + private LinkedList<KnownHostsEntry> publicKeys = new LinkedList<KnownHostsEntry>(); + + public KnownHosts() + { + } + + public KnownHosts(char[] knownHostsData) throws IOException + { + initialize(knownHostsData); + } + + public KnownHosts(File knownHosts) throws IOException + { + initialize(knownHosts); + } + + /** + * Adds a single public key entry to the database. Note: this will NOT add the public key + * to any physical file (e.g., "~/.ssh/known_hosts") - use <code>addHostkeyToFile()</code> for that purpose. + * This method is designed to be used in a {@link ServerHostKeyVerifier}. + * + * @param hostnames a list of hostname patterns - at least one most be specified. Check out the + * OpenSSH sshd man page for a description of the pattern matching algorithm. + * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. + * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. + * @throws IOException + */ + public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException + { + if (hostnames == null) + throw new IllegalArgumentException("hostnames may not be null"); + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) + { + RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); + + synchronized (publicKeys) + { + publicKeys.add(new KnownHostsEntry(hostnames, rpk)); + } + } + else if ("ssh-dss".equals(serverHostKeyAlgorithm)) + { + DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); + + synchronized (publicKeys) + { + publicKeys.add(new KnownHostsEntry(hostnames, dpk)); + } + } + else if (serverHostKeyAlgorithm.startsWith(ECDSASHA2Verify.ECDSA_SHA2_PREFIX)) + { + ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, epk)); + } + } + else + throw new IOException("Unknwon host key type (" + serverHostKeyAlgorithm + ")"); + } + + /** + * Parses the given known_hosts data and adds entries to the database. + * + * @param knownHostsData + * @throws IOException + */ + public void addHostkeys(char[] knownHostsData) throws IOException + { + initialize(knownHostsData); + } + + /** + * Parses the given known_hosts file and adds entries to the database. + * + * @param knownHosts + * @throws IOException + */ + public void addHostkeys(File knownHosts) throws IOException + { + initialize(knownHosts); + } + + /** + * Generate the hashed representation of the given hostname. Useful for adding entries + * with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen). + * + * @param hostname + * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA=" + */ + public static final String createHashedHostname(String hostname) + { + MessageDigest sha1; + try { + sha1 = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("VM doesn't support SHA1", e); + } + + byte[] salt = new byte[sha1.getDigestLength()]; + + new SecureRandom().nextBytes(salt); + + byte[] hash = hmacSha1Hash(salt, hostname); + + String base64_salt = new String(Base64.encode(salt)); + String base64_hash = new String(Base64.encode(hash)); + + return new String("|1|" + base64_salt + "|" + base64_hash); + } + + private static final byte[] hmacSha1Hash(byte[] salt, String hostname) + { + Mac hmac; + try { + hmac = Mac.getInstance("HmacSHA1"); + if (salt.length != hmac.getMacLength()) + throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")"); + hmac.init(new SecretKeySpec(salt, "HmacSHA1")); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Unable to HMAC-SHA1", e); + } catch (InvalidKeyException e) { + throw new RuntimeException("Unable to create SecretKey", e); + } + + try + { + hmac.update(hostname.getBytes("ISO-8859-1")); + }catch(UnsupportedEncodingException ignore) + { + /* Actually, ISO-8859-1 is supported by all correct + * Java implementations. But... you never know. */ + hmac.update(hostname.getBytes()); + } + + return hmac.doFinal(); + } + + private final boolean checkHashed(String entry, String hostname) + { + if (entry.startsWith("|1|") == false) + return false; + + int delim_idx = entry.indexOf('|', 3); + + if (delim_idx == -1) + return false; + + String salt_base64 = entry.substring(3, delim_idx); + String hash_base64 = entry.substring(delim_idx + 1); + + byte[] salt = null; + byte[] hash = null; + + try + { + salt = Base64.decode(salt_base64.toCharArray()); + hash = Base64.decode(hash_base64.toCharArray()); + } + catch (IOException e) + { + return false; + } + + try { + MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + if (salt.length != sha1.getDigestLength()) + return false; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("VM does not support SHA1", e); + } + + byte[] dig = hmacSha1Hash(salt, hostname); + + for (int i = 0; i < dig.length; i++) + if (dig[i] != hash[i]) + return false; + + return true; + } + + private int checkKey(String remoteHostname, PublicKey remoteKey) + { + int result = HOSTKEY_IS_NEW; + + synchronized (publicKeys) + { + Iterator<KnownHostsEntry> i = publicKeys.iterator(); + + while (i.hasNext()) + { + KnownHostsEntry ke = i.next(); + + if (hostnameMatches(ke.patterns, remoteHostname) == false) + continue; + + boolean res = matchKeys(ke.key, remoteKey); + + if (res == true) + return HOSTKEY_IS_OK; + + result = HOSTKEY_HAS_CHANGED; + } + } + return result; + } + + private Vector<PublicKey> getAllKeys(String hostname) + { + Vector<PublicKey> keys = new Vector<PublicKey>(); + + synchronized (publicKeys) + { + Iterator<KnownHostsEntry> i = publicKeys.iterator(); + + while (i.hasNext()) + { + KnownHostsEntry ke = i.next(); + + if (hostnameMatches(ke.patterns, hostname) == false) + continue; + + keys.addElement(ke.key); + } + } + + return keys; + } + + /** + * Try to find the preferred order of hostkey algorithms for the given hostname. + * Based on the type of hostkey that is present in the internal database + * (i.e., either <code>ssh-rsa</code> or <code>ssh-dss</code>) + * an ordered list of hostkey algorithms is returned which can be passed + * to <code>Connection.setServerHostKeyAlgorithms</code>. + * + * @param hostname + * @return <code>null</code> if no key for the given hostname is present or + * there are keys of multiple types present for the given hostname. Otherwise, + * an array with hostkey algorithms is returned (i.e., an array of length 2). + */ + public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname) + { + String[] algos = recommendHostkeyAlgorithms(hostname); + + if (algos != null) + return algos; + + InetAddress[] ipAdresses = null; + + try + { + ipAdresses = InetAddress.getAllByName(hostname); + } + catch (UnknownHostException e) + { + return null; + } + + for (int i = 0; i < ipAdresses.length; i++) + { + algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress()); + + if (algos != null) + return algos; + } + + return null; + } + + private final boolean hostnameMatches(String[] hostpatterns, String hostname) + { + boolean isMatch = false; + boolean negate = false; + + hostname = hostname.toLowerCase(Locale.US); + + for (int k = 0; k < hostpatterns.length; k++) + { + if (hostpatterns[k] == null) + continue; + + String pattern = null; + + /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed + * entries in lines with multiple entries). + */ + + if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) + { + pattern = hostpatterns[k].substring(1); + negate = true; + } + else + { + pattern = hostpatterns[k]; + negate = false; + } + + /* Optimize, no need to check this entry */ + + if ((isMatch) && (negate == false)) + continue; + + /* Now compare */ + + if (pattern.charAt(0) == '|') + { + if (checkHashed(pattern, hostname)) + { + if (negate) + return false; + isMatch = true; + } + } + else + { + pattern = pattern.toLowerCase(Locale.US); + + if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) + { + if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) + { + if (negate) + return false; + isMatch = true; + } + } + else if (pattern.compareTo(hostname) == 0) + { + if (negate) + return false; + isMatch = true; + } + } + } + + return isMatch; + } + + private void initialize(char[] knownHostsData) throws IOException + { + BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData)); + + while (true) + { + String line = br.readLine(); + + if (line == null) + break; + + line = line.trim(); + + if (line.startsWith("#")) + continue; + + String[] arr = line.split(" "); + + if (arr.length >= 3) + { + if ((arr[1].compareTo("ssh-rsa") == 0) || (arr[1].compareTo("ssh-dss") == 0)) + { + String[] hostnames = arr[0].split(","); + + byte[] msg = Base64.decode(arr[2].toCharArray()); + + addHostkey(hostnames, arr[1], msg); + } + } + } + } + + private void initialize(File knownHosts) throws IOException + { + char[] buff = new char[512]; + + CharArrayWriter cw = new CharArrayWriter(); + + knownHosts.createNewFile(); + + FileReader fr = new FileReader(knownHosts); + + while (true) + { + int len = fr.read(buff); + if (len < 0) + break; + cw.write(buff, 0, len); + } + + fr.close(); + + initialize(cw.toCharArray()); + } + + private final boolean matchKeys(PublicKey key1, PublicKey key2) + { + return key1.equals(key2); + } + + private final boolean pseudoRegex(char[] pattern, int i, char[] match, int j) + { + /* This matching logic is equivalent to the one present in OpenSSH 4.1 */ + + while (true) + { + /* Are we at the end of the pattern? */ + + if (pattern.length == i) + return (match.length == j); + + if (pattern[i] == '*') + { + i++; + + if (pattern.length == i) + return true; + + if ((pattern[i] != '*') && (pattern[i] != '?')) + { + while (true) + { + if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) + return true; + j++; + if (match.length == j) + return false; + } + } + + while (true) + { + if (pseudoRegex(pattern, i, match, j)) + return true; + j++; + if (match.length == j) + return false; + } + } + + if (match.length == j) + return false; + + if ((pattern[i] != '?') && (pattern[i] != match[j])) + return false; + + i++; + j++; + } + } + + private String[] recommendHostkeyAlgorithms(String hostname) + { + String preferredAlgo = null; + + Vector<PublicKey> keys = getAllKeys(hostname); + + for (int i = 0; i < keys.size(); i++) + { + String thisAlgo = null; + + if (keys.elementAt(i) instanceof RSAPublicKey) + thisAlgo = "ssh-rsa"; + else if (keys.elementAt(i) instanceof DSAPublicKey) + thisAlgo = "ssh-dss"; + else + continue; + + if (preferredAlgo != null) + { + /* If we find different key types, then return null */ + + if (preferredAlgo.compareTo(thisAlgo) != 0) + return null; + + /* OK, we found the same algo again, optimize */ + + continue; + } + } + + /* If we did not find anything that we know of, return null */ + + if (preferredAlgo == null) + return null; + + /* Now put the preferred algo to the start of the array. + * You may ask yourself why we do it that way - basically, we could just + * return only the preferred algorithm: since we have a saved key of that + * type (sent earlier from the remote host), then that should work out. + * However, imagine that the server is (for whatever reasons) not offering + * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and + * now "ssh-dss" is being used). If we then do not let the server send us + * a fresh key of the new type, then we shoot ourself into the foot: + * the connection cannot be established and hence the user cannot decide + * if he/she wants to accept the new key. + */ + + if (preferredAlgo.equals("ssh-rsa")) + return new String[] { "ssh-rsa", "ssh-dss" }; + + return new String[] { "ssh-dss", "ssh-rsa" }; + } + + /** + * Checks the internal hostkey database for the given hostkey. + * If no matching key can be found, then the hostname is resolved to an IP address + * and the search is repeated using that IP address. + * + * @param hostname the server's hostname, will be matched with all hostname patterns + * @param serverHostKeyAlgorithm type of hostkey, either <code>ssh-rsa</code> or <code>ssh-dss</code> + * @param serverHostKey the key blob + * @return <ul> + * <li><code>HOSTKEY_IS_OK</code>: the given hostkey matches an entry for the given hostname</li> + * <li><code>HOSTKEY_IS_NEW</code>: no entries found for this hostname and this type of hostkey</li> + * <li><code>HOSTKEY_HAS_CHANGED</code>: hostname is known, but with another key of the same type + * (man-in-the-middle attack?)</li> + * </ul> + * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type. + */ + public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException + { + PublicKey remoteKey = null; + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) + { + remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); + } + else if ("ssh-dss".equals(serverHostKeyAlgorithm)) + { + remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); + } + else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) + { + remoteKey = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); + } + else + throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm); + + int result = checkKey(hostname, remoteKey); + + if (result == HOSTKEY_IS_OK) + return result; + + InetAddress[] ipAdresses = null; + + try + { + ipAdresses = InetAddress.getAllByName(hostname); + } + catch (UnknownHostException e) + { + return result; + } + + for (int i = 0; i < ipAdresses.length; i++) + { + int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey); + + if (newresult == HOSTKEY_IS_OK) + return newresult; + + if (newresult == HOSTKEY_HAS_CHANGED) + result = HOSTKEY_HAS_CHANGED; + } + + return result; + } + + /** + * Adds a single public key entry to the a known_hosts file. + * This method is designed to be used in a {@link ServerHostKeyVerifier}. + * + * @param knownHosts the file where the publickey entry will be appended. + * @param hostnames a list of hostname patterns - at least one most be specified. Check out the + * OpenSSH sshd man page for a description of the pattern matching algorithm. + * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. + * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. + * @throws IOException + */ + public final static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm, + byte[] serverHostKey) throws IOException + { + if ((hostnames == null) || (hostnames.length == 0)) + throw new IllegalArgumentException("Need at least one hostname specification"); + + if ((serverHostKeyAlgorithm == null) || (serverHostKey == null)) + throw new IllegalArgumentException(); + + CharArrayWriter writer = new CharArrayWriter(); + + for (int i = 0; i < hostnames.length; i++) + { + if (i != 0) + writer.write(','); + writer.write(hostnames[i]); + } + + writer.write(' '); + writer.write(serverHostKeyAlgorithm); + writer.write(' '); + writer.write(Base64.encode(serverHostKey)); + writer.write("\n"); + + char[] entry = writer.toCharArray(); + + RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw"); + + long len = raf.length(); + + if (len > 0) + { + raf.seek(len - 1); + int last = raf.read(); + if (last != '\n') + raf.write('\n'); + } + + raf.write(new String(entry).getBytes("ISO-8859-1")); + raf.close(); + } + + /** + * Generates a "raw" fingerprint of a hostkey. + * + * @param type either "md5" or "sha1" + * @param keyType either "ssh-rsa" or "ssh-dss" + * @param hostkey the hostkey + * @return the raw fingerprint + */ + static final private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) + { + MessageDigest dig = null; + + try { + if ("md5".equals(type)) + { + dig = MessageDigest.getInstance("MD5"); + } + else if ("sha1".equals(type)) + { + dig = MessageDigest.getInstance("SHA1"); + } + else + { + throw new IllegalArgumentException("Unknown hash type " + type); + } + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Unknown hash type " + type); + } + + if (keyType.startsWith("ecdsa-sha2-")) + { + } + else if ("ssh-rsa".equals(keyType)) + { + } + else if ("ssh-dss".equals(keyType)) + { + } + else + throw new IllegalArgumentException("Unknown key type " + keyType); + + if (hostkey == null) + throw new IllegalArgumentException("hostkey is null"); + + dig.update(hostkey); + return dig.digest(); + } + + /** + * Convert a raw fingerprint to hex representation (XX:YY:ZZ...). + * @param fingerprint raw fingerprint + * @return the hex representation + */ + static final private String rawToHexFingerprint(byte[] fingerprint) + { + final char[] alpha = "0123456789abcdef".toCharArray(); + + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < fingerprint.length; i++) + { + if (i != 0) + sb.append(':'); + int b = fingerprint[i] & 0xff; + sb.append(alpha[b >> 4]); + sb.append(alpha[b & 15]); + } + + return sb.toString(); + } + + /** + * Convert a raw fingerprint to bubblebabble representation. + * @param raw raw fingerprint + * @return the bubblebabble representation + */ + static final private String rawToBubblebabbleFingerprint(byte[] raw) + { + final char[] v = "aeiouy".toCharArray(); + final char[] c = "bcdfghklmnprstvzx".toCharArray(); + + StringBuffer sb = new StringBuffer(); + + int seed = 1; + + int rounds = (raw.length / 2) + 1; + + sb.append('x'); + + for (int i = 0; i < rounds; i++) + { + if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) + { + sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]); + sb.append(c[(raw[2 * i] >> 2) & 15]); + sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]); + + if ((i + 1) < rounds) + { + sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]); + sb.append('-'); + sb.append(c[(((raw[(2 * i) + 1]))) & 15]); + // As long as seed >= 0, seed will be >= 0 afterwards + seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36; + } + } + else + { + sb.append(v[seed % 6]); // seed >= 0, therefore index positive + sb.append('x'); + sb.append(v[seed / 6]); + } + } + + sb.append('x'); + + return sb.toString(); + } + + /** + * Convert a ssh2 key-blob into a human readable hex fingerprint. + * Generated fingerprints are identical to those generated by OpenSSH. + * <p> + * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47. + + * @param keytype either "ssh-rsa" or "ssh-dss" + * @param publickey key blob + * @return Hex fingerprint + */ + public final static String createHexFingerprint(String keytype, byte[] publickey) + { + byte[] raw = rawFingerPrint("md5", keytype, publickey); + return rawToHexFingerprint(raw); + } + + /** + * Convert a ssh2 key-blob into a human readable bubblebabble fingerprint. + * The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints + * that are easier to remember for humans. + * <p> + * Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux. + * + * @param keytype either "ssh-rsa" or "ssh-dss" + * @param publickey key data + * @return Bubblebabble fingerprint + */ + public final static String createBubblebabbleFingerprint(String keytype, byte[] publickey) + { + byte[] raw = rawFingerPrint("sha1", keytype, publickey); + return rawToBubblebabbleFingerprint(raw); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/LocalPortForwarder.java b/app/src/main/java/com/trilead/ssh2/LocalPortForwarder.java new file mode 100644 index 0000000..96fa082 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/LocalPortForwarder.java @@ -0,0 +1,63 @@ + +package com.trilead.ssh2; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import com.trilead.ssh2.channel.ChannelManager; +import com.trilead.ssh2.channel.LocalAcceptThread; + + +/** + * A <code>LocalPortForwarder</code> forwards TCP/IP connections to a local + * port via the secure tunnel to another host (which may or may not be identical + * to the remote SSH-2 server). Checkout {@link Connection#createLocalPortForwarder(int, String, int)} + * on how to create one. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: LocalPortForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class LocalPortForwarder +{ + ChannelManager cm; + + String host_to_connect; + + int port_to_connect; + + LocalAcceptThread lat; + + LocalPortForwarder(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException + { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + lat = new LocalAcceptThread(cm, local_port, host_to_connect, port_to_connect); + lat.setDaemon(true); + lat.start(); + } + + LocalPortForwarder(ChannelManager cm, InetSocketAddress addr, String host_to_connect, int port_to_connect) + throws IOException + { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + lat = new LocalAcceptThread(cm, addr, host_to_connect, port_to_connect); + lat.setDaemon(true); + lat.start(); + } + + /** + * Stop TCP/IP forwarding of newly arriving connections. + * + * @throws IOException + */ + public void close() throws IOException + { + lat.stopWorking(); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/LocalStreamForwarder.java b/app/src/main/java/com/trilead/ssh2/LocalStreamForwarder.java new file mode 100644 index 0000000..7899367 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/LocalStreamForwarder.java @@ -0,0 +1,78 @@ + +package com.trilead.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import com.trilead.ssh2.channel.Channel; +import com.trilead.ssh2.channel.ChannelManager; +import com.trilead.ssh2.channel.LocalAcceptThread; + + +/** + * A <code>LocalStreamForwarder</code> forwards an Input- and Outputstream + * pair via the secure tunnel to another host (which may or may not be identical + * to the remote SSH-2 server). + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: LocalStreamForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class LocalStreamForwarder +{ + ChannelManager cm; + + String host_to_connect; + int port_to_connect; + LocalAcceptThread lat; + + Channel cn; + + LocalStreamForwarder(ChannelManager cm, String host_to_connect, int port_to_connect) throws IOException + { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, "127.0.0.1", 0); + } + + /** + * @return An <code>InputStream</code> object. + * @throws IOException + */ + public InputStream getInputStream() throws IOException + { + return cn.getStdoutStream(); + } + + /** + * Get the OutputStream. Please be aware that the implementation MAY use an + * internal buffer. To make sure that the buffered data is sent over the + * tunnel, you have to call the <code>flush</code> method of the + * <code>OutputStream</code>. To signal EOF, please use the + * <code>close</code> method of the <code>OutputStream</code>. + * + * @return An <code>OutputStream</code> object. + * @throws IOException + */ + public OutputStream getOutputStream() throws IOException + { + return cn.getStdinStream(); + } + + /** + * Close the underlying SSH forwarding channel and free up resources. + * You can also use this method to force the shutdown of the underlying + * forwarding channel. Pending output (OutputStream not flushed) will NOT + * be sent. Pending input (InputStream) can still be read. If the shutdown + * operation is already in progress (initiated from either side), then this + * call is a no-op. + * + * @throws IOException + */ + public void close() throws IOException + { + cm.closeChannel(cn, "Closed due to user request.", true); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/ProxyData.java b/app/src/main/java/com/trilead/ssh2/ProxyData.java new file mode 100644 index 0000000..059a6e3 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/ProxyData.java @@ -0,0 +1,15 @@ + +package com.trilead.ssh2; + +/** + * An abstract marker interface implemented by all proxy data implementations. + * + * @see HTTPProxyData + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ProxyData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public abstract interface ProxyData +{ +} diff --git a/app/src/main/java/com/trilead/ssh2/SCPClient.java b/app/src/main/java/com/trilead/ssh2/SCPClient.java new file mode 100644 index 0000000..b692750 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/SCPClient.java @@ -0,0 +1,729 @@ + +package com.trilead.ssh2; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A very basic <code>SCPClient</code> that can be used to copy files from/to + * the SSH-2 server. On the server side, the "scp" program must be in the PATH. + * <p> + * This scp client is thread safe - you can download (and upload) different sets + * of files concurrently without any troubles. The <code>SCPClient</code> is + * actually mapping every request to a distinct {@link Session}. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SCPClient.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ + +public class SCPClient +{ + Connection conn; + + class LenNamePair + { + long length; + String filename; + } + + public SCPClient(Connection conn) + { + if (conn == null) + throw new IllegalArgumentException("Cannot accept null argument!"); + this.conn = conn; + } + + private void readResponse(InputStream is) throws IOException + { + int c = is.read(); + + if (c == 0) + return; + + if (c == -1) + throw new IOException("Remote scp terminated unexpectedly."); + + if ((c != 1) && (c != 2)) + throw new IOException("Remote scp sent illegal error code."); + + if (c == 2) + throw new IOException("Remote scp terminated with error."); + + String err = receiveLine(is); + throw new IOException("Remote scp terminated with error (" + err + ")."); + } + + private String receiveLine(InputStream is) throws IOException + { + StringBuffer sb = new StringBuffer(30); + + while (true) + { + /* + * This is a random limit - if your path names are longer, then + * adjust it + */ + + if (sb.length() > 8192) + throw new IOException("Remote scp sent a too long line"); + + int c = is.read(); + + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + if (c == '\n') + break; + + sb.append((char) c); + + } + return sb.toString(); + } + + private LenNamePair parseCLine(String line) throws IOException + { + /* Minimum line: "xxxx y z" ---> 8 chars */ + + long len; + + if (line.length() < 8) + throw new IOException("Malformed C line sent by remote SCP binary, line too short."); + + if ((line.charAt(4) != ' ') || (line.charAt(5) == ' ')) + throw new IOException("Malformed C line sent by remote SCP binary."); + + int length_name_sep = line.indexOf(' ', 5); + + if (length_name_sep == -1) + throw new IOException("Malformed C line sent by remote SCP binary."); + + String length_substring = line.substring(5, length_name_sep); + String name_substring = line.substring(length_name_sep + 1); + + if ((length_substring.length() <= 0) || (name_substring.length() <= 0)) + throw new IOException("Malformed C line sent by remote SCP binary."); + + if ((6 + length_substring.length() + name_substring.length()) != line.length()) + throw new IOException("Malformed C line sent by remote SCP binary."); + + try + { + len = Long.parseLong(length_substring); + } + catch (NumberFormatException e) + { + throw new IOException("Malformed C line sent by remote SCP binary, cannot parse file length."); + } + + if (len < 0) + throw new IOException("Malformed C line sent by remote SCP binary, illegal file length."); + + LenNamePair lnp = new LenNamePair(); + lnp.length = len; + lnp.filename = name_substring; + + return lnp; + } + + private void sendBytes(Session sess, byte[] data, String fileName, String mode) throws IOException + { + OutputStream os = sess.getStdin(); + InputStream is = new BufferedInputStream(sess.getStdout(), 512); + + readResponse(is); + + String cline = "C" + mode + " " + data.length + " " + fileName + "\n"; + + os.write(cline.getBytes("ISO-8859-1")); + os.flush(); + + readResponse(is); + + os.write(data, 0, data.length); + os.write(0); + os.flush(); + + readResponse(is); + + os.write("E\n".getBytes("ISO-8859-1")); + os.flush(); + } + + private void sendFiles(Session sess, String[] files, String[] remoteFiles, String mode) throws IOException + { + byte[] buffer = new byte[8192]; + + OutputStream os = new BufferedOutputStream(sess.getStdin(), 40000); + InputStream is = new BufferedInputStream(sess.getStdout(), 512); + + readResponse(is); + + for (int i = 0; i < files.length; i++) + { + File f = new File(files[i]); + long remain = f.length(); + + String remoteName; + + if ((remoteFiles != null) && (remoteFiles.length > i) && (remoteFiles[i] != null)) + remoteName = remoteFiles[i]; + else + remoteName = f.getName(); + + String cline = "C" + mode + " " + remain + " " + remoteName + "\n"; + + os.write(cline.getBytes("ISO-8859-1")); + os.flush(); + + readResponse(is); + + FileInputStream fis = null; + + try + { + fis = new FileInputStream(f); + + while (remain > 0) + { + int trans; + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + if (fis.read(buffer, 0, trans) != trans) + throw new IOException("Cannot read enough from local file " + files[i]); + + os.write(buffer, 0, trans); + + remain -= trans; + } + } + finally + { + if (fis != null) + fis.close(); + } + + os.write(0); + os.flush(); + + readResponse(is); + } + + os.write("E\n".getBytes("ISO-8859-1")); + os.flush(); + } + + private void receiveFiles(Session sess, OutputStream[] targets) throws IOException + { + byte[] buffer = new byte[8192]; + + OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); + InputStream is = new BufferedInputStream(sess.getStdout(), 40000); + + os.write(0x0); + os.flush(); + + for (int i = 0; i < targets.length; i++) + { + LenNamePair lnp = null; + + while (true) + { + int c = is.read(); + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + String line = receiveLine(is); + + if (c == 'T') + { + /* Ignore modification times */ + + continue; + } + + if ((c == 1) || (c == 2)) + throw new IOException("Remote SCP error: " + line); + + if (c == 'C') + { + lnp = parseCLine(line); + break; + + } + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + + long remain = lnp.length; + + while (remain > 0) + { + int trans; + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + int this_time_received = is.read(buffer, 0, trans); + + if (this_time_received < 0) + { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + targets[i].write(buffer, 0, this_time_received); + + remain -= this_time_received; + } + + readResponse(is); + + os.write(0x0); + os.flush(); + } + } + + private void receiveFiles(Session sess, String[] files, String target) throws IOException + { + byte[] buffer = new byte[8192]; + + OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); + InputStream is = new BufferedInputStream(sess.getStdout(), 40000); + + os.write(0x0); + os.flush(); + + for (int i = 0; i < files.length; i++) + { + LenNamePair lnp = null; + + while (true) + { + int c = is.read(); + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + String line = receiveLine(is); + + if (c == 'T') + { + /* Ignore modification times */ + + continue; + } + + if ((c == 1) || (c == 2)) + throw new IOException("Remote SCP error: " + line); + + if (c == 'C') + { + lnp = parseCLine(line); + break; + + } + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + + File f = new File(target + File.separatorChar + lnp.filename); + FileOutputStream fop = null; + + try + { + fop = new FileOutputStream(f); + + long remain = lnp.length; + + while (remain > 0) + { + int trans; + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + int this_time_received = is.read(buffer, 0, trans); + + if (this_time_received < 0) + { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + fop.write(buffer, 0, this_time_received); + + remain -= this_time_received; + } + } + finally + { + if (fop != null) + fop.close(); + } + + readResponse(is); + + os.write(0x0); + os.flush(); + } + } + + /** + * Copy a local file to a remote directory, uses mode 0600 when creating the + * file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * + * @throws IOException + */ + public void put(String localFile, String remoteTargetDirectory) throws IOException + { + put(new String[] { localFile }, remoteTargetDirectory, "0600"); + } + + /** + * Copy a set of local files to a remote directory, uses mode 0600 when + * creating files on the remote side. + * + * @param localFiles + * Paths and names of local file names. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * + * @throws IOException + */ + + public void put(String[] localFiles, String remoteTargetDirectory) throws IOException + { + put(localFiles, remoteTargetDirectory, "0600"); + } + + /** + * Copy a local file to a remote directory, uses the specified mode when + * creating the file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String localFile, String remoteTargetDirectory, String mode) throws IOException + { + put(new String[] { localFile }, remoteTargetDirectory, mode); + } + + /** + * Copy a local file to a remote directory, uses the specified mode and + * remote filename when creating the file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) + throws IOException + { + put(new String[] { localFile }, new String[] { remoteFileName }, remoteTargetDirectory, mode); + } + + /** + * Create a remote file and copy the contents of the passed byte array into + * it. Uses mode 0600 for creating the remote file. + * + * @param data + * the data to be copied into the remote file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @throws IOException + */ + + public void put(byte[] data, String remoteFileName, String remoteTargetDirectory) throws IOException + { + put(data, remoteFileName, remoteTargetDirectory, "0600"); + } + + /** + * Create a remote file and copy the contents of the passed byte array into + * it. The method use the specified mode when creating the file on the + * remote side. + * + * @param data + * the data to be copied into the remote file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(byte[] data, String remoteFileName, String remoteTargetDirectory, String mode) throws IOException + { + Session sess = null; + + if ((remoteFileName == null) || (remoteTargetDirectory == null) || (mode == null)) + throw new IllegalArgumentException("Null argument."); + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + remoteTargetDirectory = remoteTargetDirectory.trim(); + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + + String cmd = "scp -t -d " + remoteTargetDirectory; + + try + { + sess = conn.openSession(); + sess.execCommand(cmd); + sendBytes(sess, data, remoteFileName, mode); + } + catch (IOException e) + { + throw (IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally + { + if (sess != null) + sess.close(); + } + } + + /** + * Copy a set of local files to a remote directory, uses the specified mode + * when creating the files on the remote side. + * + * @param localFiles + * Paths and names of the local files. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String[] localFiles, String remoteTargetDirectory, String mode) throws IOException + { + put(localFiles, null, remoteTargetDirectory, mode); + } + + public void put(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) + throws IOException + { + Session sess = null; + + /* + * remoteFiles may be null, indicating that the local filenames shall be + * used + */ + + if ((localFiles == null) || (remoteTargetDirectory == null) || (mode == null)) + throw new IllegalArgumentException("Null argument."); + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + if (localFiles.length == 0) + return; + + remoteTargetDirectory = remoteTargetDirectory.trim(); + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + + String cmd = "scp -t -d " + remoteTargetDirectory; + + for (int i = 0; i < localFiles.length; i++) + { + if (localFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + } + + try + { + sess = conn.openSession(); + sess.execCommand(cmd); + sendFiles(sess, localFiles, remoteFiles, mode); + } + catch (IOException e) + { + throw (IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally + { + if (sess != null) + sess.close(); + } + } + + /** + * Download a file from the remote server to a local directory. + * + * @param remoteFile + * Path and name of the remote file. + * @param localTargetDirectory + * Local directory to put the downloaded file. + * + * @throws IOException + */ + public void get(String remoteFile, String localTargetDirectory) throws IOException + { + get(new String[] { remoteFile }, localTargetDirectory); + } + + /** + * Download a file from the remote server and pipe its contents into an + * <code>OutputStream</code>. Please note that, to enable flexible usage + * of this method, the <code>OutputStream</code> will not be closed nor + * flushed. + * + * @param remoteFile + * Path and name of the remote file. + * @param target + * OutputStream where the contents of the file will be sent to. + * @throws IOException + */ + public void get(String remoteFile, OutputStream target) throws IOException + { + get(new String[] { remoteFile }, new OutputStream[] { target }); + } + + private void get(String remoteFiles[], OutputStream[] targets) throws IOException + { + Session sess = null; + + if ((remoteFiles == null) || (targets == null)) + throw new IllegalArgumentException("Null argument."); + + if (remoteFiles.length != targets.length) + throw new IllegalArgumentException("Length of arguments does not match."); + + if (remoteFiles.length == 0) + return; + + String cmd = "scp -f"; + + for (int i = 0; i < remoteFiles.length; i++) + { + if (remoteFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + + String tmp = remoteFiles[i].trim(); + + if (tmp.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + cmd += (" " + tmp); + } + + try + { + sess = conn.openSession(); + sess.execCommand(cmd); + receiveFiles(sess, targets); + } + catch (IOException e) + { + throw (IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally + { + if (sess != null) + sess.close(); + } + } + + /** + * Download a set of files from the remote server to a local directory. + * + * @param remoteFiles + * Paths and names of the remote files. + * @param localTargetDirectory + * Local directory to put the downloaded files. + * + * @throws IOException + */ + public void get(String remoteFiles[], String localTargetDirectory) throws IOException + { + Session sess = null; + + if ((remoteFiles == null) || (localTargetDirectory == null)) + throw new IllegalArgumentException("Null argument."); + + if (remoteFiles.length == 0) + return; + + String cmd = "scp -f"; + + for (int i = 0; i < remoteFiles.length; i++) + { + if (remoteFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + + String tmp = remoteFiles[i].trim(); + + if (tmp.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + cmd += (" " + tmp); + } + + try + { + sess = conn.openSession(); + sess.execCommand(cmd); + receiveFiles(sess, remoteFiles, localTargetDirectory); + } + catch (IOException e) + { + throw (IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally + { + if (sess != null) + sess.close(); + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/SFTPException.java b/app/src/main/java/com/trilead/ssh2/SFTPException.java new file mode 100644 index 0000000..d97723f --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/SFTPException.java @@ -0,0 +1,91 @@ + +package com.trilead.ssh2; + +import java.io.IOException; + +import com.trilead.ssh2.sftp.ErrorCodes; + + +/** + * Used in combination with the SFTPv3Client. This exception wraps + * error messages sent by the SFTP server. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SFTPException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public class SFTPException extends IOException +{ + private static final long serialVersionUID = 578654644222421811L; + + private final String sftpErrorMessage; + private final int sftpErrorCode; + + private static String constructMessage(String s, int errorCode) + { + String[] detail = ErrorCodes.getDescription(errorCode); + + if (detail == null) + return s + " (UNKNOW SFTP ERROR CODE)"; + + return s + " (" + detail[0] + ": " + detail[1] + ")"; + } + + SFTPException(String msg, int errorCode) + { + super(constructMessage(msg, errorCode)); + sftpErrorMessage = msg; + sftpErrorCode = errorCode; + } + + /** + * Get the error message sent by the server. Often, this + * message does not help a lot (e.g., "failure"). + * + * @return the plain string as sent by the server. + */ + public String getServerErrorMessage() + { + return sftpErrorMessage; + } + + /** + * Get the error code sent by the server. + * + * @return an error code as defined in the SFTP specs. + */ + public int getServerErrorCode() + { + return sftpErrorCode; + } + + /** + * Get the symbolic name of the error code as given in the SFTP specs. + * + * @return e.g., "SSH_FX_INVALID_FILENAME". + */ + public String getServerErrorCodeSymbol() + { + String[] detail = ErrorCodes.getDescription(sftpErrorCode); + + if (detail == null) + return "UNKNOW SFTP ERROR CODE " + sftpErrorCode; + + return detail[0]; + } + + /** + * Get the description of the error code as given in the SFTP specs. + * + * @return e.g., "The filename is not valid." + */ + public String getServerErrorCodeVerbose() + { + String[] detail = ErrorCodes.getDescription(sftpErrorCode); + + if (detail == null) + return "The error code " + sftpErrorCode + " is unknown."; + + return detail[1]; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/SFTPv3Client.java b/app/src/main/java/com/trilead/ssh2/SFTPv3Client.java new file mode 100644 index 0000000..06796e9 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/SFTPv3Client.java @@ -0,0 +1,1388 @@ + +package com.trilead.ssh2; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Vector; + +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.packets.TypesWriter; +import com.trilead.ssh2.sftp.AttribFlags; +import com.trilead.ssh2.sftp.ErrorCodes; +import com.trilead.ssh2.sftp.Packet; + + +/** + * A <code>SFTPv3Client</code> represents a SFTP (protocol version 3) + * client connection tunnelled over a SSH-2 connection. This is a very simple + * (synchronous) implementation. + * <p> + * Basically, most methods in this class map directly to one of + * the packet types described in draft-ietf-secsh-filexfer-02.txt. + * <p> + * Note: this is experimental code. + * <p> + * Error handling: the methods of this class throw IOExceptions. However, unless + * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will + * be thrown (a subclass of IOException). Therefore, you can implement more verbose + * behavior by checking if a thrown exception if of this type. If yes, then you + * can cast the exception and access detailed information about the failure. + * <p> + * Notes about file names, directory names and paths, copy-pasted + * from the specs: + * <ul> + * <li>SFTP v3 represents file names as strings. File names are + * assumed to use the slash ('/') character as a directory separator.</li> + * <li>File names starting with a slash are "absolute", and are relative to + * the root of the file system. Names starting with any other character + * are relative to the user's default directory (home directory).</li> + * <li>Servers SHOULD interpret a path name component ".." as referring to + * the parent directory, and "." as referring to the current directory. + * If the server implementation limits access to certain parts of the + * file system, it must be extra careful in parsing file names when + * enforcing such restrictions. There have been numerous reported + * security bugs where a ".." in a path name has allowed access outside + * the intended area.</li> + * <li>An empty path name is valid, and it refers to the user's default + * directory (usually the user's home directory).</li> + * </ul> + * <p> + * If you are still not tired then please go on and read the comment for + * {@link #setCharset(String)}. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SFTPv3Client.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $ + */ +public class SFTPv3Client +{ + final Connection conn; + final Session sess; + final PrintStream debug; + + boolean flag_closed = false; + + InputStream is; + OutputStream os; + + int protocol_version = 0; + HashMap server_extensions = new HashMap(); + + int next_request_id = 1000; + + String charsetName = null; + + /** + * Create a SFTP v3 client. + * + * @param conn The underlying SSH-2 connection to be used. + * @param debug + * @throws IOException + * + * @deprecated this constructor (debug version) will disappear in the future, + * use {@link #SFTPv3Client(Connection)} instead. + */ + public SFTPv3Client(Connection conn, PrintStream debug) throws IOException + { + if (conn == null) + throw new IllegalArgumentException("Cannot accept null argument!"); + + this.conn = conn; + this.debug = debug; + + if (debug != null) + debug.println("Opening session and starting SFTP subsystem."); + + sess = conn.openSession(); + sess.startSubSystem("sftp"); + + is = sess.getStdout(); + os = new BufferedOutputStream(sess.getStdin(), 2048); + + if ((is == null) || (os == null)) + throw new IOException("There is a problem with the streams of the underlying channel."); + + init(); + } + + /** + * Create a SFTP v3 client. + * + * @param conn The underlying SSH-2 connection to be used. + * @throws IOException + */ + public SFTPv3Client(Connection conn) throws IOException + { + this(conn, null); + } + + /** + * Set the charset used to convert between Java Unicode Strings and byte encodings + * used by the server for paths and file names. Unfortunately, the SFTP v3 draft + * says NOTHING about such conversions (well, with the exception of error messages + * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names + * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3 + * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with + * filenames containing german umlauts). "windows-1252" seems to work better for Europe. + * Luckily, "windows-1252" is the platform default in my case =). + * <p> + * If you don't set anything, then the platform default will be used (this is the default + * behavior). + * + * @see #getCharset() + * + * @param charset the name of the charset to be used or <code>null</code> to use the platform's + * default encoding. + * @throws IOException + */ + public void setCharset(String charset) throws IOException + { + if (charset == null) + { + charsetName = charset; + return; + } + + try + { + Charset.forName(charset); + } + catch (Exception e) + { + throw (IOException) new IOException("This charset is not supported").initCause(e); + } + charsetName = charset; + } + + /** + * The currently used charset for filename encoding/decoding. + * + * @see #setCharset(String) + * + * @return The name of the charset (<code>null</code> if the platform's default charset is being used) + */ + public String getCharset() + { + return charsetName; + } + + private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException + { + if (handle.client != this) + throw new IOException("The file handle was created with another SFTPv3FileHandle instance."); + + if (handle.isClosed == true) + throw new IOException("The file handle is closed."); + } + + private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException + { + int msglen = len + 1; + + if (type != Packet.SSH_FXP_INIT) + msglen += 4; + + os.write(msglen >> 24); + os.write(msglen >> 16); + os.write(msglen >> 8); + os.write(msglen); + os.write(type); + + if (type != Packet.SSH_FXP_INIT) + { + os.write(requestId >> 24); + os.write(requestId >> 16); + os.write(requestId >> 8); + os.write(requestId); + } + + os.write(msg, off, len); + os.flush(); + } + + private final void sendMessage(int type, int requestId, byte[] msg) throws IOException + { + sendMessage(type, requestId, msg, 0, msg.length); + } + + private final void readBytes(byte[] buff, int pos, int len) throws IOException + { + while (len > 0) + { + int count = is.read(buff, pos, len); + if (count < 0) + throw new IOException("Unexpected end of sftp stream."); + if ((count == 0) || (count > len)) + throw new IOException("Underlying stream implementation is bogus!"); + len -= count; + pos += count; + } + } + + /** + * Read a message and guarantee that the <b>contents</b> is not larger than + * <code>maxlen</code> bytes. + * <p> + * Note: receiveMessage(34000) actually means that the message may be up to 34004 + * bytes (the length attribute preceeding the contents is 4 bytes). + * + * @param maxlen + * @return the message contents + * @throws IOException + */ + private final byte[] receiveMessage(int maxlen) throws IOException + { + byte[] msglen = new byte[4]; + + readBytes(msglen, 0, 4); + + int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff)); + + if ((len > maxlen) || (len <= 0)) + throw new IOException("Illegal sftp packet len: " + len); + + byte[] msg = new byte[len]; + + readBytes(msg, 0, len); + + return msg; + } + + private final int generateNextRequestID() + { + synchronized (this) + { + return next_request_id++; + } + } + + private final void closeHandle(byte[] handle) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + + sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException + { + /* + * uint32 flags + * uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + * uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID + * uint32 gid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID + * uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + * uint32 atime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME + * uint32 mtime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME + * uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + * string extended_type + * string extended_data + * ... more extended data (extended_type - extended_data pairs), + * so that number of pairs equals extended_count + */ + + SFTPv3FileAttributes fa = new SFTPv3FileAttributes(); + + int flags = tr.readUINT32(); + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) + { + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_SIZE"); + fa.size = new Long(tr.readUINT64()); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) + { + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_V3_UIDGID"); + fa.uid = new Integer(tr.readUINT32()); + fa.gid = new Integer(tr.readUINT32()); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) + { + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_PERMISSIONS"); + fa.permissions = new Integer(tr.readUINT32()); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) + { + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME"); + fa.atime = new Long(((long)tr.readUINT32()) & 0xffffffffl); + fa.mtime = new Long(((long)tr.readUINT32()) & 0xffffffffl); + + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) + { + int count = tr.readUINT32(); + + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")"); + + /* Read it anyway to detect corrupt packets */ + + while (count > 0) + { + tr.readByteString(); + tr.readByteString(); + count--; + } + } + + return fa; + } + + /** + * Retrieve the file attributes of an open file. + * + * @param handle a SFTPv3FileHandle handle. + * @return a SFTPv3FileAttributes object. + * @throws IOException + */ + public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException + { + checkHandleValidAndOpen(handle); + + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); + + if (debug != null) + { + debug.println("Sending SSH_FXP_FSTAT..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + if (debug != null) + { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_ATTRS) + { + return readAttrs(tr); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + throw new SFTPException(tr.readString(), errorCode); + } + + private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); + + if (debug != null) + { + debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT..."); + debug.flush(); + } + + sendMessage(statMethod, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + if (debug != null) + { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_ATTRS) + { + return readAttrs(tr); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + throw new SFTPException(tr.readString(), errorCode); + } + + /** + * Retrieve the file attributes of a file. This method + * follows symbolic links on the server. + * + * @see #lstat(String) + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileAttributes object. + * @throws IOException + */ + public SFTPv3FileAttributes stat(String path) throws IOException + { + return statBoth(path, Packet.SSH_FXP_STAT); + } + + /** + * Retrieve the file attributes of a file. This method + * does NOT follow symbolic links on the server. + * + * @see #stat(String) + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileAttributes object. + * @throws IOException + */ + public SFTPv3FileAttributes lstat(String path) throws IOException + { + return statBoth(path, Packet.SSH_FXP_LSTAT); + } + + /** + * Read the target of a symbolic link. + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @return The target of the link. + * @throws IOException + */ + public String readLink(String path) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); + + if (debug != null) + { + debug.println("Sending SSH_FXP_READLINK..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + if (debug != null) + { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_NAME) + { + int count = tr.readUINT32(); + + if (count != 1) + throw new IOException("The server sent an invalid SSH_FXP_NAME packet."); + + return tr.readString(charsetName); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + throw new SFTPException(tr.readString(), errorCode); + } + + private void expectStatusOKMessage(int id) throws IOException + { + byte[] resp = receiveMessage(34000); + + if (debug != null) + { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != id) + throw new IOException("The server sent an invalid id field."); + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_OK) + return; + + throw new SFTPException(tr.readString(), errorCode); + } + + /** + * Modify the attributes of a file. Used for operations such as changing + * the ownership, permissions or access times, as well as for truncating a file. + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be + * made to the attributes of the file. Empty fields will be ignored. + * @throws IOException + */ + public void setstat(String path, SFTPv3FileAttributes attr) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); + tw.writeBytes(createAttrs(attr)); + + if (debug != null) + { + debug.println("Sending SSH_FXP_SETSTAT..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Modify the attributes of a file. Used for operations such as changing + * the ownership, permissions or access times, as well as for truncating a file. + * + * @param handle a SFTPv3FileHandle handle + * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be + * made to the attributes of the file. Empty fields will be ignored. + * @throws IOException + */ + public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException + { + checkHandleValidAndOpen(handle); + + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); + tw.writeBytes(createAttrs(attr)); + + if (debug != null) + { + debug.println("Sending SSH_FXP_FSETSTAT..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Create a symbolic link on the server. Creates a link "src" that points + * to "target". + * + * @param src See the {@link SFTPv3Client comment} for the class for more details. + * @param target See the {@link SFTPv3Client comment} for the class for more details. + * @throws IOException + */ + public void createSymlink(String src, String target) throws IOException + { + int req_id = generateNextRequestID(); + + /* Either I am too stupid to understand the SFTP draft + * or the OpenSSH guys changed the semantics of src and target. + */ + + TypesWriter tw = new TypesWriter(); + tw.writeString(target, charsetName); + tw.writeString(src, charsetName); + + if (debug != null) + { + debug.println("Sending SSH_FXP_SYMLINK..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Have the server canonicalize any given path name to an absolute path. + * This is useful for converting path names containing ".." components or + * relative pathnames without a leading slash into absolute paths. + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @return An absolute path. + * @throws IOException + */ + public String canonicalPath(String path) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); + + if (debug != null) + { + debug.println("Sending SSH_FXP_REALPATH..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + if (debug != null) + { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_NAME) + { + int count = tr.readUINT32(); + + if (count != 1) + throw new IOException("The server sent an invalid SSH_FXP_NAME packet."); + + return tr.readString(charsetName); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + throw new SFTPException(tr.readString(), errorCode); + } + + private final Vector scanDirectory(byte[] handle) throws IOException + { + Vector files = new Vector(); + + while (true) + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + + if (debug != null) + { + debug.println("Sending SSH_FXP_READDIR..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes()); + + /* Some servers send here a packet with size > 34000 */ + /* To whom it may concern: please learn to read the specs. */ + + byte[] resp = receiveMessage(65536); + + if (debug != null) + { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_NAME) + { + int count = tr.readUINT32(); + + if (debug != null) + debug.println("Parsing " + count + " name entries..."); + + while (count > 0) + { + SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry(); + + dirEnt.filename = tr.readString(charsetName); + dirEnt.longEntry = tr.readString(charsetName); + + dirEnt.attributes = readAttrs(tr); + files.addElement(dirEnt); + + if (debug != null) + debug.println("File: '" + dirEnt.filename + "'"); + count--; + } + continue; + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_EOF) + return files; + + throw new SFTPException(tr.readString(), errorCode); + } + } + + private final byte[] openDirectory(String path) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); + + if (debug != null) + { + debug.println("Sending SSH_FXP_OPENDIR..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_HANDLE) + { + if (debug != null) + { + debug.println("Got SSH_FXP_HANDLE."); + debug.flush(); + } + + byte[] handle = tr.readByteString(); + return handle; + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + + throw new SFTPException(errorMessage, errorCode); + } + + private final String expandString(byte[] b, int off, int len) + { + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < len; i++) + { + int c = b[off + i] & 0xff; + + if ((c >= 32) && (c <= 126)) + { + sb.append((char) c); + } + else + { + sb.append("{0x" + Integer.toHexString(c) + "}"); + } + } + + return sb.toString(); + } + + private void init() throws IOException + { + /* Send SSH_FXP_INIT (version 3) */ + + final int client_version = 3; + + if (debug != null) + debug.println("Sending SSH_FXP_INIT (" + client_version + ")..."); + + TypesWriter tw = new TypesWriter(); + tw.writeUINT32(client_version); + sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes()); + + /* Receive SSH_FXP_VERSION */ + + if (debug != null) + debug.println("Waiting for SSH_FXP_VERSION..."); + + TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */ + + int type = tr.readByte(); + + if (type != Packet.SSH_FXP_VERSION) + { + throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")"); + } + + protocol_version = tr.readUINT32(); + + if (debug != null) + debug.println("SSH_FXP_VERSION: protocol_version = " + protocol_version); + + if (protocol_version != 3) + throw new IOException("Server version " + protocol_version + " is currently not supported"); + + /* Read and save extensions (if any) for later use */ + + while (tr.remain() != 0) + { + String name = tr.readString(); + byte[] value = tr.readByteString(); + server_extensions.put(name, value); + + if (debug != null) + debug.println("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length) + + "'"); + } + } + + /** + * Returns the negotiated SFTP protocol version between the client and the server. + * + * @return SFTP protocol version, i.e., "3". + * + */ + public int getProtocolVersion() + { + return protocol_version; + } + + /** + * Close this SFTP session. NEVER forget to call this method to free up + * resources - even if you got an exception from one of the other methods. + * Sometimes these other methods may throw an exception, saying that the + * underlying channel is closed (this can happen, e.g., if the other server + * sent a close message.) However, as long as you have not called the + * <code>close()</code> method, you are likely wasting resources. + * + */ + public void close() + { + sess.close(); + } + + /** + * List the contents of a directory. + * + * @param dirName See the {@link SFTPv3Client comment} for the class for more details. + * @return A Vector containing {@link SFTPv3DirectoryEntry} objects. + * @throws IOException + */ + public Vector ls(String dirName) throws IOException + { + byte[] handle = openDirectory(dirName); + Vector result = scanDirectory(handle); + closeHandle(handle); + return result; + } + + /** + * Create a new directory. + * + * @param dirName See the {@link SFTPv3Client comment} for the class for more details. + * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that + * this is octal noation). The server will likely apply a umask. + * + * @throws IOException + */ + public void mkdir(String dirName, int posixPermissions) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(dirName, charsetName); + tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS); + tw.writeUINT32(posixPermissions); + + sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Remove a file. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @throws IOException + */ + public void rm(String fileName) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(fileName, charsetName); + + sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Remove an empty directory. + * + * @param dirName See the {@link SFTPv3Client comment} for the class for more details. + * @throws IOException + */ + public void rmdir(String dirName) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(dirName, charsetName); + + sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Move a file or directory. + * + * @param oldPath See the {@link SFTPv3Client comment} for the class for more details. + * @param newPath See the {@link SFTPv3Client comment} for the class for more details. + * @throws IOException + */ + public void mv(String oldPath, String newPath) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(oldPath, charsetName); + tw.writeString(newPath, charsetName); + + sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Open a file for reading. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRO(String fileName) throws IOException + { + return openFile(fileName, 0x00000001, null); // SSH_FXF_READ + } + + /** + * Open a file for reading and writing. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRW(String fileName) throws IOException + { + return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE + } + + // Append is broken (already in the specification, because there is no way to + // send a write operation (what offset to use??)) + // public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException + // { + // return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND + // } + + /** + * Create a file and open it for reading and writing. + * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFile(String fileName) throws IOException + { + return createFile(fileName, null); + } + + /** + * Create a file and open it for reading and writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @param attr may be <code>null</code> to use server defaults. Probably only + * the <code>uid</code>, <code>gid</code> and <code>permissions</code> + * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException + { + return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE + } + + /** + * Create a file (truncate it if it already exists) and open it for reading and writing. + * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException + { + return createFileTruncate(fileName, null); + } + + /** + * reate a file (truncate it if it already exists) and open it for reading and writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @param attr may be <code>null</code> to use server defaults. Probably only + * the <code>uid</code>, <code>gid</code> and <code>permissions</code> + * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException + { + return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE + } + + private byte[] createAttrs(SFTPv3FileAttributes attr) + { + TypesWriter tw = new TypesWriter(); + + int attrFlags = 0; + + if (attr == null) + { + tw.writeUINT32(0); + } + else + { + if (attr.size != null) + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE; + + if ((attr.uid != null) && (attr.gid != null)) + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID; + + if (attr.permissions != null) + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS; + + if ((attr.atime != null) && (attr.mtime != null)) + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME; + + tw.writeUINT32(attrFlags); + + if (attr.size != null) + tw.writeUINT64(attr.size.longValue()); + + if ((attr.uid != null) && (attr.gid != null)) + { + tw.writeUINT32(attr.uid.intValue()); + tw.writeUINT32(attr.gid.intValue()); + } + + if (attr.permissions != null) + tw.writeUINT32(attr.permissions.intValue()); + + if ((attr.atime != null) && (attr.mtime != null)) + { + tw.writeUINT32(attr.atime.intValue()); + tw.writeUINT32(attr.mtime.intValue()); + } + } + + return tw.getBytes(); + } + + private SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException + { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(fileName, charsetName); + tw.writeUINT32(flags); + tw.writeBytes(createAttrs(attr)); + + if (debug != null) + { + debug.println("Sending SSH_FXP_OPEN..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_HANDLE) + { + if (debug != null) + { + debug.println("Got SSH_FXP_HANDLE."); + debug.flush(); + } + + return new SFTPv3FileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + + throw new SFTPException(errorMessage, errorCode); + } + + /** + * Read bytes from a file. No more than 32768 bytes may be read at once. + * Be aware that the semantics of read() are different than for Java streams. + * <p> + * <ul> + * <li>The server will read as many bytes as it can from the file (up to <code>len</code>), + * and return them.</li> + * <li>If EOF is encountered before reading any data, <code>-1</code> is returned. + * <li>If an error occurs, an exception is thrown</li>. + * <li>For normal disk files, it is guaranteed that the server will return the specified + * number of bytes, or up to end of file. For, e.g., device files this may return + * fewer bytes than requested.</li> + * </ul> + * + * @param handle a SFTPv3FileHandle handle + * @param fileOffset offset (in bytes) in the file + * @param dst the destination byte array + * @param dstoff offset in the destination byte array + * @param len how many bytes to read, 0 < len <= 32768 bytes + * @return the number of bytes that could be read, may be less than requested if + * the end of the file is reached, -1 is returned in case of <code>EOF</code> + * @throws IOException + */ + public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException + { + checkHandleValidAndOpen(handle); + + if ((len > 32768) || (len <= 0)) + throw new IllegalArgumentException("invalid len argument"); + + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); + tw.writeUINT64(fileOffset); + tw.writeUINT32(len); + + if (debug != null) + { + debug.println("Sending SSH_FXP_READ..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_DATA) + { + if (debug != null) + { + debug.println("Got SSH_FXP_DATA..."); + debug.flush(); + } + + int readLen = tr.readUINT32(); + + if ((readLen < 0) || (readLen > len)) + throw new IOException("The server sent an invalid length field."); + + tr.readBytes(dst, dstoff, readLen); + + return readLen; + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_EOF) + { + if (debug != null) + { + debug.println("Got SSH_FX_EOF."); + debug.flush(); + } + + return -1; + } + + String errorMessage = tr.readString(); + + throw new SFTPException(errorMessage, errorCode); + } + + /** + * Write bytes to a file. If <code>len</code> > 32768, then the write operation will + * be split into multiple writes. + * + * @param handle a SFTPv3FileHandle handle. + * @param fileOffset offset (in bytes) in the file. + * @param src the source byte array. + * @param srcoff offset in the source byte array. + * @param len how many bytes to write. + * @throws IOException + */ + public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException + { + checkHandleValidAndOpen(handle); + + while (len > 0) + { + int writeRequestLen = len; + + if (writeRequestLen > 32768) + writeRequestLen = 32768; + + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); + tw.writeUINT64(fileOffset); + tw.writeString(src, srcoff, writeRequestLen); + + if (debug != null) + { + debug.println("Sending SSH_FXP_WRITE..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes()); + + fileOffset += writeRequestLen; + + srcoff += writeRequestLen; + len -= writeRequestLen; + + byte[] resp = receiveMessage(34000); + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_OK) + continue; + + String errorMessage = tr.readString(); + + throw new SFTPException(errorMessage, errorCode); + } + } + + /** + * Close a file. + * + * @param handle a SFTPv3FileHandle handle + * @throws IOException + */ + public void closeFile(SFTPv3FileHandle handle) throws IOException + { + if (handle == null) + throw new IllegalArgumentException("the handle argument may not be null"); + + try + { + if (handle.isClosed == false) + { + closeHandle(handle.fileHandle); + } + } + finally + { + handle.isClosed = true; + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/SFTPv3DirectoryEntry.java b/app/src/main/java/com/trilead/ssh2/SFTPv3DirectoryEntry.java new file mode 100644 index 0000000..669ba87 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/SFTPv3DirectoryEntry.java @@ -0,0 +1,38 @@ + +package com.trilead.ssh2; + +/** + * A <code>SFTPv3DirectoryEntry</code> as returned by {@link SFTPv3Client#ls(String)}. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SFTPv3DirectoryEntry.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public class SFTPv3DirectoryEntry +{ + /** + * A relative name within the directory, without any path components. + */ + public String filename; + + /** + * An expanded format for the file name, similar to what is returned by + * "ls -l" on Un*x systems. + * <p> + * The format of this field is unspecified by the SFTP v3 protocol. + * It MUST be suitable for use in the output of a directory listing + * command (in fact, the recommended operation for a directory listing + * command is to simply display this data). However, clients SHOULD NOT + * attempt to parse the longname field for file attributes; they SHOULD + * use the attrs field instead. + * <p> + * The recommended format for the longname field is as follows:<br> + * <code>-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer</code> + */ + public String longEntry; + + /** + * The attributes of this entry. + */ + public SFTPv3FileAttributes attributes; +} diff --git a/app/src/main/java/com/trilead/ssh2/SFTPv3FileAttributes.java b/app/src/main/java/com/trilead/ssh2/SFTPv3FileAttributes.java new file mode 100644 index 0000000..7b1d321 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/SFTPv3FileAttributes.java @@ -0,0 +1,145 @@ + +package com.trilead.ssh2; + +/** + * A <code>SFTPv3FileAttributes</code> object represents detail information + * about a file on the server. Not all fields may/must be present. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SFTPv3FileAttributes.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ + +public class SFTPv3FileAttributes +{ + /** + * The SIZE attribute. <code>NULL</code> if not present. + */ + public Long size = null; + + /** + * The UID attribute. <code>NULL</code> if not present. + */ + public Integer uid = null; + + /** + * The GID attribute. <code>NULL</code> if not present. + */ + public Integer gid = null; + + /** + * The POSIX permissions. <code>NULL</code> if not present. + * <p> + * Here is a list: + * <p> + * <pre>Note: these numbers are all OCTAL. + * + * S_IFMT 0170000 bitmask for the file type bitfields + * S_IFSOCK 0140000 socket + * S_IFLNK 0120000 symbolic link + * S_IFREG 0100000 regular file + * S_IFBLK 0060000 block device + * S_IFDIR 0040000 directory + * S_IFCHR 0020000 character device + * S_IFIFO 0010000 fifo + * S_ISUID 0004000 set UID bit + * S_ISGID 0002000 set GID bit + * S_ISVTX 0001000 sticky bit + * + * S_IRWXU 00700 mask for file owner permissions + * S_IRUSR 00400 owner has read permission + * S_IWUSR 00200 owner has write permission + * S_IXUSR 00100 owner has execute permission + * S_IRWXG 00070 mask for group permissions + * S_IRGRP 00040 group has read permission + * S_IWGRP 00020 group has write permission + * S_IXGRP 00010 group has execute permission + * S_IRWXO 00007 mask for permissions for others (not in group) + * S_IROTH 00004 others have read permission + * S_IWOTH 00002 others have write permisson + * S_IXOTH 00001 others have execute permission + * </pre> + */ + public Integer permissions = null; + + /** + * The ATIME attribute. Represented as seconds from Jan 1, 1970 in UTC. + * <code>NULL</code> if not present. + */ + public Long atime = null; + + /** + * The MTIME attribute. Represented as seconds from Jan 1, 1970 in UTC. + * <code>NULL</code> if not present. + */ + public Long mtime = null; + + /** + * Checks if this entry is a directory. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a directory. + */ + public boolean isDirectory() + { + if (permissions == null) + return false; + + return ((permissions.intValue() & 0040000) != 0); + } + + /** + * Checks if this entry is a regular file. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a regular file. + */ + public boolean isRegularFile() + { + if (permissions == null) + return false; + + return ((permissions.intValue() & 0100000) != 0); + } + + /** + * Checks if this entry is a a symlink. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a symlink. + */ + public boolean isSymlink() + { + if (permissions == null) + return false; + + return ((permissions.intValue() & 0120000) != 0); + } + + /** + * Turn the POSIX permissions into a 7 digit octal representation. + * Note: the returned value is first masked with <code>0177777</code>. + * + * @return <code>NULL</code> if permissions are not available. + */ + public String getOctalPermissions() + { + if (permissions == null) + return null; + + String res = Integer.toString(permissions.intValue() & 0177777, 8); + + StringBuffer sb = new StringBuffer(); + + int leadingZeros = 7 - res.length(); + + while (leadingZeros > 0) + { + sb.append('0'); + leadingZeros--; + } + + sb.append(res); + + return sb.toString(); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/SFTPv3FileHandle.java b/app/src/main/java/com/trilead/ssh2/SFTPv3FileHandle.java new file mode 100644 index 0000000..9b3dbb6 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/SFTPv3FileHandle.java @@ -0,0 +1,45 @@ + +package com.trilead.ssh2; + +/** + * A <code>SFTPv3FileHandle</code>. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SFTPv3FileHandle.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public class SFTPv3FileHandle +{ + final SFTPv3Client client; + final byte[] fileHandle; + boolean isClosed = false; + + /* The constructor is NOT public */ + + SFTPv3FileHandle(SFTPv3Client client, byte[] h) + { + this.client = client; + this.fileHandle = h; + } + + /** + * Get the SFTPv3Client instance which created this handle. + * + * @return A SFTPv3Client instance. + */ + public SFTPv3Client getClient() + { + return client; + } + + /** + * Check if this handle was closed with the {@link SFTPv3Client#closeFile(SFTPv3FileHandle)} method + * of the <code>SFTPv3Client</code> instance which created the handle. + * + * @return if the handle is closed. + */ + public boolean isClosed() + { + return isClosed; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/ServerHostKeyVerifier.java b/app/src/main/java/com/trilead/ssh2/ServerHostKeyVerifier.java new file mode 100644 index 0000000..ac65955 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/ServerHostKeyVerifier.java @@ -0,0 +1,31 @@ + +package com.trilead.ssh2; + +/** + * A callback interface used to implement a client specific method of checking + * server host keys. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ServerHostKeyVerifier.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public interface ServerHostKeyVerifier +{ + /** + * The actual verifier method, it will be called by the key exchange code + * on EVERY key exchange - this can happen several times during the lifetime + * of a connection. + * <p> + * Note: SSH-2 servers are allowed to change their hostkey at ANY time. + * + * @param hostname the hostname used to create the {@link Connection} object + * @param port the remote TCP port + * @param serverHostKeyAlgorithm the public key algorithm (<code>ssh-rsa</code> or <code>ssh-dss</code>) + * @param serverHostKey the server's public key blob + * @return if the client wants to accept the server's host key - if not, the + * connection will be closed. + * @throws Exception Will be wrapped with an IOException, extended version of returning false =) + */ + public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) + throws Exception; +} diff --git a/app/src/main/java/com/trilead/ssh2/Session.java b/app/src/main/java/com/trilead/ssh2/Session.java new file mode 100644 index 0000000..bf3c7e0 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/Session.java @@ -0,0 +1,530 @@ + +package com.trilead.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import com.trilead.ssh2.channel.Channel; +import com.trilead.ssh2.channel.ChannelManager; +import com.trilead.ssh2.channel.X11ServerData; + + +/** + * A <code>Session</code> is a remote execution of a program. "Program" means + * in this context either a shell, an application or a system command. The + * program may or may not have a tty. Only one single program can be started on + * a session. However, multiple sessions can be active simultaneously. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Session.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $ + */ +public class Session +{ + ChannelManager cm; + Channel cn; + + boolean flag_pty_requested = false; + boolean flag_x11_requested = false; + boolean flag_execution_started = false; + boolean flag_closed = false; + + String x11FakeCookie = null; + + final SecureRandom rnd; + + Session(ChannelManager cm, SecureRandom rnd) throws IOException + { + this.cm = cm; + this.cn = cm.openSessionChannel(); + this.rnd = rnd; + } + + /** + * Basically just a wrapper for lazy people - identical to calling + * <code>requestPTY("dumb", 0, 0, 0, 0, null)</code>. + * + * @throws IOException + */ + public void requestDumbPTY() throws IOException + { + requestPTY("dumb", 0, 0, 0, 0, null); + } + + /** + * Basically just another wrapper for lazy people - identical to calling + * <code>requestPTY(term, 0, 0, 0, 0, null)</code>. + * + * @throws IOException + */ + public void requestPTY(String term) throws IOException + { + requestPTY(term, 0, 0, 0, 0, null); + } + + /** + * Allocate a pseudo-terminal for this session. + * <p> + * This method may only be called before a program or shell is started in + * this session. + * <p> + * Different aspects can be specified: + * <p> + * <ul> + * <li>The TERM environment variable value (e.g., vt100)</li> + * <li>The terminal's dimensions.</li> + * <li>The encoded terminal modes.</li> + * </ul> + * Zero dimension parameters are ignored. The character/row dimensions + * override the pixel dimensions (when nonzero). Pixel dimensions refer to + * the drawable area of the window. The dimension parameters are only + * informational. The encoding of terminal modes (parameter + * <code>terminal_modes</code>) is described in RFC4254. + * + * @param term + * The TERM environment variable value (e.g., vt100) + * @param term_width_characters + * terminal width, characters (e.g., 80) + * @param term_height_characters + * terminal height, rows (e.g., 24) + * @param term_width_pixels + * terminal width, pixels (e.g., 640) + * @param term_height_pixels + * terminal height, pixels (e.g., 480) + * @param terminal_modes + * encoded terminal modes (may be <code>null</code>) + * @throws IOException + */ + public void requestPTY(String term, int term_width_characters, int term_height_characters, int term_width_pixels, + int term_height_pixels, byte[] terminal_modes) throws IOException + { + if (term == null) + throw new IllegalArgumentException("TERM cannot be null."); + + if ((terminal_modes != null) && (terminal_modes.length > 0)) + { + if (terminal_modes[terminal_modes.length - 1] != 0) + throw new IOException("Illegal terminal modes description, does not end in zero byte"); + } + else + terminal_modes = new byte[] { 0 }; + + synchronized (this) + { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_pty_requested) + throw new IOException("A PTY was already requested."); + + if (flag_execution_started) + throw new IOException( + "Cannot request PTY at this stage anymore, a remote execution has already started."); + + flag_pty_requested = true; + } + + cm.requestPTY(cn, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels, + terminal_modes); + } + + /** + * Inform other side of connection that our PTY has resized. + * <p> + * Zero dimension parameters are ignored. The character/row dimensions + * override the pixel dimensions (when nonzero). Pixel dimensions refer to + * the drawable area of the window. The dimension parameters are only + * informational. + * + * @param term_width_characters + * terminal width, characters (e.g., 80) + * @param term_height_characters + * terminal height, rows (e.g., 24) + * @param term_width_pixels + * terminal width, pixels (e.g., 640) + * @param term_height_pixels + * terminal height, pixels (e.g., 480) + * @throws IOException + */ + public void resizePTY(int term_width_characters, int term_height_characters, int term_width_pixels, + int term_height_pixels) throws IOException { + synchronized (this) + { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + } + + cm.resizePTY(cn, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels); + } + + /** + * Request X11 forwarding for the current session. + * <p> + * You have to supply the name and port of your X-server. + * <p> + * This method may only be called before a program or shell is started in + * this session. + * + * @param hostname the hostname of the real (target) X11 server (e.g., 127.0.0.1) + * @param port the port of the real (target) X11 server (e.g., 6010) + * @param cookie if non-null, then present this cookie to the real X11 server + * @param singleConnection if true, then the server is instructed to only forward one single + * connection, no more connections shall be forwarded after first, or after the session + * channel has been closed + * @throws IOException + */ + public void requestX11Forwarding(String hostname, int port, byte[] cookie, boolean singleConnection) + throws IOException + { + if (hostname == null) + throw new IllegalArgumentException("hostname argument may not be null"); + + synchronized (this) + { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_x11_requested) + throw new IOException("X11 forwarding was already requested."); + + if (flag_execution_started) + throw new IOException( + "Cannot request X11 forwarding at this stage anymore, a remote execution has already started."); + + flag_x11_requested = true; + } + + /* X11ServerData - used to store data about the target X11 server */ + + X11ServerData x11data = new X11ServerData(); + + x11data.hostname = hostname; + x11data.port = port; + x11data.x11_magic_cookie = cookie; /* if non-null, then present this cookie to the real X11 server */ + + /* Generate fake cookie - this one is used between remote clients and our proxy */ + + byte[] fakeCookie = new byte[16]; + String hexEncodedFakeCookie; + + /* Make sure that this fake cookie is unique for this connection */ + + while (true) + { + rnd.nextBytes(fakeCookie); + + /* Generate also hex representation of fake cookie */ + + StringBuffer tmp = new StringBuffer(32); + for (int i = 0; i < fakeCookie.length; i++) + { + String digit2 = Integer.toHexString(fakeCookie[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + hexEncodedFakeCookie = tmp.toString(); + + /* Well, yes, chances are low, but we want to be on the safe side */ + + if (cm.checkX11Cookie(hexEncodedFakeCookie) == null) + break; + } + + /* Ask for X11 forwarding */ + + cm.requestX11(cn, singleConnection, "MIT-MAGIC-COOKIE-1", hexEncodedFakeCookie, 0); + + /* OK, that went fine, get ready to accept X11 connections... */ + /* ... but only if the user has not called close() in the meantime =) */ + + synchronized (this) + { + if (flag_closed == false) + { + this.x11FakeCookie = hexEncodedFakeCookie; + cm.registerX11Cookie(hexEncodedFakeCookie, x11data); + } + } + + /* Now it is safe to start remote X11 programs */ + } + + /** + * Execute a command on the remote machine. + * + * @param cmd + * The command to execute on the remote host. + * @throws IOException + */ + public void execCommand(String cmd) throws IOException + { + if (cmd == null) + throw new IllegalArgumentException("cmd argument may not be null"); + + synchronized (this) + { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestExecCommand(cn, cmd); + } + + /** + * Start a shell on the remote machine. + * + * @throws IOException + */ + public void startShell() throws IOException + { + synchronized (this) + { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestShell(cn); + } + + /** + * Start a subsystem on the remote machine. + * Unless you know what you are doing, you will never need this. + * + * @param name the name of the subsystem. + * @throws IOException + */ + public void startSubSystem(String name) throws IOException + { + if (name == null) + throw new IllegalArgumentException("name argument may not be null"); + + synchronized (this) + { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestSubSystem(cn, name); + } + + /** + * This method can be used to perform end-to-end session (i.e., SSH channel) + * testing. It sends a 'ping' message to the server and waits for the 'pong' + * from the server. + * <p> + * Implementation details: this method sends a SSH_MSG_CHANNEL_REQUEST request + * ('trilead-ping') to the server and waits for the SSH_MSG_CHANNEL_FAILURE reply + * packet. + * + * @throws IOException in case of any problem or when the session is closed + */ + public void ping() throws IOException + { + synchronized (this) + { + /* + * The following is just a nicer error, we would catch it anyway + * later in the channel code + */ + if (flag_closed) + throw new IOException("This session is closed."); + } + + cm.requestChannelTrileadPing(cn); + } + + /** + * Request authentication agent forwarding. + * @param agent object that implements the callbacks + * + * @throws IOException in case of any problem or when the session is closed + */ + public synchronized boolean requestAuthAgentForwarding(AuthAgentCallback agent) throws IOException + { + synchronized (this) + { + /* + * The following is just a nicer error, we would catch it anyway + * later in the channel code + */ + if (flag_closed) + throw new IOException("This session is closed."); + } + + return cm.requestChannelAgentForwarding(cn, agent); + } + + public InputStream getStdout() + { + return cn.getStdoutStream(); + } + + public InputStream getStderr() + { + return cn.getStderrStream(); + } + + public OutputStream getStdin() + { + return cn.getStdinStream(); + } + + /** + * This method blocks until there is more data available on either the + * stdout or stderr InputStream of this <code>Session</code>. Very useful + * if you do not want to use two parallel threads for reading from the two + * InputStreams. One can also specify a timeout. NOTE: do NOT call this + * method if you use concurrent threads that operate on either of the two + * InputStreams of this <code>Session</code> (otherwise this method may + * block, even though more data is available). + * + * @param timeout + * The (non-negative) timeout in <code>ms</code>. <code>0</code> means no + * timeout, the call may block forever. + * @return + * <ul> + * <li><code>0</code> if no more data will arrive.</li> + * <li><code>1</code> if more data is available.</li> + * <li><code>-1</code> if a timeout occurred.</li> + * </ul> + * + * @throws IOException + * @deprecated This method has been replaced with a much more powerful wait-for-condition + * interface and therefore acts only as a wrapper. + * + */ + public int waitUntilDataAvailable(long timeout) throws IOException + { + if (timeout < 0) + throw new IllegalArgumentException("timeout must not be negative!"); + + int conditions = cm.waitForCondition(cn, timeout, ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA + | ChannelCondition.EOF); + + if ((conditions & ChannelCondition.TIMEOUT) != 0) + return -1; + + if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) != 0) + return 1; + + /* Here we do not need to check separately for CLOSED, since CLOSED implies EOF */ + + if ((conditions & ChannelCondition.EOF) != 0) + return 0; + + throw new IllegalStateException("Unexpected condition result (" + conditions + ")"); + } + + /** + * This method blocks until certain conditions hold true on the underlying SSH-2 channel. + * <p> + * This method returns as soon as one of the following happens: + * <ul> + * <li>at least of the specified conditions (see {@link ChannelCondition}) holds true</li> + * <li>timeout > 0 and a timeout occured (TIMEOUT will be set in result conditions)</a> + * <li>the underlying channel was closed (CLOSED will be set in result conditions)</a> + * </ul> + * <p> + * In any case, the result value contains ALL current conditions, which may be more + * than the specified condition set (i.e., never use the "==" operator to test for conditions + * in the bitmask, see also comments in {@link ChannelCondition}). + * <p> + * Note: do NOT call this method if you want to wait for STDOUT_DATA or STDERR_DATA and + * there are concurrent threads (e.g., StreamGobblers) that operate on either of the two + * InputStreams of this <code>Session</code> (otherwise this method may + * block, even though more data is available in the StreamGobblers). + * + * @param condition_set a bitmask based on {@link ChannelCondition} values + * @param timeout non-negative timeout in ms, <code>0</code> means no timeout + * @return all bitmask specifying all current conditions that are true + */ + + public int waitForCondition(int condition_set, long timeout) + { + if (timeout < 0) + throw new IllegalArgumentException("timeout must be non-negative!"); + + return cm.waitForCondition(cn, timeout, condition_set); + } + + /** + * Get the exit code/status from the remote command - if available. Be + * careful - not all server implementations return this value. It is + * generally a good idea to call this method only when all data from the + * remote side has been consumed (see also the <code<WaitForCondition</code> method). + * + * @return An <code>Integer</code> holding the exit code, or + * <code>null</code> if no exit code is (yet) available. + */ + public Integer getExitStatus() + { + return cn.getExitStatus(); + } + + /** + * Get the name of the signal by which the process on the remote side was + * stopped - if available and applicable. Be careful - not all server + * implementations return this value. + * + * @return An <code>String</code> holding the name of the signal, or + * <code>null</code> if the process exited normally or is still + * running (or if the server forgot to send this information). + */ + public String getExitSignal() + { + return cn.getExitSignal(); + } + + /** + * Close this session. NEVER forget to call this method to free up resources - + * even if you got an exception from one of the other methods (or when + * getting an Exception on the Input- or OutputStreams). Sometimes these other + * methods may throw an exception, saying that the underlying channel is + * closed (this can happen, e.g., if the other server sent a close message.) + * However, as long as you have not called the <code>close()</code> + * method, you may be wasting (local) resources. + * + */ + public void close() + { + synchronized (this) + { + if (flag_closed) + return; + + flag_closed = true; + + if (x11FakeCookie != null) + cm.unRegisterX11Cookie(x11FakeCookie, true); + + try + { + cm.closeChannel(cn, "Closed due to user request", true); + } + catch (IOException ignored) + { + } + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/StreamGobbler.java b/app/src/main/java/com/trilead/ssh2/StreamGobbler.java new file mode 100644 index 0000000..e93c388 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/StreamGobbler.java @@ -0,0 +1,229 @@ + +package com.trilead.ssh2; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A <code>StreamGobbler</code> is an InputStream that uses an internal worker + * thread to constantly consume input from another InputStream. It uses a buffer + * to store the consumed data. The buffer size is automatically adjusted, if needed. + * <p> + * This class is sometimes very convenient - if you wrap a session's STDOUT and STDERR + * InputStreams with instances of this class, then you don't have to bother about + * the shared window of STDOUT and STDERR in the low level SSH-2 protocol, + * since all arriving data will be immediatelly consumed by the worker threads. + * Also, as a side effect, the streams will be buffered (e.g., single byte + * read() operations are faster). + * <p> + * Other SSH for Java libraries include this functionality by default in + * their STDOUT and STDERR InputStream implementations, however, please be aware + * that this approach has also a downside: + * <p> + * If you do not call the StreamGobbler's <code>read()</code> method often enough + * and the peer is constantly sending huge amounts of data, then you will sooner or later + * encounter a low memory situation due to the aggregated data (well, it also depends on the Java heap size). + * Joe Average will like this class anyway - a paranoid programmer would never use such an approach. + * <p> + * The term "StreamGobbler" was taken from an article called "When Runtime.exec() won't", + * see http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: StreamGobbler.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public class StreamGobbler extends InputStream +{ + class GobblerThread extends Thread + { + public void run() + { + byte[] buff = new byte[8192]; + + while (true) + { + try + { + int avail = is.read(buff); + + synchronized (synchronizer) + { + if (avail <= 0) + { + isEOF = true; + synchronizer.notifyAll(); + break; + } + + int space_available = buffer.length - write_pos; + + if (space_available < avail) + { + /* compact/resize buffer */ + + int unread_size = write_pos - read_pos; + int need_space = unread_size + avail; + + byte[] new_buffer = buffer; + + if (need_space > buffer.length) + { + int inc = need_space / 3; + inc = (inc < 256) ? 256 : inc; + inc = (inc > 8192) ? 8192 : inc; + new_buffer = new byte[need_space + inc]; + } + + if (unread_size > 0) + System.arraycopy(buffer, read_pos, new_buffer, 0, unread_size); + + buffer = new_buffer; + + read_pos = 0; + write_pos = unread_size; + } + + System.arraycopy(buff, 0, buffer, write_pos, avail); + write_pos += avail; + + synchronizer.notifyAll(); + } + } + catch (IOException e) + { + synchronized (synchronizer) + { + exception = e; + synchronizer.notifyAll(); + break; + } + } + } + } + } + + private InputStream is; + private GobblerThread t; + + private Object synchronizer = new Object(); + + private boolean isEOF = false; + private boolean isClosed = false; + private IOException exception = null; + + private byte[] buffer = new byte[2048]; + private int read_pos = 0; + private int write_pos = 0; + + public StreamGobbler(InputStream is) + { + this.is = is; + t = new GobblerThread(); + t.setDaemon(true); + t.start(); + } + + public int read() throws IOException + { + synchronized (synchronizer) + { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + while (read_pos == write_pos) + { + if (exception != null) + throw exception; + + if (isEOF) + return -1; + + try + { + synchronizer.wait(); + } + catch (InterruptedException e) + { + } + } + + int b = buffer[read_pos++] & 0xff; + + return b; + } + } + + public int available() throws IOException + { + synchronized (synchronizer) + { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + return write_pos - read_pos; + } + } + + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + public void close() throws IOException + { + synchronized (synchronizer) + { + if (isClosed) + return; + isClosed = true; + isEOF = true; + synchronizer.notifyAll(); + is.close(); + } + } + + public int read(byte[] b, int off, int len) throws IOException + { + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return 0; + + synchronized (synchronizer) + { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + while (read_pos == write_pos) + { + if (exception != null) + throw exception; + + if (isEOF) + return -1; + + try + { + synchronizer.wait(); + } + catch (InterruptedException e) + { + } + } + + int avail = write_pos - read_pos; + + avail = (avail > len) ? len : avail; + + System.arraycopy(buffer, read_pos, b, off, avail); + + read_pos += avail; + + return avail; + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java b/app/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java new file mode 100644 index 0000000..e551495 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java @@ -0,0 +1,466 @@ + +package com.trilead.ssh2.auth; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Vector; + +import com.trilead.ssh2.InteractiveCallback; +import com.trilead.ssh2.crypto.PEMDecoder; +import com.trilead.ssh2.packets.PacketServiceAccept; +import com.trilead.ssh2.packets.PacketServiceRequest; +import com.trilead.ssh2.packets.PacketUserauthBanner; +import com.trilead.ssh2.packets.PacketUserauthFailure; +import com.trilead.ssh2.packets.PacketUserauthInfoRequest; +import com.trilead.ssh2.packets.PacketUserauthInfoResponse; +import com.trilead.ssh2.packets.PacketUserauthRequestInteractive; +import com.trilead.ssh2.packets.PacketUserauthRequestNone; +import com.trilead.ssh2.packets.PacketUserauthRequestPassword; +import com.trilead.ssh2.packets.PacketUserauthRequestPublicKey; +import com.trilead.ssh2.packets.Packets; +import com.trilead.ssh2.packets.TypesWriter; +import com.trilead.ssh2.signature.DSASHA1Verify; +import com.trilead.ssh2.signature.ECDSASHA2Verify; +import com.trilead.ssh2.signature.RSASHA1Verify; +import com.trilead.ssh2.transport.MessageHandler; +import com.trilead.ssh2.transport.TransportManager; + + +/** + * AuthenticationManager. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: AuthenticationManager.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class AuthenticationManager implements MessageHandler +{ + TransportManager tm; + + Vector packets = new Vector(); + boolean connectionClosed = false; + + String banner; + + String[] remainingMethods = new String[0]; + boolean isPartialSuccess = false; + + boolean authenticated = false; + boolean initDone = false; + + public AuthenticationManager(TransportManager tm) + { + this.tm = tm; + } + + boolean methodPossible(String methName) + { + if (remainingMethods == null) + return false; + + for (int i = 0; i < remainingMethods.length; i++) + { + if (remainingMethods[i].compareTo(methName) == 0) + return true; + } + return false; + } + + byte[] deQueue() throws IOException + { + synchronized (packets) + { + while (packets.size() == 0) + { + if (connectionClosed) + throw (IOException) new IOException("The connection is closed.").initCause(tm + .getReasonClosedCause()); + + try + { + packets.wait(); + } + catch (InterruptedException ign) + { + } + } + /* This sequence works with J2ME */ + byte[] res = (byte[]) packets.firstElement(); + packets.removeElementAt(0); + return res; + } + } + + byte[] getNextMessage() throws IOException + { + while (true) + { + byte[] msg = deQueue(); + + if (msg[0] != Packets.SSH_MSG_USERAUTH_BANNER) + return msg; + + PacketUserauthBanner sb = new PacketUserauthBanner(msg, 0, msg.length); + + banner = sb.getBanner(); + } + } + + public String[] getRemainingMethods(String user) throws IOException + { + initialize(user); + return remainingMethods; + } + + public boolean getPartialSuccess() + { + return isPartialSuccess; + } + + private boolean initialize(String user) throws IOException + { + if (initDone == false) + { + tm.registerMessageHandler(this, 0, 255); + + PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth"); + tm.sendMessage(sr.getPayload()); + + PacketUserauthRequestNone urn = new PacketUserauthRequestNone("ssh-connection", user); + tm.sendMessage(urn.getPayload()); + + byte[] msg = getNextMessage(); + new PacketServiceAccept(msg, 0, msg.length); + msg = getNextMessage(); + + initDone = true; + + if (msg[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) + { + authenticated = true; + tm.removeMessageHandler(this, 0, 255); + return true; + } + + if (msg[0] == Packets.SSH_MSG_USERAUTH_FAILURE) + { + PacketUserauthFailure puf = new PacketUserauthFailure(msg, 0, msg.length); + + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new IOException("Unexpected SSH message (type " + msg[0] + ")"); + } + return authenticated; + } + + public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd) + throws IOException + { + KeyPair pair = PEMDecoder.decode(PEMPrivateKey, password); + + return authenticatePublicKey(user, pair, rnd); + } + + public boolean authenticatePublicKey(String user, KeyPair pair, SecureRandom rnd) + throws IOException + { + PrivateKey key = pair.getPrivate(); + + try + { + initialize(user); + + if (methodPossible("publickey") == false) + throw new IOException("Authentication method publickey not supported by the server at this stage."); + + if (key instanceof DSAPrivateKey) + { + DSAPrivateKey pk = (DSAPrivateKey) key; + + byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pair.getPublic()); + + TypesWriter tw = new TypesWriter(); + + byte[] H = tm.getSessionIdentifier(); + + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString("ssh-dss"); + tw.writeString(pk_enc, 0, pk_enc.length); + + byte[] msg = tw.getBytes(); + + byte[] ds = DSASHA1Verify.generateSignature(msg, pk, rnd); + + byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds); + + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-dss", pk_enc, ds_enc); + tm.sendMessage(ua.getPayload()); + } + else if (key instanceof RSAPrivateKey) + { + RSAPrivateKey pk = (RSAPrivateKey) key; + + byte[] pk_enc = RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pair.getPublic()); + + TypesWriter tw = new TypesWriter(); + { + byte[] H = tm.getSessionIdentifier(); + + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString("ssh-rsa"); + tw.writeString(pk_enc, 0, pk_enc.length); + } + + byte[] msg = tw.getBytes(); + + byte[] ds = RSASHA1Verify.generateSignature(msg, pk); + + byte[] rsa_sig_enc = RSASHA1Verify.encodeSSHRSASignature(ds); + + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-rsa", pk_enc, rsa_sig_enc); + + tm.sendMessage(ua.getPayload()); + } + else if (key instanceof ECPrivateKey) + { + ECPrivateKey pk = (ECPrivateKey) key; + final String algo = ECDSASHA2Verify.ECDSA_SHA2_PREFIX + + ECDSASHA2Verify.getCurveName(pk.getParams()); + + byte[] pk_enc = ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey) pair.getPublic()); + + TypesWriter tw = new TypesWriter(); + { + byte[] H = tm.getSessionIdentifier(); + + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(algo); + tw.writeString(pk_enc, 0, pk_enc.length); + } + + byte[] msg = tw.getBytes(); + + byte[] ds = ECDSASHA2Verify.generateSignature(msg, pk); + + byte[] ec_sig_enc = ECDSASHA2Verify.encodeSSHECDSASignature(ds, pk.getParams()); + + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + algo, pk_enc, ec_sig_enc); + + tm.sendMessage(ua.getPayload()); + } + else + { + throw new IOException("Unknown private key type returned by the PEM decoder."); + } + + byte[] ar = getNextMessage(); + if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) + { + authenticated = true; + tm.removeMessageHandler(this, 0, 255); + return true; + } + + if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) + { + PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); + + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + + return false; + } + + throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); + + } + catch (IOException e) + { +e.printStackTrace(); + tm.close(e, false); + throw (IOException) new IOException("Publickey authentication failed.").initCause(e); + } + } + + public boolean authenticateNone(String user) throws IOException + { + try + { + initialize(user); + return authenticated; + } + catch (IOException e) + { + tm.close(e, false); + throw (IOException) new IOException("None authentication failed.").initCause(e); + } + } + + public boolean authenticatePassword(String user, String pass) throws IOException + { + try + { + initialize(user); + + if (methodPossible("password") == false) + throw new IOException("Authentication method password not supported by the server at this stage."); + + PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass); + tm.sendMessage(ua.getPayload()); + + byte[] ar = getNextMessage(); + + if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) + { + authenticated = true; + tm.removeMessageHandler(this, 0, 255); + return true; + } + + if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) + { + PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); + + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + + return false; + } + + throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); + + } + catch (IOException e) + { + tm.close(e, false); + throw (IOException) new IOException("Password authentication failed.").initCause(e); + } + } + + public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException + { + try + { + initialize(user); + + if (methodPossible("keyboard-interactive") == false) + throw new IOException( + "Authentication method keyboard-interactive not supported by the server at this stage."); + + if (submethods == null) + submethods = new String[0]; + + PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user, + submethods); + + tm.sendMessage(ua.getPayload()); + + while (true) + { + byte[] ar = getNextMessage(); + + if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) + { + authenticated = true; + tm.removeMessageHandler(this, 0, 255); + return true; + } + + if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) + { + PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); + + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + + return false; + } + + if (ar[0] == Packets.SSH_MSG_USERAUTH_INFO_REQUEST) + { + PacketUserauthInfoRequest pui = new PacketUserauthInfoRequest(ar, 0, ar.length); + + String[] responses; + + try + { + responses = cb.replyToChallenge(pui.getName(), pui.getInstruction(), pui.getNumPrompts(), pui + .getPrompt(), pui.getEcho()); + } + catch (Exception e) + { + throw (IOException) new IOException("Exception in callback.").initCause(e); + } + + if (responses == null) + throw new IOException("Your callback may not return NULL!"); + + PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses); + tm.sendMessage(puir.getPayload()); + + continue; + } + + throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); + } + } + catch (IOException e) + { + tm.close(e, false); + throw (IOException) new IOException("Keyboard-interactive authentication failed.").initCause(e); + } + } + + public void handleMessage(byte[] msg, int msglen) throws IOException + { + synchronized (packets) + { + if (msg == null) + { + connectionClosed = true; + } + else + { + byte[] tmp = new byte[msglen]; + System.arraycopy(msg, 0, tmp, 0, msglen); + packets.addElement(tmp); + } + + packets.notifyAll(); + + if (packets.size() > 5) + { + connectionClosed = true; + throw new IOException("Error, peer is flooding us with authentication packets."); + } + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java b/app/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java new file mode 100644 index 0000000..c6831e6 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java @@ -0,0 +1,579 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Map; +import java.util.Map.Entry; + +import com.trilead.ssh2.AuthAgentCallback; +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.packets.TypesWriter; +import com.trilead.ssh2.signature.DSASHA1Verify; +import com.trilead.ssh2.signature.ECDSASHA2Verify; +import com.trilead.ssh2.signature.RSASHA1Verify; + +/** + * AuthAgentForwardThread. + * + * @author Kenny Root + * @version $Id$ + */ +public class AuthAgentForwardThread extends Thread implements IChannelWorkerThread +{ + private static final byte[] SSH_AGENT_FAILURE = {0, 0, 0, 1, 5}; // 5 + private static final byte[] SSH_AGENT_SUCCESS = {0, 0, 0, 1, 6}; // 6 + + private static final int SSH2_AGENTC_REQUEST_IDENTITIES = 11; + private static final int SSH2_AGENT_IDENTITIES_ANSWER = 12; + + private static final int SSH2_AGENTC_SIGN_REQUEST = 13; + private static final int SSH2_AGENT_SIGN_RESPONSE = 14; + + private static final int SSH2_AGENTC_ADD_IDENTITY = 17; + private static final int SSH2_AGENTC_REMOVE_IDENTITY = 18; + private static final int SSH2_AGENTC_REMOVE_ALL_IDENTITIES = 19; + +// private static final int SSH_AGENTC_ADD_SMARTCARD_KEY = 20; +// private static final int SSH_AGENTC_REMOVE_SMARTCARD_KEY = 21; + + private static final int SSH_AGENTC_LOCK = 22; + private static final int SSH_AGENTC_UNLOCK = 23; + + private static final int SSH2_AGENTC_ADD_ID_CONSTRAINED = 25; +// private static final int SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED = 26; + + // Constraints for adding keys + private static final int SSH_AGENT_CONSTRAIN_LIFETIME = 1; + private static final int SSH_AGENT_CONSTRAIN_CONFIRM = 2; + + // Flags for signature requests +// private static final int SSH_AGENT_OLD_SIGNATURE = 1; + + private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); + + AuthAgentCallback authAgent; + OutputStream os; + InputStream is; + Channel c; + + byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; + + public AuthAgentForwardThread(Channel c, AuthAgentCallback authAgent) + { + this.c = c; + this.authAgent = authAgent; + + if (log.isEnabled()) + log.log(20, "AuthAgentForwardThread started"); + } + + @Override + public void run() + { + try + { + c.cm.registerThread(this); + } + catch (IOException e) + { + stopWorking(); + return; + } + + try + { + c.cm.sendOpenConfirmation(c); + + is = c.getStdoutStream(); + os = c.getStdinStream(); + + int totalSize = 4; + int readSoFar = 0; + + while (true) { + int len; + + try + { + len = is.read(buffer, readSoFar, buffer.length - readSoFar); + } + catch (IOException e) + { + stopWorking(); + return; + } + + if (len <= 0) + break; + + readSoFar += len; + + if (readSoFar >= 4) { + TypesReader tr = new TypesReader(buffer, 0, 4); + totalSize = tr.readUINT32() + 4; + } + + if (totalSize == readSoFar) { + TypesReader tr = new TypesReader(buffer, 4, readSoFar - 4); + int messageType = tr.readByte(); + + switch (messageType) { + case SSH2_AGENTC_REQUEST_IDENTITIES: + sendIdentities(); + break; + case SSH2_AGENTC_ADD_IDENTITY: + addIdentity(tr, false); + break; + case SSH2_AGENTC_ADD_ID_CONSTRAINED: + addIdentity(tr, true); + break; + case SSH2_AGENTC_REMOVE_IDENTITY: + removeIdentity(tr); + break; + case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: + removeAllIdentities(tr); + break; + case SSH2_AGENTC_SIGN_REQUEST: + processSignRequest(tr); + break; + case SSH_AGENTC_LOCK: + processLockRequest(tr); + break; + case SSH_AGENTC_UNLOCK: + processUnlockRequest(tr); + break; + default: + os.write(SSH_AGENT_FAILURE); + break; + } + + readSoFar = 0; + } + } + + c.cm.closeChannel(c, "EOF on both streams reached.", true); + } + catch (IOException e) + { + log.log(50, "IOException in agent forwarder: " + e.getMessage()); + + try + { + is.close(); + } + catch (IOException e1) + { + } + + try + { + os.close(); + } + catch (IOException e2) + { + } + + try + { + c.cm.closeChannel(c, "IOException in agent forwarder (" + e.getMessage() + ")", true); + } + catch (IOException e3) + { + } + } + } + + public void stopWorking() { + try + { + /* This will lead to an IOException in the is.read() call */ + is.close(); + } + catch (IOException e) + { + } + } + + /** + * @return whether the agent is locked + */ + private boolean failWhenLocked() throws IOException + { + if (authAgent.isAgentLocked()) { + os.write(SSH_AGENT_FAILURE); + return true; + } else + return false; + } + + private void sendIdentities() throws IOException + { + Map<String,byte[]> keys = null; + + TypesWriter tw = new TypesWriter(); + tw.writeByte(SSH2_AGENT_IDENTITIES_ANSWER); + int numKeys = 0; + + if (!authAgent.isAgentLocked()) + keys = authAgent.retrieveIdentities(); + + if (keys != null) + numKeys = keys.size(); + + tw.writeUINT32(numKeys); + + if (keys != null) { + for (Entry<String,byte[]> entry : keys.entrySet()) { + byte[] keyBytes = entry.getValue(); + tw.writeString(keyBytes, 0, keyBytes.length); + tw.writeString(entry.getKey()); + } + } + + sendPacket(tw.getBytes()); + } + + /** + * @param tr + */ + private void addIdentity(TypesReader tr, boolean checkConstraints) { + try + { + if (failWhenLocked()) + return; + + String type = tr.readString(); + + String comment; + String keyType; + KeySpec pubSpec; + KeySpec privSpec; + + if (type.equals("ssh-rsa")) { + keyType = "RSA"; + + BigInteger n = tr.readMPINT(); + BigInteger e = tr.readMPINT(); + BigInteger d = tr.readMPINT(); + BigInteger iqmp = tr.readMPINT(); + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + comment = tr.readString(); + + // Derive the extra values Java needs. + BigInteger dmp1 = d.mod(p.subtract(BigInteger.ONE)); + BigInteger dmq1 = d.mod(q.subtract(BigInteger.ONE)); + + pubSpec = new RSAPublicKeySpec(n, e); + privSpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, dmp1, dmq1, iqmp); + } else if (type.equals("ssh-dss")) { + keyType = "DSA"; + + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + BigInteger g = tr.readMPINT(); + BigInteger y = tr.readMPINT(); + BigInteger x = tr.readMPINT(); + comment = tr.readString(); + + pubSpec = new DSAPublicKeySpec(y, p, q, g); + privSpec = new DSAPrivateKeySpec(x, p, q, g); + } else if (type.equals("ecdsa-sha2-nistp256")) { + keyType = "EC"; + + String curveName = tr.readString(); + byte[] groupBytes = tr.readByteString(); + BigInteger exponent = tr.readMPINT(); + comment = tr.readString(); + + if (!"nistp256".equals(curveName)) { + log.log(2, "Invalid curve name for ecdsa-sha2-nistp256: " + curveName); + os.write(SSH_AGENT_FAILURE); + return; + } + + ECParameterSpec nistp256 = ECDSASHA2Verify.EllipticCurves.nistp256; + ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, nistp256.getCurve()); + if (group == null) { + // TODO log error + os.write(SSH_AGENT_FAILURE); + return; + } + + pubSpec = new ECPublicKeySpec(group, nistp256); + privSpec = new ECPrivateKeySpec(exponent, nistp256); + } else { + log.log(2, "Unknown key type: " + type); + os.write(SSH_AGENT_FAILURE); + return; + } + + PublicKey pubKey; + PrivateKey privKey; + try { + KeyFactory kf = KeyFactory.getInstance(keyType); + pubKey = kf.generatePublic(pubSpec); + privKey = kf.generatePrivate(privSpec); + } catch (NoSuchAlgorithmException ex) { + // TODO: log error + os.write(SSH_AGENT_FAILURE); + return; + } catch (InvalidKeySpecException ex) { + // TODO: log error + os.write(SSH_AGENT_FAILURE); + return; + } + + KeyPair pair = new KeyPair(pubKey, privKey); + + boolean confirmUse = false; + int lifetime = 0; + + if (checkConstraints) { + while (tr.remain() > 0) { + int constraint = tr.readByte(); + if (constraint == SSH_AGENT_CONSTRAIN_CONFIRM) + confirmUse = true; + else if (constraint == SSH_AGENT_CONSTRAIN_LIFETIME) + lifetime = tr.readUINT32(); + else { + // Unknown constraint. Bail. + os.write(SSH_AGENT_FAILURE); + return; + } + } + } + + if (authAgent.addIdentity(pair, comment, confirmUse, lifetime)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) + { + try + { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) + { + } + } + } + + /** + * @param tr + */ + private void removeIdentity(TypesReader tr) { + try + { + if (failWhenLocked()) + return; + + byte[] publicKey = tr.readByteString(); + if (authAgent.removeIdentity(publicKey)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) + { + try + { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) + { + } + } + } + + /** + * @param tr + */ + private void removeAllIdentities(TypesReader tr) { + try + { + if (failWhenLocked()) + return; + + if (authAgent.removeAllIdentities()) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) + { + try + { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) + { + } + } + } + + private void processSignRequest(TypesReader tr) + { + try + { + if (failWhenLocked()) + return; + + byte[] publicKeyBytes = tr.readByteString(); + byte[] challenge = tr.readByteString(); + + int flags = tr.readUINT32(); + + if (flags != 0) { + // We don't understand any flags; abort! + os.write(SSH_AGENT_FAILURE); + return; + } + + KeyPair pair = authAgent.getKeyPair(publicKeyBytes); + + if (pair == null) { + os.write(SSH_AGENT_FAILURE); + return; + } + + byte[] response; + + PrivateKey privKey = pair.getPrivate(); + if (privKey instanceof RSAPrivateKey) { + byte[] signature = RSASHA1Verify.generateSignature(challenge, + (RSAPrivateKey) privKey); + response = RSASHA1Verify.encodeSSHRSASignature(signature); + } else if (privKey instanceof DSAPrivateKey) { + byte[] signature = DSASHA1Verify.generateSignature(challenge, + (DSAPrivateKey) privKey, new SecureRandom()); + response = DSASHA1Verify.encodeSSHDSASignature(signature); + } else { + os.write(SSH_AGENT_FAILURE); + return; + } + + TypesWriter tw = new TypesWriter(); + tw.writeByte(SSH2_AGENT_SIGN_RESPONSE); + tw.writeString(response, 0, response.length); + + sendPacket(tw.getBytes()); + } + catch (IOException e) + { + try + { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) + { + } + } + } + + /** + * @param tr + */ + private void processLockRequest(TypesReader tr) { + try + { + if (failWhenLocked()) + return; + + String lockPassphrase = tr.readString(); + if (!authAgent.setAgentLock(lockPassphrase)) { + os.write(SSH_AGENT_FAILURE); + return; + } else + os.write(SSH_AGENT_SUCCESS); + } + catch (IOException e) + { + try + { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) + { + } + } + } + + /** + * @param tr + */ + private void processUnlockRequest(TypesReader tr) + { + try + { + String unlockPassphrase = tr.readString(); + + if (authAgent.requestAgentUnlock(unlockPassphrase)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) + { + try + { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) + { + } + } + } + + /** + * @param tw + * @throws IOException + */ + private void sendPacket(byte[] message) throws IOException + { + TypesWriter packet = new TypesWriter(); + packet.writeUINT32(message.length); + packet.writeBytes(message); + os.write(packet.getBytes()); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/Channel.java b/app/src/main/java/com/trilead/ssh2/channel/Channel.java new file mode 100644 index 0000000..8365f12 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/Channel.java @@ -0,0 +1,207 @@ + +package com.trilead.ssh2.channel; + +/** + * Channel. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Channel.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class Channel +{ + /* + * OK. Here is an important part of the JVM Specification: + * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214) + * + * Any association between locks and variables is purely conventional. + * Locking any lock conceptually flushes all variables from a thread's + * working memory, and unlocking any lock forces the writing out to main + * memory of all variables that the thread has assigned. That a lock may be + * associated with a particular object or a class is purely a convention. + * (...) + * + * If a thread uses a particular shared variable only after locking a + * particular lock and before the corresponding unlocking of that same lock, + * then the thread will read the shared value of that variable from main + * memory after the lock operation, if necessary, and will copy back to main + * memory the value most recently assigned to that variable before the + * unlock operation. + * + * This, in conjunction with the mutual exclusion rules for locks, suffices + * to guarantee that values are correctly transmitted from one thread to + * another through shared variables. + * + * ====> Always keep that in mind when modifying the Channel/ChannelManger + * code. + * + */ + + static final int STATE_OPENING = 1; + static final int STATE_OPEN = 2; + static final int STATE_CLOSED = 4; + + static final int CHANNEL_BUFFER_SIZE = 30000; + + /* + * To achieve correctness, the following rules have to be respected when + * accessing this object: + */ + + // These fields can always be read + final ChannelManager cm; + final ChannelOutputStream stdinStream; + final ChannelInputStream stdoutStream; + final ChannelInputStream stderrStream; + + // These two fields will only be written while the Channel is in state + // STATE_OPENING. + // The code makes sure that the two fields are written out when the state is + // changing to STATE_OPEN. + // Therefore, if you know that the Channel is in state STATE_OPEN, then you + // can read these two fields without synchronizing on the Channel. However, make + // sure that you get the latest values (e.g., flush caches by synchronizing on any + // object). However, to be on the safe side, you can lock the channel. + + int localID = -1; + int remoteID = -1; + + /* + * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE + * msg. + * + * This is a little bit complicated, but we have to do it in that way, since + * we cannot keep a lock on the Channel during the send operation (this + * would block sometimes the receiver thread, and, in extreme cases, can + * lead to a deadlock on both sides of the connection (senders are blocked + * since the receive buffers on the other side are full, and receiver + * threads wait for the senders to finish). It all depends on the + * implementation on the other side. But we cannot make any assumptions, we + * have to assume the worst case. Confused? Just believe me. + */ + + /* + * If you send a message on a channel, then you have to aquire the + * "channelSendLock" and check the "closeMessageSent" flag (this variable + * may only be accessed while holding the "channelSendLock" !!! + * + * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation + * above. + */ + + final Object channelSendLock = new Object(); + boolean closeMessageSent = false; + + /* + * Stop memory fragmentation by allocating this often used buffer. + * May only be used while holding the channelSendLock + */ + + final byte[] msgWindowAdjust = new byte[9]; + + // If you access (read or write) any of the following fields, then you have + // to synchronize on the channel. + + int state = STATE_OPENING; + + boolean closeMessageRecv = false; + + /* This is a stupid implementation. At the moment we can only wait + * for one pending request per channel. + */ + int successCounter = 0; + int failedCounter = 0; + + int localWindow = 0; /* locally, we use a small window, < 2^31 */ + long remoteWindow = 0; /* long for readable 2^32 - 1 window support */ + + int localMaxPacketSize = -1; + int remoteMaxPacketSize = -1; + + final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE]; + final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE]; + + int stdoutReadpos = 0; + int stdoutWritepos = 0; + int stderrReadpos = 0; + int stderrWritepos = 0; + + boolean EOF = false; + + Integer exit_status; + + String exit_signal; + + // we keep the x11 cookie so that this channel can be closed when this + // specific x11 forwarding gets stopped + + String hexX11FakeCookie; + + // reasonClosed is special, since we sometimes need to access it + // while holding the channelSendLock. + // We protect it with a private short term lock. + + private final Object reasonClosedLock = new Object(); + private String reasonClosed = null; + + public Channel(ChannelManager cm) + { + this.cm = cm; + + this.localWindow = CHANNEL_BUFFER_SIZE; + this.localMaxPacketSize = 35000 - 1024; // leave enough slack + + this.stdinStream = new ChannelOutputStream(this); + this.stdoutStream = new ChannelInputStream(this, false); + this.stderrStream = new ChannelInputStream(this, true); + } + + /* Methods to allow access from classes outside of this package */ + + public ChannelInputStream getStderrStream() + { + return stderrStream; + } + + public ChannelOutputStream getStdinStream() + { + return stdinStream; + } + + public ChannelInputStream getStdoutStream() + { + return stdoutStream; + } + + public String getExitSignal() + { + synchronized (this) + { + return exit_signal; + } + } + + public Integer getExitStatus() + { + synchronized (this) + { + return exit_status; + } + } + + public String getReasonClosed() + { + synchronized (reasonClosedLock) + { + return reasonClosed; + } + } + + public void setReasonClosed(String reasonClosed) + { + synchronized (reasonClosedLock) + { + if (this.reasonClosed == null) + this.reasonClosed = reasonClosed; + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java b/app/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java new file mode 100644 index 0000000..f88522c --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java @@ -0,0 +1,86 @@ + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; + +/** + * ChannelInputStream. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ChannelInputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public final class ChannelInputStream extends InputStream +{ + Channel c; + + boolean isClosed = false; + boolean isEOF = false; + boolean extendedFlag = false; + + ChannelInputStream(Channel c, boolean isExtended) + { + this.c = c; + this.extendedFlag = isExtended; + } + + public int available() throws IOException + { + if (isEOF) + return 0; + + int avail = c.cm.getAvailable(c, extendedFlag); + + /* We must not return -1 on EOF */ + + return (avail > 0) ? avail : 0; + } + + public void close() throws IOException + { + isClosed = true; + } + + public int read(byte[] b, int off, int len) throws IOException + { + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return 0; + + if (isEOF) + return -1; + + int ret = c.cm.getChannelData(c, extendedFlag, b, off, len); + + if (ret == -1) + { + isEOF = true; + } + + return ret; + } + + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + public int read() throws IOException + { + /* Yes, this stream is pure and unbuffered, a single byte read() is slow */ + + final byte b[] = new byte[1]; + + int ret = read(b, 0, 1); + + if (ret != 1) + return -1; + + return b[0] & 0xff; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/ChannelManager.java b/app/src/main/java/com/trilead/ssh2/channel/ChannelManager.java new file mode 100644 index 0000000..88beffd --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/ChannelManager.java @@ -0,0 +1,1756 @@ + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Vector; + +import com.trilead.ssh2.AuthAgentCallback; +import com.trilead.ssh2.ChannelCondition; +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.PacketChannelAuthAgentReq; +import com.trilead.ssh2.packets.PacketChannelOpenConfirmation; +import com.trilead.ssh2.packets.PacketChannelOpenFailure; +import com.trilead.ssh2.packets.PacketChannelTrileadPing; +import com.trilead.ssh2.packets.PacketGlobalCancelForwardRequest; +import com.trilead.ssh2.packets.PacketGlobalForwardRequest; +import com.trilead.ssh2.packets.PacketGlobalTrileadPing; +import com.trilead.ssh2.packets.PacketOpenDirectTCPIPChannel; +import com.trilead.ssh2.packets.PacketOpenSessionChannel; +import com.trilead.ssh2.packets.PacketSessionExecCommand; +import com.trilead.ssh2.packets.PacketSessionPtyRequest; +import com.trilead.ssh2.packets.PacketSessionPtyResize; +import com.trilead.ssh2.packets.PacketSessionStartShell; +import com.trilead.ssh2.packets.PacketSessionSubsystemRequest; +import com.trilead.ssh2.packets.PacketSessionX11Request; +import com.trilead.ssh2.packets.Packets; +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.transport.MessageHandler; +import com.trilead.ssh2.transport.TransportManager; + +/** + * ChannelManager. Please read the comments in Channel.java. + * <p> + * Besides the crypto part, this is the core of the library. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ChannelManager.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $ + */ +public class ChannelManager implements MessageHandler +{ + private static final Logger log = Logger.getLogger(ChannelManager.class); + + private HashMap<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>(); + + private TransportManager tm; + + private Vector<Channel> channels = new Vector<Channel>(); + private int nextLocalChannel = 100; + private boolean shutdown = false; + private int globalSuccessCounter = 0; + private int globalFailedCounter = 0; + + private HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>(); + + private AuthAgentCallback authAgent; + + private Vector<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>(); + + private boolean listenerThreadsAllowed = true; + + public ChannelManager(TransportManager tm) + { + this.tm = tm; + tm.registerMessageHandler(this, 80, 100); + } + + private Channel getChannel(int id) + { + synchronized (channels) + { + for (int i = 0; i < channels.size(); i++) + { + Channel c = channels.elementAt(i); + if (c.localID == id) + return c; + } + } + return null; + } + + private void removeChannel(int id) + { + synchronized (channels) + { + for (int i = 0; i < channels.size(); i++) + { + Channel c = channels.elementAt(i); + if (c.localID == id) + { + channels.removeElementAt(i); + break; + } + } + } + } + + private int addChannel(Channel c) + { + synchronized (channels) + { + channels.addElement(c); + return nextLocalChannel++; + } + } + + private void waitUntilChannelOpen(Channel c) throws IOException + { + synchronized (c) + { + while (c.state == Channel.STATE_OPENING) + { + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + } + } + + if (c.state != Channel.STATE_OPEN) + { + removeChannel(c.localID); + + String detail = c.getReasonClosed(); + + if (detail == null) + detail = "state: " + c.state; + + throw new IOException("Could not open channel (" + detail + ")"); + } + } + } + + private final boolean waitForGlobalRequestResult() throws IOException + { + synchronized (channels) + { + while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) + { + if (shutdown) + { + throw new IOException("The connection is being shutdown"); + } + + try + { + channels.wait(); + } + catch (InterruptedException ignore) + { + } + } + + if ((globalFailedCounter == 0) && (globalSuccessCounter == 1)) + return true; + + if ((globalFailedCounter == 1) && (globalSuccessCounter == 0)) + return false; + + throw new IOException("Illegal state. The server sent " + globalSuccessCounter + + " SSH_MSG_REQUEST_SUCCESS and " + globalFailedCounter + " SSH_MSG_REQUEST_FAILURE messages."); + } + } + + private final boolean waitForChannelRequestResult(Channel c) throws IOException + { + synchronized (c) + { + while ((c.successCounter == 0) && (c.failedCounter == 0)) + { + if (c.state != Channel.STATE_OPEN) + { + String detail = c.getReasonClosed(); + + if (detail == null) + detail = "state: " + c.state; + + throw new IOException("This SSH2 channel is not open (" + detail + ")"); + } + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + } + } + + if ((c.failedCounter == 0) && (c.successCounter == 1)) + return true; + + if ((c.failedCounter == 1) && (c.successCounter == 0)) + return false; + + throw new IOException("Illegal state. The server sent " + c.successCounter + + " SSH_MSG_CHANNEL_SUCCESS and " + c.failedCounter + " SSH_MSG_CHANNEL_FAILURE messages."); + } + } + + public void registerX11Cookie(String hexFakeCookie, X11ServerData data) + { + synchronized (x11_magic_cookies) + { + x11_magic_cookies.put(hexFakeCookie, data); + } + } + + public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) + { + if (hexFakeCookie == null) + throw new IllegalStateException("hexFakeCookie may not be null"); + + synchronized (x11_magic_cookies) + { + x11_magic_cookies.remove(hexFakeCookie); + } + + if (killChannels == false) + return; + + if (log.isEnabled()) + log.log(50, "Closing all X11 channels for the given fake cookie"); + + Vector<Channel> channel_copy; + + synchronized (channels) + { + channel_copy = (Vector<Channel>) channels.clone(); + } + + for (int i = 0; i < channel_copy.size(); i++) + { + Channel c = channel_copy.elementAt(i); + + synchronized (c) + { + if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) + continue; + } + + try + { + closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); + } + catch (IOException e) + { + } + } + } + + public X11ServerData checkX11Cookie(String hexFakeCookie) + { + synchronized (x11_magic_cookies) + { + if (hexFakeCookie != null) + return x11_magic_cookies.get(hexFakeCookie); + } + return null; + } + + public void closeAllChannels() + { + if (log.isEnabled()) + log.log(50, "Closing all channels"); + + Vector<Channel> channel_copy; + + synchronized (channels) + { + channel_copy = (Vector<Channel>) channels.clone(); + } + + for (int i = 0; i < channel_copy.size(); i++) + { + Channel c = channel_copy.elementAt(i); + try + { + closeChannel(c, "Closing all channels", true); + } + catch (IOException e) + { + } + } + } + + public void closeChannel(Channel c, String reason, boolean force) throws IOException + { + byte msg[] = new byte[5]; + + synchronized (c) + { + if (force) + { + c.state = Channel.STATE_CLOSED; + c.EOF = true; + } + + c.setReasonClosed(reason); + + msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + + c.notifyAll(); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(msg); + c.closeMessageSent = true; + } + + if (log.isEnabled()) + log.log(50, "Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); + } + + public void sendEOF(Channel c) throws IOException + { + byte[] msg = new byte[5]; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + return; + + msg[0] = Packets.SSH_MSG_CHANNEL_EOF; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(msg); + } + + if (log.isEnabled()) + log.log(50, "Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); + } + + public void sendOpenConfirmation(Channel c) throws IOException + { + PacketChannelOpenConfirmation pcoc = null; + + synchronized (c) + { + if (c.state != Channel.STATE_OPENING) + return; + + c.state = Channel.STATE_OPEN; + + pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(pcoc.getPayload()); + } + } + + public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException + { + while (len > 0) + { + int thislen = 0; + byte[] msg; + + synchronized (c) + { + while (true) + { + if (c.state == Channel.STATE_CLOSED) + throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); + + if (c.state != Channel.STATE_OPEN) + throw new IOException("SSH channel in strange state. (" + c.state + ")"); + + if (c.remoteWindow != 0) + break; + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + } + } + + /* len > 0, no sign extension can happen when comparing */ + + thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; + + int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); + + /* The worst case scenario =) a true bottleneck */ + + if (estimatedMaxDataLen <= 0) + { + estimatedMaxDataLen = 1; + } + + if (thislen > estimatedMaxDataLen) + thislen = estimatedMaxDataLen; + + c.remoteWindow -= thislen; + + msg = new byte[1 + 8 + thislen]; + + msg[0] = Packets.SSH_MSG_CHANNEL_DATA; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + msg[5] = (byte) (thislen >> 24); + msg[6] = (byte) (thislen >> 16); + msg[7] = (byte) (thislen >> 8); + msg[8] = (byte) (thislen); + + System.arraycopy(buffer, pos, msg, 9, thislen); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); + + tm.sendMessage(msg); + } + + pos += thislen; + len -= thislen; + } + } + + public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) + throws IOException + { + RemoteForwardingData rfd = new RemoteForwardingData(); + + rfd.bindAddress = bindAddress; + rfd.bindPort = bindPort; + rfd.targetAddress = targetAddress; + rfd.targetPort = targetPort; + + synchronized (remoteForwardings) + { + Integer key = Integer.valueOf(bindPort); + + if (remoteForwardings.get(key) != null) + { + throw new IOException("There is already a forwarding for remote port " + bindPort); + } + + remoteForwardings.put(key, rfd); + } + + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); + tm.sendMessage(pgf.getPayload()); + + if (log.isEnabled()) + log.log(50, "Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); + + try + { + if (waitForGlobalRequestResult() == false) + throw new IOException("The server denied the request (did you enable port forwarding?)"); + } + catch (IOException e) + { + synchronized (remoteForwardings) + { + remoteForwardings.remove(rfd); + } + throw e; + } + + return bindPort; + } + + public void requestCancelGlobalForward(int bindPort) throws IOException + { + RemoteForwardingData rfd = null; + + synchronized (remoteForwardings) + { + rfd = remoteForwardings.get(Integer.valueOf(bindPort)); + + if (rfd == null) + throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); + } + + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, + rfd.bindPort); + tm.sendMessage(pgcf.getPayload()); + + if (log.isEnabled()) + log.log(50, "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); + + try + { + if (waitForGlobalRequestResult() == false) + throw new IOException("The server denied the request."); + } + finally + { + synchronized (remoteForwardings) + { + /* Only now we are sure that no more forwarded connections will arrive */ + remoteForwardings.remove(rfd); + } + } + + } + + /** + * @param agent + * @throws IOException + */ + public boolean requestChannelAgentForwarding(Channel c, AuthAgentCallback authAgent) throws IOException { + synchronized (this) + { + if (this.authAgent != null) + throw new IllegalStateException("Auth agent already exists"); + + this.authAgent = authAgent; + } + + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + if (log.isEnabled()) + log.log(50, "Requesting agent forwarding"); + + PacketChannelAuthAgentReq aar = new PacketChannelAuthAgentReq(c.remoteID); + tm.sendMessage(aar.getPayload()); + + if (waitForChannelRequestResult(c) == false) { + authAgent = null; + return false; + } + + return true; + } + + public void registerThread(IChannelWorkerThread thr) throws IOException + { + synchronized (listenerThreads) + { + if (listenerThreadsAllowed == false) + throw new IOException("Too late, this connection is closed."); + listenerThreads.addElement(thr); + } + } + + public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) throws IOException + { + Channel c = new Channel(this); + + synchronized (c) + { + c.localID = addChannel(c); + // end of synchronized block forces writing out to main memory + } + + PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, + c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); + + tm.sendMessage(dtc.getPayload()); + + waitUntilChannelOpen(c); + + return c; + } + + public Channel openSessionChannel() throws IOException + { + Channel c = new Channel(this); + + synchronized (c) + { + c.localID = addChannel(c); + // end of synchronized block forces the writing out to main memory + } + + if (log.isEnabled()) + log.log(50, "Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); + + PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); + tm.sendMessage(smo.getPayload()); + + waitUntilChannelOpen(c); + + return c; + } + + public void requestGlobalTrileadPing() throws IOException + { + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalTrileadPing pgtp = new PacketGlobalTrileadPing(); + + tm.sendMessage(pgtp.getPayload()); + + if (log.isEnabled()) + log.log(50, "Sending SSH_MSG_GLOBAL_REQUEST 'trilead-ping'."); + + try + { + if (waitForGlobalRequestResult() == true) + throw new IOException("Your server is alive - but buggy. " + + "It replied with SSH_MSG_REQUEST_SUCCESS when it actually should not."); + + } + catch (IOException e) + { + throw (IOException) new IOException("The ping request failed.").initCause(e); + } + } + + public void requestChannelTrileadPing(Channel c) throws IOException + { + PacketChannelTrileadPing pctp; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")"); + + pctp = new PacketChannelTrileadPing(c.remoteID); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(pctp.getPayload()); + } + + try + { + if (waitForChannelRequestResult(c) == true) + throw new IOException("Your server is alive - but buggy. " + + "It replied with SSH_MSG_SESSION_SUCCESS when it actually should not."); + + } + catch (IOException e) + { + throw (IOException) new IOException("The ping request failed.").initCause(e); + } + } + + public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException + { + PacketSessionPtyRequest spr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); + + spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels, terminal_modes); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(spr.getPayload()); + } + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("PTY request failed").initCause(e); + } + } + + + public void resizePTY(Channel c, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels) throws IOException { + PacketSessionPtyResize spr; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request PTY on this channel (" + + c.getReasonClosed() + ")"); + + spr = new PacketSessionPtyResize(c.remoteID, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) + throw new IOException("Cannot request PTY on this channel (" + + c.getReasonClosed() + ")"); + tm.sendMessage(spr.getPayload()); + } + } + + + public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, + String x11AuthenticationCookie, int x11ScreenNumber) throws IOException + { + PacketSessionX11Request psr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); + + psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, + x11AuthenticationCookie, x11ScreenNumber); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(psr.getPayload()); + } + + if (log.isEnabled()) + log.log(50, "Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("The X11 request failed.").initCause(e); + } + } + + public void requestSubSystem(Channel c, String subSystemName) throws IOException + { + PacketSessionSubsystemRequest ssr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); + + ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(ssr.getPayload()); + } + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("The subsystem request failed.").initCause(e); + } + } + + public void requestExecCommand(Channel c, String cmd) throws IOException + { + PacketSessionExecCommand sm; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); + + sm = new PacketSessionExecCommand(c.remoteID, true, cmd); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(sm.getPayload()); + } + + if (log.isEnabled()) + log.log(50, "Executing command (channel " + c.localID + ", '" + cmd + "')"); + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("The execute request failed.").initCause(e); + } + } + + public void requestShell(Channel c) throws IOException + { + PacketSessionStartShell sm; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); + + sm = new PacketSessionStartShell(c.remoteID, true); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(sm.getPayload()); + } + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("The shell request failed.").initCause(e); + } + } + + public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException + { + if (msglen <= 13) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); + + if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); + + if (len != (msglen - 13)) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) + + ", got " + len + ")"); + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) + { + if (c.state == Channel.STATE_CLOSED) + return; // ignore + + if (c.state != Channel.STATE_OPEN) + throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" + + c.state + ")"); + + if (c.localWindow < len) + throw new IOException("Remote sent too much data, does not fit into window."); + + c.localWindow -= len; + + System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); + c.stderrWritepos += len; + + c.notifyAll(); + } + } + + /** + * Wait until for a condition. + * + * @param c + * Channel + * @param timeout + * in ms, 0 means no timeout. + * @param condition_mask + * minimum event mask + * @return all current events + * + */ + public int waitForCondition(Channel c, long timeout, int condition_mask) + { + long end_time = 0; + boolean end_time_set = false; + + synchronized (c) + { + while (true) + { + int current_cond = 0; + + int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + int stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if (stdoutAvail > 0) + current_cond = current_cond | ChannelCondition.STDOUT_DATA; + + if (stderrAvail > 0) + current_cond = current_cond | ChannelCondition.STDERR_DATA; + + if (c.EOF) + current_cond = current_cond | ChannelCondition.EOF; + + if (c.getExitStatus() != null) + current_cond = current_cond | ChannelCondition.EXIT_STATUS; + + if (c.getExitSignal() != null) + current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; + + if (c.state == Channel.STATE_CLOSED) + return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; + + if ((current_cond & condition_mask) != 0) + return current_cond; + + if (timeout > 0) + { + if (!end_time_set) + { + end_time = System.currentTimeMillis() + timeout; + end_time_set = true; + } + else + { + timeout = end_time - System.currentTimeMillis(); + + if (timeout <= 0) + return current_cond | ChannelCondition.TIMEOUT; + } + } + + try + { + if (timeout > 0) + c.wait(timeout); + else + c.wait(); + } + catch (InterruptedException e) + { + } + } + } + } + + public int getAvailable(Channel c, boolean extended) throws IOException + { + synchronized (c) + { + int avail; + + if (extended) + avail = c.stderrWritepos - c.stderrReadpos; + else + avail = c.stdoutWritepos - c.stdoutReadpos; + + return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); + } + } + + public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException + { + int copylen = 0; + int increment = 0; + int remoteID = 0; + int localID = 0; + + synchronized (c) + { + int stdoutAvail = 0; + int stderrAvail = 0; + + while (true) + { + /* + * Data available? We have to return remaining data even if the + * channel is already closed. + */ + + stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if ((!extended) && (stdoutAvail != 0)) + break; + + if ((extended) && (stderrAvail != 0)) + break; + + /* Do not wait if more data will never arrive (EOF or CLOSED) */ + + if ((c.EOF) || (c.state != Channel.STATE_OPEN)) + return -1; + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + } + } + + /* OK, there is some data. Return it. */ + + if (!extended) + { + copylen = (stdoutAvail > len) ? len : stdoutAvail; + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); + c.stdoutReadpos += copylen; + + if (c.stdoutReadpos != c.stdoutWritepos) + + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos + - c.stdoutReadpos); + + c.stdoutWritepos -= c.stdoutReadpos; + c.stdoutReadpos = 0; + } + else + { + copylen = (stderrAvail > len) ? len : stderrAvail; + System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); + c.stderrReadpos += copylen; + + if (c.stderrReadpos != c.stderrWritepos) + + System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos + - c.stderrReadpos); + + c.stderrWritepos -= c.stderrReadpos; + c.stderrReadpos = 0; + } + + if (c.state != Channel.STATE_OPEN) + return copylen; + + if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) + { + int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE + - c.stderrWritepos); + + increment = minFreeSpace - c.localWindow; + c.localWindow = minFreeSpace; + } + + remoteID = c.remoteID; /* read while holding the lock */ + localID = c.localID; /* read while holding the lock */ + } + + /* + * If a consumer reads stdout and stdin in parallel, we may end up with + * sending two msgWindowAdjust messages. Luckily, it + * does not matter in which order they arrive at the server. + */ + + if (increment > 0) + { + if (log.isEnabled()) + log.log(80, "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); + + synchronized (c.channelSendLock) + { + byte[] msg = c.msgWindowAdjust; + + msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; + msg[1] = (byte) (remoteID >> 24); + msg[2] = (byte) (remoteID >> 16); + msg[3] = (byte) (remoteID >> 8); + msg[4] = (byte) (remoteID); + msg[5] = (byte) (increment >> 24); + msg[6] = (byte) (increment >> 16); + msg[7] = (byte) (increment >> 8); + msg[8] = (byte) (increment); + + if (c.closeMessageSent == false) + tm.sendMessage(msg); + } + } + + return copylen; + } + + public void msgChannelData(byte[] msg, int msglen) throws IOException + { + if (msglen <= 9) + throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); + + if (len != (msglen - 9)) + throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " + + len + ")"); + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) + { + if (c.state == Channel.STATE_CLOSED) + return; // ignore + + if (c.state != Channel.STATE_OPEN) + throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); + + if (c.localWindow < len) + throw new IOException("Remote sent too much data, does not fit into window."); + + c.localWindow -= len; + + System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); + c.stdoutWritepos += len; + + c.notifyAll(); + } + } + + public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException + { + if (msglen != 9) + throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); + + synchronized (c) + { + final long huge = 0xFFFFffffL; /* 2^32 - 1 */ + + c.remoteWindow += (windowChange & huge); /* avoid sign extension */ + + /* TODO - is this a good heuristic? */ + + if ((c.remoteWindow > huge)) + c.remoteWindow = huge; + + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); + } + + public void msgChannelOpen(byte[] msg, int msglen) throws IOException + { + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + String channelType = tr.readString(); + int remoteID = tr.readUINT32(); /* sender channel */ + int remoteWindow = tr.readUINT32(); /* initial window size */ + int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ + + if ("x11".equals(channelType)) + { + synchronized (x11_magic_cookies) + { + /* If we did not request X11 forwarding, then simply ignore this bogus request. */ + + if (x11_magic_cookies.size() == 0) + { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); + + tm.sendAsynchronousMessage(pcof.getPayload()); + + if (log.isEnabled()) + log.log(20, "Unexpected X11 request, denying it!"); + + return; + } + } + + String remoteOriginatorAddress = tr.readString(); + int remoteOriginatorPort = tr.readUINT32(); + + Channel c = new Channel(this); + + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + /* + * The open confirmation message will be sent from another thread + */ + + RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); + rxat.setDaemon(true); + rxat.start(); + + return; + } + + if ("forwarded-tcpip".equals(channelType)) + { + String remoteConnectedAddress = tr.readString(); /* address that was connected */ + int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ + String remoteOriginatorAddress = tr.readString(); /* originator IP address */ + int remoteOriginatorPort = tr.readUINT32(); /* originator port */ + + RemoteForwardingData rfd = null; + + synchronized (remoteForwardings) + { + rfd = remoteForwardings.get(Integer.valueOf(remoteConnectedPort)); + } + + if (rfd == null) + { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "No thanks, unknown port in forwarded-tcpip request", ""); + + /* Always try to be polite. */ + + tm.sendAsynchronousMessage(pcof.getPayload()); + + if (log.isEnabled()) + log.log(20, "Unexpected forwarded-tcpip request, denying it!"); + + return; + } + + Channel c = new Channel(this); + + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + /* + * The open confirmation message will be sent from another thread. + */ + + RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, + remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); + + rat.setDaemon(true); + rat.start(); + + return; + } + + if ("auth-agent@openssh.com".equals(channelType)) { + Channel c = new Channel(this); + + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + AuthAgentForwardThread aat = new AuthAgentForwardThread(c, authAgent); + + aat.setDaemon(true); + aat.start(); + + return; + } + + /* Tell the server that we have no idea what it is talking about */ + + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, + "Unknown channel type", ""); + + tm.sendAsynchronousMessage(pcof.getPayload()); + + if (log.isEnabled()) + log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")"); + } + + public void msgChannelRequest(byte[] msg, int msglen) throws IOException + { + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + int id = tr.readUINT32(); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); + + String type = tr.readString("US-ASCII"); + boolean wantReply = tr.readBoolean(); + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); + + if (type.equals("exit-status")) + { + if (wantReply != false) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); + + int exit_status = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + synchronized (c) + { + c.exit_status = Integer.valueOf(exit_status); + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); + + return; + } + + if (type.equals("exit-signal")) + { + if (wantReply != false) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); + + String signame = tr.readString("US-ASCII"); + tr.readBoolean(); + tr.readString(); + tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + synchronized (c) + { + c.exit_signal = signame; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); + + return; + } + + /* We simply ignore unknown channel requests, however, if the server wants a reply, + * then we signal that we have no idea what it is about. + */ + + if (wantReply) + { + byte[] reply = new byte[5]; + + reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE; + reply[1] = (byte) (c.remoteID >> 24); + reply[2] = (byte) (c.remoteID >> 16); + reply[3] = (byte) (c.remoteID >> 8); + reply[4] = (byte) (c.remoteID); + + tm.sendAsynchronousMessage(reply); + } + + if (log.isEnabled()) + log.log(50, "Channel request '" + type + "' is not known, ignoring it"); + } + + public void msgChannelEOF(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); + + synchronized (c) + { + c.EOF = true; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); + } + + public void msgChannelClose(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); + + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("Close requested by remote"); + c.closeMessageRecv = true; + + removeChannel(c.localID); + + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); + } + + public void msgChannelSuccess(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); + + synchronized (c) + { + c.successCounter++; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); + } + + public void msgChannelFailure(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); + + synchronized (c) + { + c.failedCounter++; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); + } + + public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException + { + PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); + + Channel c = getChannel(sm.recipientChannelID); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " + + sm.recipientChannelID); + + synchronized (c) + { + if (c.state != Channel.STATE_OPENING) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + + sm.recipientChannelID); + + c.remoteID = sm.senderChannelID; + c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = sm.maxPacketSize; + c.state = Channel.STATE_OPEN; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " + + sm.senderChannelID + ")"); + } + + public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException + { + if (msglen < 5) + throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); + + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + int id = tr.readUINT32(); /* sender channel */ + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); + + int reasonCode = tr.readUINT32(); + String description = tr.readString("UTF-8"); + + String reasonCodeSymbolicName = null; + + switch (reasonCode) + { + case 1: + reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; + break; + case 2: + reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; + break; + case 3: + reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; + break; + case 4: + reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; + break; + default: + reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; + } + + StringBuffer descriptionBuffer = new StringBuffer(); + descriptionBuffer.append(description); + + for (int i = 0; i < descriptionBuffer.length(); i++) + { + char cc = descriptionBuffer.charAt(i); + + if ((cc >= 32) && (cc <= 126)) + continue; + descriptionBuffer.setCharAt(i, '\uFFFD'); + } + + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '" + + descriptionBuffer.toString() + "')"); + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); + } + + public void msgGlobalRequest(byte[] msg, int msglen) throws IOException + { + /* Currently we do not support any kind of global request */ + + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + String requestName = tr.readString(); + boolean wantReply = tr.readBoolean(); + + if (wantReply) + { + byte[] reply_failure = new byte[1]; + reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; + + tm.sendAsynchronousMessage(reply_failure); + } + + /* We do not clean up the requestName String - that is OK for debug */ + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); + } + + public void msgGlobalSuccess() throws IOException + { + synchronized (channels) + { + globalSuccessCounter++; + channels.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_REQUEST_SUCCESS"); + } + + public void msgGlobalFailure() throws IOException + { + synchronized (channels) + { + globalFailedCounter++; + channels.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_REQUEST_FAILURE"); + } + + public void handleMessage(byte[] msg, int msglen) throws IOException + { + if (msg == null) + { + if (log.isEnabled()) + log.log(50, "HandleMessage: got shutdown"); + + synchronized (listenerThreads) + { + for (int i = 0; i < listenerThreads.size(); i++) + { + IChannelWorkerThread lat = listenerThreads.elementAt(i); + lat.stopWorking(); + } + listenerThreadsAllowed = false; + } + + synchronized (channels) + { + shutdown = true; + + for (int i = 0; i < channels.size(); i++) + { + Channel c = channels.elementAt(i); + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("The connection is being shutdown"); + c.closeMessageRecv = true; /* + * You never know, perhaps + * we are waiting for a + * pending close message + * from the server... + */ + c.notifyAll(); + } + } + /* Works with J2ME */ + channels.setSize(0); + channels.trimToSize(); + channels.notifyAll(); /* Notify global response waiters */ + return; + } + } + + switch (msg[0]) + { + case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + msgChannelOpenConfirmation(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: + msgChannelWindowAdjust(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_DATA: + msgChannelData(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: + msgChannelExtendedData(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_REQUEST: + msgChannelRequest(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_EOF: + msgChannelEOF(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_OPEN: + msgChannelOpen(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_CLOSE: + msgChannelClose(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_SUCCESS: + msgChannelSuccess(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_FAILURE: + msgChannelFailure(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: + msgChannelOpenFailure(msg, msglen); + break; + case Packets.SSH_MSG_GLOBAL_REQUEST: + msgGlobalRequest(msg, msglen); + break; + case Packets.SSH_MSG_REQUEST_SUCCESS: + msgGlobalSuccess(); + break; + case Packets.SSH_MSG_REQUEST_FAILURE: + msgGlobalFailure(); + break; + default: + throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java b/app/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java new file mode 100644 index 0000000..c1d56e8 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java @@ -0,0 +1,71 @@ +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * ChannelOutputStream. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ChannelOutputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public final class ChannelOutputStream extends OutputStream +{ + Channel c; + + private byte[] writeBuffer; + + boolean isClosed = false; + + ChannelOutputStream(Channel c) + { + this.c = c; + writeBuffer = new byte[1]; + } + + public void write(int b) throws IOException + { + writeBuffer[0] = (byte) b; + + write(writeBuffer, 0, 1); + } + + public void close() throws IOException + { + if (isClosed == false) + { + isClosed = true; + c.cm.sendEOF(c); + } + } + + public void flush() throws IOException + { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + /* This is a no-op, since this stream is unbuffered */ + } + + public void write(byte[] b, int off, int len) throws IOException + { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return; + + c.cm.sendData(c, b, off, len); + } + + public void write(byte[] b) throws IOException + { + write(b, 0, b.length); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java b/app/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java new file mode 100644 index 0000000..ef3a3d0 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java @@ -0,0 +1,284 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NoRouteToHostException; +import java.net.ServerSocket; +import java.net.Socket; + +import net.sourceforge.jsocks.Proxy; +import net.sourceforge.jsocks.ProxyMessage; +import net.sourceforge.jsocks.Socks4Message; +import net.sourceforge.jsocks.Socks5Message; +import net.sourceforge.jsocks.SocksException; +import net.sourceforge.jsocks.server.ServerAuthenticator; +import net.sourceforge.jsocks.server.ServerAuthenticatorNone; + +/** + * DynamicAcceptThread. + * + * @author Kenny Root + * @version $Id$ + */ +public class DynamicAcceptThread extends Thread implements IChannelWorkerThread { + private ChannelManager cm; + private ServerSocket ss; + + class DynamicAcceptRunnable implements Runnable { + private static final int idleTimeout = 180000; //3 minutes + + private ServerAuthenticator auth; + private Socket sock; + private InputStream in; + private OutputStream out; + private ProxyMessage msg; + + public DynamicAcceptRunnable(ServerAuthenticator auth, Socket sock) { + this.auth = auth; + this.sock = sock; + + setName("DynamicAcceptRunnable"); + } + + public void run() { + try { + startSession(); + } catch (IOException ioe) { + int error_code = Proxy.SOCKS_FAILURE; + + if (ioe instanceof SocksException) + error_code = ((SocksException) ioe).errCode; + else if (ioe instanceof NoRouteToHostException) + error_code = Proxy.SOCKS_HOST_UNREACHABLE; + else if (ioe instanceof ConnectException) + error_code = Proxy.SOCKS_CONNECTION_REFUSED; + else if (ioe instanceof InterruptedIOException) + error_code = Proxy.SOCKS_TTL_EXPIRE; + + if (error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED + || error_code < 0) { + error_code = Proxy.SOCKS_FAILURE; + } + + sendErrorMessage(error_code); + } finally { + if (auth != null) + auth.endSession(); + } + } + + private ProxyMessage readMsg(InputStream in) throws IOException { + PushbackInputStream push_in; + if (in instanceof PushbackInputStream) + push_in = (PushbackInputStream) in; + else + push_in = new PushbackInputStream(in); + + int version = push_in.read(); + push_in.unread(version); + + ProxyMessage msg; + + if (version == 5) { + msg = new Socks5Message(push_in, false); + } else if (version == 4) { + msg = new Socks4Message(push_in, false); + } else { + throw new SocksException(Proxy.SOCKS_FAILURE); + } + return msg; + } + + private void sendErrorMessage(int error_code) { + ProxyMessage err_msg; + if (msg instanceof Socks4Message) + err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED); + else + err_msg = new Socks5Message(error_code); + try { + err_msg.write(out); + } catch (IOException ioe) { + } + } + + private void handleRequest(ProxyMessage msg) throws IOException { + if (!auth.checkRequest(msg)) + throw new SocksException(Proxy.SOCKS_FAILURE); + + switch (msg.command) { + case Proxy.SOCKS_CMD_CONNECT: + onConnect(msg); + break; + default: + throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED); + } + } + + private void startSession() throws IOException { + sock.setSoTimeout(idleTimeout); + + try { + auth = auth.startSession(sock); + } catch (IOException ioe) { + System.out.println("Could not start SOCKS session"); + ioe.printStackTrace(); + auth = null; + return; + } + + if (auth == null) { // Authentication failed + System.out.println("SOCKS auth failed"); + return; + } + + in = auth.getInputStream(); + out = auth.getOutputStream(); + + msg = readMsg(in); + handleRequest(msg); + } + + private void onConnect(ProxyMessage msg) throws IOException { + ProxyMessage response = null; + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + if (msg instanceof Socks5Message) { + response = new Socks5Message(Proxy.SOCKS_SUCCESS, (InetAddress)null, 0); + } else { + response = new Socks4Message(Socks4Message.REPLY_OK, (InetAddress)null, 0); + } + response.write(out); + + String destHost = msg.host; + if (msg.ip != null) + destHost = msg.ip.getHostAddress(); + + try { + /* + * This may fail, e.g., if the remote port is closed (in + * optimistic terms: not open yet) + */ + + cn = cm.openDirectTCPIPChannel(destHost, msg.port, + "127.0.0.1", 0); + + } catch (IOException e) { + /* + * Simply close the local socket and wait for the next incoming + * connection + */ + + try { + sock.close(); + } catch (IOException ignore) { + } + + return; + } + + try { + r2l = new StreamForwarder(cn, null, sock, cn.stdoutStream, out, "RemoteToLocal"); + l2r = new StreamForwarder(cn, r2l, sock, in, cn.stdinStream, "LocalToRemote"); + } catch (IOException e) { + try { + /* + * This message is only visible during debugging, since we + * discard the channel immediatelly + */ + cn.cm.closeChannel(cn, + "Weird error during creation of StreamForwarder (" + + e.getMessage() + ")", true); + } catch (IOException ignore) { + } + + return; + } + + r2l.setDaemon(true); + l2r.setDaemon(true); + r2l.start(); + l2r.start(); + } + } + + public DynamicAcceptThread(ChannelManager cm, int local_port) + throws IOException { + this.cm = cm; + + setName("DynamicAcceptThread"); + + ss = new ServerSocket(local_port); + } + + public DynamicAcceptThread(ChannelManager cm, InetSocketAddress localAddress) + throws IOException { + this.cm = cm; + + ss = new ServerSocket(); + ss.bind(localAddress); + } + + @Override + public void run() { + try { + cm.registerThread(this); + } catch (IOException e) { + stopWorking(); + return; + } + + while (true) { + Socket sock = null; + + try { + sock = ss.accept(); + } catch (IOException e) { + stopWorking(); + return; + } + + DynamicAcceptRunnable dar = new DynamicAcceptRunnable(new ServerAuthenticatorNone(), sock); + Thread t = new Thread(dar); + t.setDaemon(true); + t.start(); + } + } + + /* + * (non-Javadoc) + * + * @see com.trilead.ssh2.channel.IChannelWorkerThread#stopWorking() + */ + public void stopWorking() { + try { + /* This will lead to an IOException in the ss.accept() call */ + ss.close(); + } catch (IOException e) { + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java b/app/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java new file mode 100644 index 0000000..bce9b1b --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java @@ -0,0 +1,13 @@ + +package com.trilead.ssh2.channel; + +/** + * IChannelWorkerThread. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: IChannelWorkerThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +interface IChannelWorkerThread +{ + public void stopWorking(); +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java b/app/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java new file mode 100644 index 0000000..0d1bb35 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java @@ -0,0 +1,135 @@ + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * LocalAcceptThread. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: LocalAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class LocalAcceptThread extends Thread implements IChannelWorkerThread +{ + ChannelManager cm; + String host_to_connect; + int port_to_connect; + + final ServerSocket ss; + + public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException + { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + ss = new ServerSocket(local_port); + } + + public LocalAcceptThread(ChannelManager cm, InetSocketAddress localAddress, String host_to_connect, + int port_to_connect) throws IOException + { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + ss = new ServerSocket(); + ss.bind(localAddress); + } + + public void run() + { + try + { + cm.registerThread(this); + } + catch (IOException e) + { + stopWorking(); + return; + } + + while (true) + { + Socket s = null; + + try + { + s = ss.accept(); + } + catch (IOException e) + { + stopWorking(); + return; + } + + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + try + { + /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */ + + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s + .getPort()); + + } + catch (IOException e) + { + /* Simply close the local socket and wait for the next incoming connection */ + + try + { + s.close(); + } + catch (IOException ignore) + { + } + + continue; + } + + try + { + r2l = new StreamForwarder(cn, null, s, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal"); + l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), cn.stdinStream, "LocalToRemote"); + } + catch (IOException e) + { + try + { + /* This message is only visible during debugging, since we discard the channel immediatelly */ + cn.cm.closeChannel(cn, "Weird error during creation of StreamForwarder (" + e.getMessage() + ")", + true); + } + catch (IOException ignore) + { + } + + continue; + } + + r2l.setDaemon(true); + l2r.setDaemon(true); + r2l.start(); + l2r.start(); + } + } + + public void stopWorking() + { + try + { + /* This will lead to an IOException in the ss.accept() call */ + ss.close(); + } + catch (IOException e) + { + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java b/app/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java new file mode 100644 index 0000000..29b02b8 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java @@ -0,0 +1,103 @@ + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.net.Socket; + +import com.trilead.ssh2.log.Logger; + + +/** + * RemoteAcceptThread. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RemoteAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class RemoteAcceptThread extends Thread +{ + private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); + + Channel c; + + String remoteConnectedAddress; + int remoteConnectedPort; + String remoteOriginatorAddress; + int remoteOriginatorPort; + String targetAddress; + int targetPort; + + Socket s; + + public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort, + String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort) + { + this.c = c; + this.remoteConnectedAddress = remoteConnectedAddress; + this.remoteConnectedPort = remoteConnectedPort; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + this.targetAddress = targetAddress; + this.targetPort = targetPort; + + if (log.isEnabled()) + log.log(20, "RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: " + + remoteOriginatorAddress + "/" + remoteOriginatorPort); + } + + public void run() + { + try + { + c.cm.sendOpenConfirmation(c); + + s = new Socket(targetAddress, targetPort); + + StreamForwarder r2l = new StreamForwarder(c, null, s, c.getStdoutStream(), s.getOutputStream(), + "RemoteToLocal"); + StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(), + "LocalToRemote"); + + /* No need to start two threads, one can be executed in the current thread */ + + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) + { + try + { + r2l.join(); + } + catch (InterruptedException e) + { + } + } + + /* If the channel is already closed, then this is a no-op */ + + c.cm.closeChannel(c, "EOF on both streams reached.", true); + s.close(); + } + catch (IOException e) + { + log.log(50, "IOException in proxy code: " + e.getMessage()); + + try + { + c.cm.closeChannel(c, "IOException in proxy code (" + e.getMessage() + ")", true); + } + catch (IOException e1) + { + } + try + { + if (s != null) + s.close(); + } + catch (IOException e1) + { + } + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java b/app/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java new file mode 100644 index 0000000..d05378e --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java @@ -0,0 +1,17 @@ + +package com.trilead.ssh2.channel; + +/** + * RemoteForwardingData. Data about a requested remote forwarding. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RemoteForwardingData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class RemoteForwardingData +{ + public String bindAddress; + public int bindPort; + + String targetAddress; + int targetPort; +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java b/app/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java new file mode 100644 index 0000000..9f99410 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java @@ -0,0 +1,240 @@ + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import com.trilead.ssh2.log.Logger; + + +/** + * RemoteX11AcceptThread. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RemoteX11AcceptThread.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class RemoteX11AcceptThread extends Thread +{ + private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class); + + Channel c; + + String remoteOriginatorAddress; + int remoteOriginatorPort; + + Socket s; + + public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort) + { + this.c = c; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + } + + public void run() + { + try + { + /* Send Open Confirmation */ + + c.cm.sendOpenConfirmation(c); + + /* Read startup packet from client */ + + OutputStream remote_os = c.getStdinStream(); + InputStream remote_is = c.getStdoutStream(); + + /* The following code is based on the protocol description given in: + * Scheifler/Gettys, + * X Windows System: Core and Extension Protocols: + * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X + */ + + /* + * Client startup: + * + * 1 0X42 MSB first/0x6c lSB first - byteorder + * 1 - unused + * 2 card16 - protocol-major-version + * 2 card16 - protocol-minor-version + * 2 n - lenght of authorization-protocol-name + * 2 d - lenght of authorization-protocol-data + * 2 - unused + * string8 - authorization-protocol-name + * p - unused, p=pad(n) + * string8 - authorization-protocol-data + * q - unused, q=pad(d) + * + * pad(X) = (4 - (X mod 4)) mod 4 + * + * Server response: + * + * 1 (0 failed, 2 authenticate, 1 success) + * ... + * + */ + + /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */ + + byte[] header = new byte[6]; + + if (remote_is.read(header) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); + + if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first + throw new IOException("Unknown endian format in X11 message!"); + + /* Yes, I came up with this myself - shall I file an application for a patent? =) */ + + int idxMSB = (header[0] == 0x42) ? 0 : 1; + + /* Read authorization data header */ + + byte[] auth_buff = new byte[6]; + + if (remote_is.read(auth_buff) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); + + int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff); + int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff); + + if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256)) + throw new IOException("Buggy X11 authorization data"); + + int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4); + int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4); + + byte[] authProtocolName = new byte[authProtocolNameLength]; + byte[] authProtocolData = new byte[authProtocolDataLength]; + + byte[] paddingBuffer = new byte[4]; + + if (remote_is.read(authProtocolName) != authProtocolNameLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)"); + + if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)"); + + if (remote_is.read(authProtocolData) != authProtocolDataLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)"); + + if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)"); + + if ("MIT-MAGIC-COOKIE-1".equals(new String(authProtocolName, "ISO-8859-1")) == false) + throw new IOException("Unknown X11 authorization protocol!"); + + if (authProtocolDataLength != 16) + throw new IOException("Wrong data length for X11 authorization data!"); + + StringBuffer tmp = new StringBuffer(32); + for (int i = 0; i < authProtocolData.length; i++) + { + String digit2 = Integer.toHexString(authProtocolData[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + String hexEncodedFakeCookie = tmp.toString(); + + /* Order is very important here - it may be that a certain x11 forwarding + * gets disabled right in the moment when we check and register our connection + * */ + + synchronized (c) + { + /* Please read the comment in Channel.java */ + c.hexX11FakeCookie = hexEncodedFakeCookie; + } + + /* Now check our fake cookie directory to see if we produced this cookie */ + + X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie); + + if (sd == null) + throw new IOException("Invalid X11 cookie received."); + + /* If the session which corresponds to this cookie is closed then we will + * detect this: the session's close code will close all channels + * with the session's assigned x11 fake cookie. + */ + + s = new Socket(sd.hostname, sd.port); + + OutputStream x11_os = s.getOutputStream(); + InputStream x11_is = s.getInputStream(); + + /* Now we are sending the startup packet to the real X11 server */ + + x11_os.write(header); + + if (sd.x11_magic_cookie == null) + { + byte[] emptyAuthData = new byte[6]; + /* empty auth data, hopefully you are connecting to localhost =) */ + x11_os.write(emptyAuthData); + } + else + { + if (sd.x11_magic_cookie.length != 16) + throw new IOException("The real X11 cookie has an invalid length!"); + + /* send X11 cookie specified by client */ + x11_os.write(auth_buff); + x11_os.write(authProtocolName); /* re-use */ + x11_os.write(paddingBuffer, 0, authProtocolNamePadding); + x11_os.write(sd.x11_magic_cookie); + x11_os.write(paddingBuffer, 0, authProtocolDataPadding); + } + + x11_os.flush(); + + /* Start forwarding traffic */ + + StreamForwarder r2l = new StreamForwarder(c, null, s, remote_is, x11_os, "RemoteToX11"); + StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote"); + + /* No need to start two threads, one can be executed in the current thread */ + + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) + { + try + { + r2l.join(); + } + catch (InterruptedException e) + { + } + } + + /* If the channel is already closed, then this is a no-op */ + + c.cm.closeChannel(c, "EOF on both X11 streams reached.", true); + s.close(); + } + catch (IOException e) + { + log.log(50, "IOException in X11 proxy code: " + e.getMessage()); + + try + { + c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true); + } + catch (IOException e1) + { + } + try + { + if (s != null) + s.close(); + } + catch (IOException e1) + { + } + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java b/app/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java new file mode 100644 index 0000000..e1afee8 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java @@ -0,0 +1,113 @@ + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * A StreamForwarder forwards data between two given streams. + * If two StreamForwarder threads are used (one for each direction) + * then one can be configured to shutdown the underlying channel/socket + * if both threads have finished forwarding (EOF). + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: StreamForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class StreamForwarder extends Thread +{ + final OutputStream os; + final InputStream is; + final byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; + final Channel c; + final StreamForwarder sibling; + final Socket s; + final String mode; + + StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode) + throws IOException + { + this.is = is; + this.os = os; + this.mode = mode; + this.c = c; + this.sibling = sibling; + this.s = s; + } + + public void run() + { + try + { + while (true) + { + int len = is.read(buffer); + if (len <= 0) + break; + os.write(buffer, 0, len); + os.flush(); + } + } + catch (IOException ignore) + { + try + { + c.cm.closeChannel(c, "Closed due to exception in StreamForwarder (" + mode + "): " + + ignore.getMessage(), true); + } + catch (IOException e) + { + } + } + finally + { + try + { + os.close(); + } + catch (IOException e1) + { + } + try + { + is.close(); + } + catch (IOException e2) + { + } + + if (sibling != null) + { + while (sibling.isAlive()) + { + try + { + sibling.join(); + } + catch (InterruptedException e) + { + } + } + + try + { + c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true); + } + catch (IOException e3) + { + } + } + + if (s != null) { + try + { + s.close(); + } + catch (IOException e1) + { + } + } + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/channel/X11ServerData.java b/app/src/main/java/com/trilead/ssh2/channel/X11ServerData.java new file mode 100644 index 0000000..041f9cb --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/channel/X11ServerData.java @@ -0,0 +1,16 @@ + +package com.trilead.ssh2.channel; + +/** + * X11ServerData. Data regarding an x11 forwarding target. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: X11ServerData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + * + */ +public class X11ServerData +{ + public String hostname; + public int port; + public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */ +} diff --git a/app/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java b/app/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java new file mode 100644 index 0000000..9f8d7ef --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java @@ -0,0 +1,96 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.trilead.ssh2.compression; + +import java.util.Vector; + +/** + * @author Kenny Root + * + */ +public class CompressionFactory { + static class CompressorEntry + { + String type; + String compressorClass; + + public CompressorEntry(String type, String compressorClass) + { + this.type = type; + this.compressorClass = compressorClass; + } + } + + static Vector<CompressorEntry> compressors = new Vector<CompressorEntry>(); + + static + { + /* Higher Priority First */ + + compressors.addElement(new CompressorEntry("zlib", "com.trilead.ssh2.compression.Zlib")); + compressors.addElement(new CompressorEntry("zlib@openssh.com", "com.trilead.ssh2.compression.ZlibOpenSSH")); + compressors.addElement(new CompressorEntry("none", "")); + } + + public static String[] getDefaultCompressorList() + { + String list[] = new String[compressors.size()]; + for (int i = 0; i < compressors.size(); i++) + { + CompressorEntry ce = compressors.elementAt(i); + list[i] = new String(ce.type); + } + return list; + } + + public static void checkCompressorList(String[] compressorCandidates) + { + for (int i = 0; i < compressorCandidates.length; i++) + getEntry(compressorCandidates[i]); + } + + public static ICompressor createCompressor(String type) + { + try + { + CompressorEntry ce = getEntry(type); + if ("".equals(ce.compressorClass)) + return null; + + Class<?> cc = Class.forName(ce.compressorClass); + ICompressor cmp = (ICompressor) cc.newInstance(); + + return cmp; + } + catch (Exception e) + { + throw new IllegalArgumentException("Cannot instantiate " + type); + } + } + + private static CompressorEntry getEntry(String type) + { + for (int i = 0; i < compressors.size(); i++) + { + CompressorEntry ce = compressors.elementAt(i); + if (ce.type.equals(type)) + return ce; + } + throw new IllegalArgumentException("Unkown algorithm " + type); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/compression/ICompressor.java b/app/src/main/java/com/trilead/ssh2/compression/ICompressor.java new file mode 100644 index 0000000..0b435b9 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/compression/ICompressor.java @@ -0,0 +1,32 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.trilead.ssh2.compression; + +/** + * @author Kenny Root + * + */ +public interface ICompressor { + int getBufferSize(); + + int compress(byte[] buf, int start, int len, byte[] output); + + byte[] uncompress(byte[] buf, int start, int[] len); + + boolean canCompressPreauth(); +} diff --git a/app/src/main/java/com/trilead/ssh2/compression/Zlib.java b/app/src/main/java/com/trilead/ssh2/compression/Zlib.java new file mode 100644 index 0000000..c1203a3 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/compression/Zlib.java @@ -0,0 +1,130 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.trilead.ssh2.compression; + +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZStream; + +/** + * @author Kenny Root + * + */ +public class Zlib implements ICompressor { + static private final int DEFAULT_BUF_SIZE = 4096; + static private final int LEVEL = 5; + + private ZStream deflate; + private byte[] deflate_tmpbuf; + + private ZStream inflate; + private byte[] inflate_tmpbuf; + private byte[] inflated_buf; + + public Zlib() { + deflate = new ZStream(); + inflate = new ZStream(); + + deflate.deflateInit(LEVEL); + inflate.inflateInit(); + + deflate_tmpbuf = new byte[DEFAULT_BUF_SIZE]; + inflate_tmpbuf = new byte[DEFAULT_BUF_SIZE]; + inflated_buf = new byte[DEFAULT_BUF_SIZE]; + } + + public boolean canCompressPreauth() { + return true; + } + + public int getBufferSize() { + return DEFAULT_BUF_SIZE; + } + + public int compress(byte[] buf, int start, int len, byte[] output) { + deflate.next_in = buf; + deflate.next_in_index = start; + deflate.avail_in = len - start; + + if ((buf.length + 1024) > deflate_tmpbuf.length) { + deflate_tmpbuf = new byte[buf.length + 1024]; + } + + deflate.next_out = deflate_tmpbuf; + deflate.next_out_index = 0; + deflate.avail_out = output.length; + + if (deflate.deflate(JZlib.Z_PARTIAL_FLUSH) != JZlib.Z_OK) { + System.err.println("compress: compression failure"); + } + + if (deflate.avail_in > 0) { + System.err.println("compress: deflated data too large"); + } + + int outputlen = output.length - deflate.avail_out; + + System.arraycopy(deflate_tmpbuf, 0, output, 0, outputlen); + + return outputlen; + } + + public byte[] uncompress(byte[] buffer, int start, int[] length) { + int inflated_end = 0; + + inflate.next_in = buffer; + inflate.next_in_index = start; + inflate.avail_in = length[0]; + + while (true) { + inflate.next_out = inflate_tmpbuf; + inflate.next_out_index = 0; + inflate.avail_out = DEFAULT_BUF_SIZE; + int status = inflate.inflate(JZlib.Z_PARTIAL_FLUSH); + switch (status) { + case JZlib.Z_OK: + if (inflated_buf.length < inflated_end + DEFAULT_BUF_SIZE + - inflate.avail_out) { + byte[] foo = new byte[inflated_end + DEFAULT_BUF_SIZE + - inflate.avail_out]; + System.arraycopy(inflated_buf, 0, foo, 0, inflated_end); + inflated_buf = foo; + } + System.arraycopy(inflate_tmpbuf, 0, inflated_buf, inflated_end, + DEFAULT_BUF_SIZE - inflate.avail_out); + inflated_end += (DEFAULT_BUF_SIZE - inflate.avail_out); + length[0] = inflated_end; + break; + case JZlib.Z_BUF_ERROR: + if (inflated_end > buffer.length - start) { + byte[] foo = new byte[inflated_end + start]; + System.arraycopy(buffer, 0, foo, 0, start); + System.arraycopy(inflated_buf, 0, foo, start, inflated_end); + buffer = foo; + } else { + System.arraycopy(inflated_buf, 0, buffer, start, + inflated_end); + } + length[0] = inflated_end; + return buffer; + default: + System.err.println("uncompress: inflate returnd " + status); + return null; + } + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/compression/ZlibOpenSSH.java b/app/src/main/java/com/trilead/ssh2/compression/ZlibOpenSSH.java new file mode 100644 index 0000000..266fff9 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/compression/ZlibOpenSSH.java @@ -0,0 +1,35 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.trilead.ssh2.compression; + +/** + * Defines how zlib@openssh.org compression works. + * See + * http://www.openssh.org/txt/draft-miller-secsh-compression-delayed-00.txt + * compression is disabled until userauth has occurred. + * + * @author Matt Johnston + * + */ +public class ZlibOpenSSH extends Zlib { + + public boolean canCompressPreauth() { + return false; + } + +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/Base64.java b/app/src/main/java/com/trilead/ssh2/crypto/Base64.java new file mode 100644 index 0000000..93770ac --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/Base64.java @@ -0,0 +1,148 @@ + +package com.trilead.ssh2.crypto; + +import java.io.CharArrayWriter; +import java.io.IOException; + +/** + * Basic Base64 Support. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Base64.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class Base64 +{ + static final char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + + public static char[] encode(byte[] content) + { + CharArrayWriter cw = new CharArrayWriter((4 * content.length) / 3); + + int idx = 0; + + int x = 0; + + for (int i = 0; i < content.length; i++) + { + if (idx == 0) + x = (content[i] & 0xff) << 16; + else if (idx == 1) + x = x | ((content[i] & 0xff) << 8); + else + x = x | (content[i] & 0xff); + + idx++; + + if (idx == 3) + { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write(alphabet[(x >> 6) & 0x3f]); + cw.write(alphabet[x & 0x3f]); + + idx = 0; + } + } + + if (idx == 1) + { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write('='); + cw.write('='); + } + + if (idx == 2) + { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write(alphabet[(x >> 6) & 0x3f]); + cw.write('='); + } + + return cw.toCharArray(); + } + + public static byte[] decode(char[] message) throws IOException + { + byte buff[] = new byte[4]; + byte dest[] = new byte[message.length]; + + int bpos = 0; + int destpos = 0; + + for (int i = 0; i < message.length; i++) + { + int c = message[i]; + + if ((c == '\n') || (c == '\r') || (c == ' ') || (c == '\t')) + continue; + + if ((c >= 'A') && (c <= 'Z')) + { + buff[bpos++] = (byte) (c - 'A'); + } + else if ((c >= 'a') && (c <= 'z')) + { + buff[bpos++] = (byte) ((c - 'a') + 26); + } + else if ((c >= '0') && (c <= '9')) + { + buff[bpos++] = (byte) ((c - '0') + 52); + } + else if (c == '+') + { + buff[bpos++] = 62; + } + else if (c == '/') + { + buff[bpos++] = 63; + } + else if (c == '=') + { + buff[bpos++] = 64; + } + else + { + throw new IOException("Illegal char in base64 code."); + } + + if (bpos == 4) + { + bpos = 0; + + if (buff[0] == 64) + break; + + if (buff[1] == 64) + throw new IOException("Unexpected '=' in base64 code."); + + if (buff[2] == 64) + { + int v = (((buff[0] & 0x3f) << 6) | ((buff[1] & 0x3f))); + dest[destpos++] = (byte) (v >> 4); + break; + } + else if (buff[3] == 64) + { + int v = (((buff[0] & 0x3f) << 12) | ((buff[1] & 0x3f) << 6) | ((buff[2] & 0x3f))); + dest[destpos++] = (byte) (v >> 10); + dest[destpos++] = (byte) (v >> 2); + break; + } + else + { + int v = (((buff[0] & 0x3f) << 18) | ((buff[1] & 0x3f) << 12) | ((buff[2] & 0x3f) << 6) | ((buff[3] & 0x3f))); + dest[destpos++] = (byte) (v >> 16); + dest[destpos++] = (byte) (v >> 8); + dest[destpos++] = (byte) (v); + } + } + } + + byte[] res = new byte[destpos]; + System.arraycopy(dest, 0, res, 0, destpos); + + return res; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java b/app/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java new file mode 100644 index 0000000..86959e7 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java @@ -0,0 +1,26 @@ + +package com.trilead.ssh2.crypto; + +import com.trilead.ssh2.compression.CompressionFactory; +import com.trilead.ssh2.crypto.cipher.BlockCipherFactory; +import com.trilead.ssh2.crypto.digest.MAC; +import com.trilead.ssh2.transport.KexManager; + + +/** + * CryptoWishList. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: CryptoWishList.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class CryptoWishList +{ + public String[] kexAlgorithms = KexManager.getDefaultKexAlgorithmList(); + public String[] serverHostKeyAlgorithms = KexManager.getDefaultServerHostkeyAlgorithmList(); + public String[] c2s_enc_algos = BlockCipherFactory.getDefaultCipherList(); + public String[] s2c_enc_algos = BlockCipherFactory.getDefaultCipherList(); + public String[] c2s_mac_algos = MAC.getMacList(); + public String[] s2c_mac_algos = MAC.getMacList(); + public String[] c2s_comp_algos = CompressionFactory.getDefaultCompressorList(); + public String[] s2c_comp_algos = CompressionFactory.getDefaultCompressorList(); +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/KeyMaterial.java b/app/src/main/java/com/trilead/ssh2/crypto/KeyMaterial.java new file mode 100644 index 0000000..1dbd6c7 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/KeyMaterial.java @@ -0,0 +1,91 @@ + +package com.trilead.ssh2.crypto; + + +import java.math.BigInteger; + +import com.trilead.ssh2.crypto.digest.HashForSSH2Types; + +/** + * Establishes key material for iv/key/mac (both directions). + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: KeyMaterial.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class KeyMaterial +{ + public byte[] initial_iv_client_to_server; + public byte[] initial_iv_server_to_client; + public byte[] enc_key_client_to_server; + public byte[] enc_key_server_to_client; + public byte[] integrity_key_client_to_server; + public byte[] integrity_key_server_to_client; + + private static byte[] calculateKey(HashForSSH2Types sh, BigInteger K, byte[] H, byte type, byte[] SessionID, + int keyLength) + { + byte[] res = new byte[keyLength]; + + int dglen = sh.getDigestLength(); + int numRounds = (keyLength + dglen - 1) / dglen; + + byte[][] tmp = new byte[numRounds][]; + + sh.reset(); + sh.updateBigInt(K); + sh.updateBytes(H); + sh.updateByte(type); + sh.updateBytes(SessionID); + + tmp[0] = sh.getDigest(); + + int off = 0; + int produced = Math.min(dglen, keyLength); + + System.arraycopy(tmp[0], 0, res, off, produced); + + keyLength -= produced; + off += produced; + + for (int i = 1; i < numRounds; i++) + { + sh.updateBigInt(K); + sh.updateBytes(H); + + for (int j = 0; j < i; j++) + sh.updateBytes(tmp[j]); + + tmp[i] = sh.getDigest(); + + produced = Math.min(dglen, keyLength); + System.arraycopy(tmp[i], 0, res, off, produced); + keyLength -= produced; + off += produced; + } + + return res; + } + + public static KeyMaterial create(String hashAlgo, byte[] H, BigInteger K, byte[] SessionID, int keyLengthCS, + int blockSizeCS, int macLengthCS, int keyLengthSC, int blockSizeSC, int macLengthSC) + throws IllegalArgumentException + { + KeyMaterial km = new KeyMaterial(); + + HashForSSH2Types sh = new HashForSSH2Types(hashAlgo); + + km.initial_iv_client_to_server = calculateKey(sh, K, H, (byte) 'A', SessionID, blockSizeCS); + + km.initial_iv_server_to_client = calculateKey(sh, K, H, (byte) 'B', SessionID, blockSizeSC); + + km.enc_key_client_to_server = calculateKey(sh, K, H, (byte) 'C', SessionID, keyLengthCS); + + km.enc_key_server_to_client = calculateKey(sh, K, H, (byte) 'D', SessionID, keyLengthSC); + + km.integrity_key_client_to_server = calculateKey(sh, K, H, (byte) 'E', SessionID, macLengthCS); + + km.integrity_key_server_to_client = calculateKey(sh, K, H, (byte) 'F', SessionID, macLengthSC); + + return km; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java b/app/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java new file mode 100644 index 0000000..5c0c2fd --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java @@ -0,0 +1,494 @@ + +package com.trilead.ssh2.crypto; + +import java.io.BufferedReader; +import java.io.CharArrayReader; +import java.io.IOException; +import java.math.BigInteger; +import java.security.DigestException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; + +import com.trilead.ssh2.crypto.cipher.AES; +import com.trilead.ssh2.crypto.cipher.BlockCipher; +import com.trilead.ssh2.crypto.cipher.CBCMode; +import com.trilead.ssh2.crypto.cipher.DES; +import com.trilead.ssh2.crypto.cipher.DESede; +import com.trilead.ssh2.signature.ECDSASHA2Verify; + +/** + * PEM Support. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PEMDecoder.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class PEMDecoder +{ + public static final int PEM_RSA_PRIVATE_KEY = 1; + public static final int PEM_DSA_PRIVATE_KEY = 2; + public static final int PEM_EC_PRIVATE_KEY = 3; + + private static final int hexToInt(char c) + { + if ((c >= 'a') && (c <= 'f')) + { + return (c - 'a') + 10; + } + + if ((c >= 'A') && (c <= 'F')) + { + return (c - 'A') + 10; + } + + if ((c >= '0') && (c <= '9')) + { + return (c - '0'); + } + + throw new IllegalArgumentException("Need hex char"); + } + + private static byte[] hexToByteArray(String hex) + { + if (hex == null) + throw new IllegalArgumentException("null argument"); + + if ((hex.length() % 2) != 0) + throw new IllegalArgumentException("Uneven string length in hex encoding."); + + byte decoded[] = new byte[hex.length() / 2]; + + for (int i = 0; i < decoded.length; i++) + { + int hi = hexToInt(hex.charAt(i * 2)); + int lo = hexToInt(hex.charAt((i * 2) + 1)); + + decoded[i] = (byte) (hi * 16 + lo); + } + + return decoded; + } + + private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) + throws IOException + { + if (salt.length < 8) + throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); + + MessageDigest md5; + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("VM does not support MD5", e); + } + + byte[] key = new byte[keyLen]; + byte[] tmp = new byte[md5.getDigestLength()]; + + while (true) + { + md5.update(password, 0, password.length); + md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the + // salt in this step. + // This took me two hours until I got AES-xxx running. + + int copy = (keyLen < tmp.length) ? keyLen : tmp.length; + + try { + md5.digest(tmp, 0, tmp.length); + } catch (DigestException e) { + IOException ex = new IOException("could not digest password"); + ex.initCause(e); + throw ex; + } + + System.arraycopy(tmp, 0, key, key.length - keyLen, copy); + + keyLen -= copy; + + if (keyLen == 0) + return key; + + md5.update(tmp, 0, tmp.length); + } + } + + private static byte[] removePadding(byte[] buff, int blockSize) throws IOException + { + /* Removes RFC 1423/PKCS #7 padding */ + + int rfc_1423_padding = buff[buff.length - 1] & 0xff; + + if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) + throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); + + for (int i = 2; i <= rfc_1423_padding; i++) + { + if (buff[buff.length - i] != rfc_1423_padding) + throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); + } + + byte[] tmp = new byte[buff.length - rfc_1423_padding]; + System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); + return tmp; + } + + public static final PEMStructure parsePEM(char[] pem) throws IOException + { + PEMStructure ps = new PEMStructure(); + + String line = null; + + BufferedReader br = new BufferedReader(new CharArrayReader(pem)); + + String endLine = null; + + while (true) + { + line = br.readLine(); + + if (line == null) + throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); + + line = line.trim(); + + if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) + { + endLine = "-----END DSA PRIVATE KEY-----"; + ps.pemType = PEM_DSA_PRIVATE_KEY; + break; + } + + if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) + { + endLine = "-----END RSA PRIVATE KEY-----"; + ps.pemType = PEM_RSA_PRIVATE_KEY; + break; + } + + if (line.startsWith("-----BEGIN EC PRIVATE KEY-----")) { + endLine = "-----END EC PRIVATE KEY-----"; + ps.pemType = PEM_EC_PRIVATE_KEY; + break; + } + } + + while (true) + { + line = br.readLine(); + + if (line == null) + throw new IOException("Invalid PEM structure, " + endLine + " missing"); + + line = line.trim(); + + int sem_idx = line.indexOf(':'); + + if (sem_idx == -1) + break; + + String name = line.substring(0, sem_idx + 1); + String value = line.substring(sem_idx + 1); + + String values[] = value.split(","); + + for (int i = 0; i < values.length; i++) + values[i] = values[i].trim(); + + // Proc-Type: 4,ENCRYPTED + // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 + + if ("Proc-Type:".equals(name)) + { + ps.procType = values; + continue; + } + + if ("DEK-Info:".equals(name)) + { + ps.dekInfo = values; + continue; + } + /* Ignore line */ + } + + StringBuffer keyData = new StringBuffer(); + + while (true) + { + if (line == null) + throw new IOException("Invalid PEM structure, " + endLine + " missing"); + + line = line.trim(); + + if (line.startsWith(endLine)) + break; + + keyData.append(line); + + line = br.readLine(); + } + + char[] pem_chars = new char[keyData.length()]; + keyData.getChars(0, pem_chars.length, pem_chars, 0); + + ps.data = Base64.decode(pem_chars); + + if (ps.data.length == 0) + throw new IOException("Invalid PEM structure, no data available"); + + return ps; + } + + private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException + { + if (ps.dekInfo == null) + throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); + + if (ps.dekInfo.length != 2) + throw new IOException("Broken PEM, DEK-Info is incomplete!"); + + String algo = ps.dekInfo[0]; + byte[] salt = hexToByteArray(ps.dekInfo[1]); + + BlockCipher bc = null; + + if (algo.equals("DES-EDE3-CBC")) + { + DESede des3 = new DESede(); + des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); + bc = new CBCMode(des3, salt, false); + } + else if (algo.equals("DES-CBC")) + { + DES des = new DES(); + des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); + bc = new CBCMode(des, salt, false); + } + else if (algo.equals("AES-128-CBC")) + { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); + bc = new CBCMode(aes, salt, false); + } + else if (algo.equals("AES-192-CBC")) + { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); + bc = new CBCMode(aes, salt, false); + } + else if (algo.equals("AES-256-CBC")) + { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); + bc = new CBCMode(aes, salt, false); + } + else + { + throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); + } + + if ((ps.data.length % bc.getBlockSize()) != 0) + throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " + + bc.getBlockSize()); + + /* Now decrypt the content */ + + byte[] dz = new byte[ps.data.length]; + + for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) + { + bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); + } + + /* Now check and remove RFC 1423/PKCS #7 padding */ + + dz = removePadding(dz, bc.getBlockSize()); + + ps.data = dz; + ps.dekInfo = null; + ps.procType = null; + } + + public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException + { + if (ps.procType == null) + return false; + + if (ps.procType.length != 2) + throw new IOException("Unknown Proc-Type field."); + + if ("4".equals(ps.procType[0]) == false) + throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); + + if ("ENCRYPTED".equals(ps.procType[1])) + return true; + + return false; + } + + public static KeyPair decode(char[] pem, String password) throws IOException + { + PEMStructure ps = parsePEM(pem); + return decode(ps, password); + } + + public static KeyPair decode(PEMStructure ps, String password) throws IOException + { + if (isPEMEncrypted(ps)) + { + if (password == null) + throw new IOException("PEM is encrypted, but no password was specified"); + + decryptPEM(ps, password.getBytes("ISO-8859-1")); + } + + if (ps.pemType == PEM_DSA_PRIVATE_KEY) + { + SimpleDERReader dr = new SimpleDERReader(ps.data); + + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in DSA PRIVATE KEY DER stream."); + + dr.resetInput(seq); + + BigInteger version = dr.readInt(); + + if (version.compareTo(BigInteger.ZERO) != 0) + throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); + + BigInteger p = dr.readInt(); + BigInteger q = dr.readInt(); + BigInteger g = dr.readInt(); + BigInteger y = dr.readInt(); + BigInteger x = dr.readInt(); + + if (dr.available() != 0) + throw new IOException("Padding in DSA PRIVATE KEY DER stream."); + + DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec(x, p, q, g); + DSAPublicKeySpec pubSpec = new DSAPublicKeySpec(y, p, q, g); + + return generateKeyPair("DSA", privSpec, pubSpec); + } + + if (ps.pemType == PEM_RSA_PRIVATE_KEY) + { + SimpleDERReader dr = new SimpleDERReader(ps.data); + + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in RSA PRIVATE KEY DER stream."); + + dr.resetInput(seq); + + BigInteger version = dr.readInt(); + + if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) + throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); + + BigInteger n = dr.readInt(); + BigInteger e = dr.readInt(); + BigInteger d = dr.readInt(); + // TODO: is this right? + BigInteger primeP = dr.readInt(); + BigInteger primeQ = dr.readInt(); + BigInteger expP = dr.readInt(); + BigInteger expQ = dr.readInt(); + BigInteger coeff = dr.readInt(); + + RSAPrivateKeySpec privSpec = new RSAPrivateCrtKeySpec(n, e, d, primeP, primeQ, expP, expQ, coeff); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(n, e); + + return generateKeyPair("RSA", privSpec, pubSpec); + } + + if (ps.pemType == PEM_EC_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); + + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in EC PRIVATE KEY DER stream."); + + dr.resetInput(seq); + + BigInteger version = dr.readInt(); + + if ((version.compareTo(BigInteger.ONE) != 0)) + throw new IOException("Wrong version (" + version + ") in EC PRIVATE KEY DER stream."); + + byte[] privateBytes = dr.readOctetString(); + + String curveOid = null; + byte[] publicBytes = null; + while (dr.available() > 0) { + int type = dr.readConstructedType(); + SimpleDERReader cr = dr.readConstructed(); + switch (type) { + case 0: + curveOid = cr.readOid(); + break; + case 1: + publicBytes = cr.readOctetString(); + break; + } + } + + ECParameterSpec params = ECDSASHA2Verify.getCurveForOID(curveOid); + if (params == null) + throw new IOException("invalid OID"); + + BigInteger s = new BigInteger(privateBytes); + byte[] publicBytesSlice = new byte[publicBytes.length - 1]; + System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length); + ECPoint w = ECDSASHA2Verify.decodeECPoint(publicBytesSlice, params.getCurve()); + + ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, params); + ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, params); + + return generateKeyPair("EC", privSpec, pubSpec); + } + + throw new IOException("PEM problem: it is of unknown type"); + } + + /** + * Generate a {@code KeyPair} given an {@code algorithm} and {@code KeySpec}. + */ + private static KeyPair generateKeyPair(String algorithm, KeySpec privSpec, KeySpec pubSpec) + throws IOException { + try { + final KeyFactory kf = KeyFactory.getInstance(algorithm); + final PublicKey pubKey = kf.generatePublic(pubSpec); + final PrivateKey privKey = kf.generatePrivate(privSpec); + return new KeyPair(pubKey, privKey); + } catch (NoSuchAlgorithmException ex) { + IOException ioex = new IOException(); + ioex.initCause(ex); + throw ioex; + } catch (InvalidKeySpecException ex) { + IOException ioex = new IOException("invalid keyspec"); + ioex.initCause(ex); + throw ioex; + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java b/app/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java new file mode 100644 index 0000000..83fb799 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java @@ -0,0 +1,17 @@ + +package com.trilead.ssh2.crypto; + +/** + * Parsed PEM structure. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PEMStructure.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ + +public class PEMStructure +{ + public int pemType; + String dekInfo[]; + String procType[]; + public byte[] data; +}
\ No newline at end of file diff --git a/app/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java b/app/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java new file mode 100644 index 0000000..ff8112a --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java @@ -0,0 +1,229 @@ +package com.trilead.ssh2.crypto; + +import java.io.IOException; + +import java.math.BigInteger; + +/** + * SimpleDERReader. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SimpleDERReader.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class SimpleDERReader +{ + private static final int CONSTRUCTED = 0x20; + + byte[] buffer; + int pos; + int count; + + public SimpleDERReader(byte[] b) + { + resetInput(b); + } + + public SimpleDERReader(byte[] b, int off, int len) + { + resetInput(b, off, len); + } + + public void resetInput(byte[] b) + { + resetInput(b, 0, b.length); + } + + public void resetInput(byte[] b, int off, int len) + { + buffer = b; + pos = off; + count = len; + } + + private byte readByte() throws IOException + { + if (count <= 0) + throw new IOException("DER byte array: out of data"); + count--; + return buffer[pos++]; + } + + private byte[] readBytes(int len) throws IOException + { + if (len > count) + throw new IOException("DER byte array: out of data"); + + byte[] b = new byte[len]; + + System.arraycopy(buffer, pos, b, 0, len); + + pos += len; + count -= len; + + return b; + } + + public int available() + { + return count; + } + + private int readLength() throws IOException + { + int len = readByte() & 0xff; + + if ((len & 0x80) == 0) + return len; + + int remain = len & 0x7F; + + if (remain == 0) + return -1; + + len = 0; + + while (remain > 0) + { + len = len << 8; + len = len | (readByte() & 0xff); + remain--; + } + + return len; + } + + public int ignoreNextObject() throws IOException + { + int type = readByte() & 0xff; + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + readBytes(len); + + return type; + } + + public BigInteger readInt() throws IOException + { + int type = readByte() & 0xff; + + if (type != 0x02) + throw new IOException("Expected DER Integer, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + + BigInteger bi = new BigInteger(b); + + return bi; + } + + public int readConstructedType() throws IOException { + int type = readByte() & 0xff; + + if ((type & CONSTRUCTED) != CONSTRUCTED) + throw new IOException("Expected constructed type, but was " + type); + + return type & 0x1f; + } + + public SimpleDERReader readConstructed() throws IOException + { + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + SimpleDERReader cr = new SimpleDERReader(buffer, pos, len); + + pos += len; + count -= len; + + return cr; + } + + public byte[] readSequenceAsByteArray() throws IOException + { + int type = readByte() & 0xff; + + if (type != 0x30) + throw new IOException("Expected DER Sequence, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + + return b; + } + + public String readOid() throws IOException + { + int type = readByte() & 0xff; + + if (type != 0x06) + throw new IOException("Expected DER OID, but found type " + type); + + int len = readLength(); + + if ((len < 1) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + + long value = 0; + + StringBuilder sb = new StringBuilder(64); + switch(b[0] / 40) { + case 0: + sb.append('0'); + break; + case 1: + sb.append('1'); + b[0] -= 40; + break; + default: + sb.append('2'); + b[0] -= 80; + break; + } + + for (int i = 0; i < len; i++) { + value = (value << 7) + (b[i] & 0x7F); + if ((b[i] & 0x80) == 0) { + sb.append('.'); + sb.append(value); + value = 0; + } + } + + return sb.toString(); + } + + public byte[] readOctetString() throws IOException + { + int type = readByte() & 0xff; + + if (type != 0x04 && type != 0x03) + throw new IOException("Expected DER Octetstring, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + + return b; + } + +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/AES.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/AES.java new file mode 100644 index 0000000..e89e4a6 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/AES.java @@ -0,0 +1,698 @@ + +package com.trilead.ssh2.crypto.cipher; + +/* + This file was shamelessly taken from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * An implementation of the AES (Rijndael), from FIPS-197. + * <p> + * For further details see: <a + * href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/ + * </a>. + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper + * and C code at <a + * href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/ + * </a> + * + * There are three levels of tradeoff of speed vs memory Because java has no + * preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 + * 256 word tables for encryption and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a + * total of 2Kbytes, adding 12 rotate operations per round to compute the values + * contained in the other tables from the contents of the first + * + * The slowest version uses no static tables at all and computes the values in + * each round + * <p> + * This file contains the fast version with 8Kbytes of static tables for round + * precomputation + * + * @author See comments in the source file + * @version $Id: AES.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class AES implements BlockCipher +{ + // The S box + private static final byte[] S = { (byte) 99, (byte) 124, (byte) 119, (byte) 123, (byte) 242, (byte) 107, + (byte) 111, (byte) 197, (byte) 48, (byte) 1, (byte) 103, (byte) 43, (byte) 254, (byte) 215, (byte) 171, + (byte) 118, (byte) 202, (byte) 130, (byte) 201, (byte) 125, (byte) 250, (byte) 89, (byte) 71, (byte) 240, + (byte) 173, (byte) 212, (byte) 162, (byte) 175, (byte) 156, (byte) 164, (byte) 114, (byte) 192, (byte) 183, + (byte) 253, (byte) 147, (byte) 38, (byte) 54, (byte) 63, (byte) 247, (byte) 204, (byte) 52, (byte) 165, + (byte) 229, (byte) 241, (byte) 113, (byte) 216, (byte) 49, (byte) 21, (byte) 4, (byte) 199, (byte) 35, + (byte) 195, (byte) 24, (byte) 150, (byte) 5, (byte) 154, (byte) 7, (byte) 18, (byte) 128, (byte) 226, + (byte) 235, (byte) 39, (byte) 178, (byte) 117, (byte) 9, (byte) 131, (byte) 44, (byte) 26, (byte) 27, + (byte) 110, (byte) 90, (byte) 160, (byte) 82, (byte) 59, (byte) 214, (byte) 179, (byte) 41, (byte) 227, + (byte) 47, (byte) 132, (byte) 83, (byte) 209, (byte) 0, (byte) 237, (byte) 32, (byte) 252, (byte) 177, + (byte) 91, (byte) 106, (byte) 203, (byte) 190, (byte) 57, (byte) 74, (byte) 76, (byte) 88, (byte) 207, + (byte) 208, (byte) 239, (byte) 170, (byte) 251, (byte) 67, (byte) 77, (byte) 51, (byte) 133, (byte) 69, + (byte) 249, (byte) 2, (byte) 127, (byte) 80, (byte) 60, (byte) 159, (byte) 168, (byte) 81, (byte) 163, + (byte) 64, (byte) 143, (byte) 146, (byte) 157, (byte) 56, (byte) 245, (byte) 188, (byte) 182, (byte) 218, + (byte) 33, (byte) 16, (byte) 255, (byte) 243, (byte) 210, (byte) 205, (byte) 12, (byte) 19, (byte) 236, + (byte) 95, (byte) 151, (byte) 68, (byte) 23, (byte) 196, (byte) 167, (byte) 126, (byte) 61, (byte) 100, + (byte) 93, (byte) 25, (byte) 115, (byte) 96, (byte) 129, (byte) 79, (byte) 220, (byte) 34, (byte) 42, + (byte) 144, (byte) 136, (byte) 70, (byte) 238, (byte) 184, (byte) 20, (byte) 222, (byte) 94, (byte) 11, + (byte) 219, (byte) 224, (byte) 50, (byte) 58, (byte) 10, (byte) 73, (byte) 6, (byte) 36, (byte) 92, + (byte) 194, (byte) 211, (byte) 172, (byte) 98, (byte) 145, (byte) 149, (byte) 228, (byte) 121, (byte) 231, + (byte) 200, (byte) 55, (byte) 109, (byte) 141, (byte) 213, (byte) 78, (byte) 169, (byte) 108, (byte) 86, + (byte) 244, (byte) 234, (byte) 101, (byte) 122, (byte) 174, (byte) 8, (byte) 186, (byte) 120, (byte) 37, + (byte) 46, (byte) 28, (byte) 166, (byte) 180, (byte) 198, (byte) 232, (byte) 221, (byte) 116, (byte) 31, + (byte) 75, (byte) 189, (byte) 139, (byte) 138, (byte) 112, (byte) 62, (byte) 181, (byte) 102, (byte) 72, + (byte) 3, (byte) 246, (byte) 14, (byte) 97, (byte) 53, (byte) 87, (byte) 185, (byte) 134, (byte) 193, + (byte) 29, (byte) 158, (byte) 225, (byte) 248, (byte) 152, (byte) 17, (byte) 105, (byte) 217, (byte) 142, + (byte) 148, (byte) 155, (byte) 30, (byte) 135, (byte) 233, (byte) 206, (byte) 85, (byte) 40, (byte) 223, + (byte) 140, (byte) 161, (byte) 137, (byte) 13, (byte) 191, (byte) 230, (byte) 66, (byte) 104, (byte) 65, + (byte) 153, (byte) 45, (byte) 15, (byte) 176, (byte) 84, (byte) 187, (byte) 22, }; + + // The inverse S-box + private static final byte[] Si = { (byte) 82, (byte) 9, (byte) 106, (byte) 213, (byte) 48, (byte) 54, (byte) 165, + (byte) 56, (byte) 191, (byte) 64, (byte) 163, (byte) 158, (byte) 129, (byte) 243, (byte) 215, (byte) 251, + (byte) 124, (byte) 227, (byte) 57, (byte) 130, (byte) 155, (byte) 47, (byte) 255, (byte) 135, (byte) 52, + (byte) 142, (byte) 67, (byte) 68, (byte) 196, (byte) 222, (byte) 233, (byte) 203, (byte) 84, (byte) 123, + (byte) 148, (byte) 50, (byte) 166, (byte) 194, (byte) 35, (byte) 61, (byte) 238, (byte) 76, (byte) 149, + (byte) 11, (byte) 66, (byte) 250, (byte) 195, (byte) 78, (byte) 8, (byte) 46, (byte) 161, (byte) 102, + (byte) 40, (byte) 217, (byte) 36, (byte) 178, (byte) 118, (byte) 91, (byte) 162, (byte) 73, (byte) 109, + (byte) 139, (byte) 209, (byte) 37, (byte) 114, (byte) 248, (byte) 246, (byte) 100, (byte) 134, (byte) 104, + (byte) 152, (byte) 22, (byte) 212, (byte) 164, (byte) 92, (byte) 204, (byte) 93, (byte) 101, (byte) 182, + (byte) 146, (byte) 108, (byte) 112, (byte) 72, (byte) 80, (byte) 253, (byte) 237, (byte) 185, (byte) 218, + (byte) 94, (byte) 21, (byte) 70, (byte) 87, (byte) 167, (byte) 141, (byte) 157, (byte) 132, (byte) 144, + (byte) 216, (byte) 171, (byte) 0, (byte) 140, (byte) 188, (byte) 211, (byte) 10, (byte) 247, (byte) 228, + (byte) 88, (byte) 5, (byte) 184, (byte) 179, (byte) 69, (byte) 6, (byte) 208, (byte) 44, (byte) 30, + (byte) 143, (byte) 202, (byte) 63, (byte) 15, (byte) 2, (byte) 193, (byte) 175, (byte) 189, (byte) 3, + (byte) 1, (byte) 19, (byte) 138, (byte) 107, (byte) 58, (byte) 145, (byte) 17, (byte) 65, (byte) 79, + (byte) 103, (byte) 220, (byte) 234, (byte) 151, (byte) 242, (byte) 207, (byte) 206, (byte) 240, (byte) 180, + (byte) 230, (byte) 115, (byte) 150, (byte) 172, (byte) 116, (byte) 34, (byte) 231, (byte) 173, (byte) 53, + (byte) 133, (byte) 226, (byte) 249, (byte) 55, (byte) 232, (byte) 28, (byte) 117, (byte) 223, (byte) 110, + (byte) 71, (byte) 241, (byte) 26, (byte) 113, (byte) 29, (byte) 41, (byte) 197, (byte) 137, (byte) 111, + (byte) 183, (byte) 98, (byte) 14, (byte) 170, (byte) 24, (byte) 190, (byte) 27, (byte) 252, (byte) 86, + (byte) 62, (byte) 75, (byte) 198, (byte) 210, (byte) 121, (byte) 32, (byte) 154, (byte) 219, (byte) 192, + (byte) 254, (byte) 120, (byte) 205, (byte) 90, (byte) 244, (byte) 31, (byte) 221, (byte) 168, (byte) 51, + (byte) 136, (byte) 7, (byte) 199, (byte) 49, (byte) 177, (byte) 18, (byte) 16, (byte) 89, (byte) 39, + (byte) 128, (byte) 236, (byte) 95, (byte) 96, (byte) 81, (byte) 127, (byte) 169, (byte) 25, (byte) 181, + (byte) 74, (byte) 13, (byte) 45, (byte) 229, (byte) 122, (byte) 159, (byte) 147, (byte) 201, (byte) 156, + (byte) 239, (byte) 160, (byte) 224, (byte) 59, (byte) 77, (byte) 174, (byte) 42, (byte) 245, (byte) 176, + (byte) 200, (byte) 235, (byte) 187, (byte) 60, (byte) 131, (byte) 83, (byte) 153, (byte) 97, (byte) 23, + (byte) 43, (byte) 4, (byte) 126, (byte) 186, (byte) 119, (byte) 214, (byte) 38, (byte) 225, (byte) 105, + (byte) 20, (byte) 99, (byte) 85, (byte) 33, (byte) 12, (byte) 125, }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, + 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + // precomputation tables of calculations for rounds + private static final int[] T0 = { 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, + 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, + 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, 0xc2b7b775, + 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, 0x5c343468, 0xf4a5a551, + 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, 0x65232346, + 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, + 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, + 0x712f2f5e, 0x97848413, 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, + 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, + 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, + 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, + 0x63212142, 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, + 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 0xac6464c8, + 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, + 0x76dbdbad, 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8, + 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, 0x32e7e7d5, + 0x43c8c88b, 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, 0xfa5656ac, + 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, + 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890, + 0x05030306, 0x01f6f6f7, 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, + 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, + 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, + 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c }; + + private static final int[] T1 = { 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, + 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, + 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, + 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, 0xb7b775c2, + 0xfdfde11c, 0x93933dae, 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, 0x3434685c, 0xa5a551f4, + 0xe5e5d134, 0xf1f1f908, 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, 0x23234665, + 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, + 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, + 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, + 0x2f2f5e71, 0x84841397, 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, 0xfcfce31f, 0xb1b179c8, + 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, + 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, + 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, + 0x404080c0, 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, + 0x21214263, 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, 0x13132635, 0xececc32f, + 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, 0x6464c8ac, + 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, + 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, + 0xdbdbad76, 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, 0x06060c0a, 0x2424486c, 0x5c5cb8e4, + 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, 0xe7e7d532, + 0xc8c88b43, 0x37376e59, 0x6d6ddab7, 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, 0x5656acfa, + 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, + 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, + 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, 0x484890d8, + 0x03030605, 0xf6f6f701, 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, 0x86861791, 0xc1c19958, + 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, + 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, + 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a }; + + private static final int[] T2 = { 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, + 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, + 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, + 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, 0xb775c2b7, + 0xfde11cfd, 0x933dae93, 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, 0x34685c34, 0xa551f4a5, + 0xe5d134e5, 0xf1f908f1, 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, 0x23466523, + 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, + 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, + 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, + 0x2f5e712f, 0x84139784, 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, 0xfce31ffc, 0xb179c8b1, + 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, + 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, + 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, + 0x4080c040, 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, + 0x21426321, 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, 0x13263513, 0xecc32fec, + 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, 0x64c8ac64, + 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, + 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, + 0xdbad76db, 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, 0x060c0a06, 0x24486c24, 0x5cb8e45c, + 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, 0xe7d532e7, + 0xc88b43c8, 0x376e5937, 0x6ddab76d, 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, 0x56acfa56, + 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, + 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, + 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, 0x4890d848, + 0x03060503, 0xf6f701f6, 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, 0x86179186, 0xc19958c1, + 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, 0x8e07898e, + 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, + 0x9929b099, 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16 }; + + private static final int[] T3 = { 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, + 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, + 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, + 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, + 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, 0x685c3434, 0x51f4a5a5, + 0xd134e5e5, 0xf908f1f1, 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, 0x46652323, + 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, + 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, + 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, + 0x5e712f2f, 0x13978484, 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, 0xe31ffcfc, 0x79c8b1b1, + 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, + 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, + 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, + 0x80c04040, 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, + 0x42632121, 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, + 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, 0xc8ac6464, + 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, + 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, + 0xad76dbdb, 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, 0x0c0a0606, 0x486c2424, 0xb8e45c5c, + 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, 0xd532e7e7, + 0x8b43c8c8, 0x6e593737, 0xdab76d6d, 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, 0xacfa5656, + 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, + 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, + 0x96dd4b4b, 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, 0x90d84848, + 0x06050303, 0xf701f6f6, 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, 0x17918686, 0x9958c1c1, + 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, 0x07898e8e, + 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, + 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616 }; + + private static final int[] Tinv0 = { 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 0xf1459d1f, + 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, + 0xe75f8f03, 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e, 0x6a89c275, + 0x78798ef4, 0x6b3e5899, 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 0x184adf63, 0x82311ae5, + 0x60335197, 0x457f5362, 0xe07764b1, 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, 0x876cde94, + 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, + 0xd5be0506, 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4, 0x39ec830b, 0xaaef6040, + 0x069f715e, 0x51106ebd, 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, 0x055dc471, 0x6fd40604, + 0xff155060, 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879, + 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, + 0xd296eeb4, 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, + 0x1d171b12, 0x0b0d090e, 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3, + 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 0xcadc31d7, + 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, + 0xef903322, 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, + 0xe49d3a2c, 0x0d927850, 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382, 0xbe805d9f, + 0x7c93d069, 0xa92dd56f, 0xb31225cf, 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, 0xf418596e, + 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, + 0x4a9804f1, 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4, 0xe3b5d19e, + 0x1b886a4c, 0xb81f2cc1, 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, 0x5a1d67b3, 0x52d2db92, + 0x335610e9, 0x1347d66d, 0x8c61d79a, 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, 0xede51ce1, + 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, + 0xdeb30c08, 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0 }; + + private static final int[] Tinv1 = { 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, 0x459d1ff1, + 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, + 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, + 0x5f8f03e7, 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, 0x69e04929, 0xc8c98e44, 0x89c2756a, + 0x798ef478, 0x3e58996b, 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, 0x4adf6318, 0x311ae582, + 0x33519760, 0x7f536245, 0x7764b1e0, 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, 0x6cde9487, + 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, + 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, + 0xbe0506d5, 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, 0xebf6a475, 0xec830b39, 0xef6040aa, + 0x9f715e06, 0x106ebd51, 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, 0x5dc47105, 0xd406046f, + 0x155060ff, 0xfb981924, 0xe9bdd697, 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, 0xeec879db, + 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, + 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, + 0x96eeb4d2, 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, + 0x171b121d, 0x0d090e0b, 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, 0xdd99eebb, 0x607fa3fd, + 0x2601f79f, 0xf5725cbc, 0x3b6644c5, 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, 0xdc31d7ca, + 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, + 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, + 0x903322ef, 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, + 0x9d3a2ce4, 0x9278500d, 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, 0xafc382f5, 0x805d9fbe, + 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, 0x18596ef4, + 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, + 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, + 0x9804f14a, 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, 0x4daacc54, 0x0496e4df, 0xb5d19ee3, + 0x886a4c1b, 0x1f2cc1b8, 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, 0x1d67b35a, 0xd2db9252, + 0x5610e933, 0x47d66d13, 0x61d79a8c, 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, 0xe51ce1ed, + 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, + 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, + 0xb30c08de, 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, 0x57b8d042 }; + + private static final int[] Tinv2 = { 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, 0x9d1ff145, + 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, + 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, + 0x8f03e75f, 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, 0xe0492969, 0xc98e44c8, 0xc2756a89, + 0x8ef47879, 0x58996b3e, 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, 0xdf63184a, 0x1ae58231, + 0x51976033, 0x5362457f, 0x64b1e077, 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, 0xde94876c, + 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, + 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, + 0x0506d5be, 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, 0xf6a475eb, 0x830b39ec, 0x6040aaef, + 0x715e069f, 0x6ebd5110, 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, 0xc471055d, 0x06046fd4, + 0x5060ff15, 0x981924fb, 0xbdd697e9, 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, 0xc879dbee, + 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, + 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, + 0xeeb4d296, 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, 0x93e20aba, 0xa0c0e52a, 0x223c43e0, + 0x1b121d17, 0x090e0b0d, 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, 0x99eebbdd, 0x7fa3fd60, + 0x01f79f26, 0x725cbcf5, 0x6644c53b, 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, 0x31d7cadc, + 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, + 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, + 0x3322ef90, 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, 0x7aa528de, 0xb7da268e, 0xad3fa4bf, + 0x3a2ce49d, 0x78500d92, 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, 0xc382f5af, 0x5d9fbe80, + 0xd0697c93, 0xd56fa92d, 0x25cfb312, 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, 0x596ef418, + 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, + 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, + 0x04f14a98, 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, 0xaacc544d, 0x96e4df04, 0xd19ee3b5, + 0x6a4c1b88, 0x2cc1b81f, 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, 0x67b35a1d, 0xdb9252d2, + 0x10e93356, 0xd66d1347, 0xd79a8c61, 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, 0x1ce1ede5, + 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, + 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, + 0x0c08deb3, 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, 0xb8d04257 }; + + private static final int[] Tinv3 = { 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, 0x1ff1459d, + 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, + 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, + 0x03e75f8f, 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, 0x492969e0, 0x8e44c8c9, 0x756a89c2, + 0xf478798e, 0x996b3e58, 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, 0x63184adf, 0xe582311a, + 0x97603351, 0x62457f53, 0xb1e07764, 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, 0x94876cde, + 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, + 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, + 0x06d5be05, 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, 0xa475ebf6, 0x0b39ec83, 0x40aaef60, + 0x5e069f71, 0xbd51106e, 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, 0x71055dc4, 0x046fd406, + 0x60ff1550, 0x1924fb98, 0xd697e9bd, 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, 0x79dbeec8, + 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, + 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, + 0xb4d296ee, 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, 0xe20aba93, 0xc0e52aa0, 0x3c43e022, + 0x121d171b, 0x0e0b0d09, 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, 0xeebbdd99, 0xa3fd607f, + 0xf79f2601, 0x5cbcf572, 0x44c53b66, 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, 0xd7cadc31, + 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, + 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, + 0x22ef9033, 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, 0xa528de7a, 0xda268eb7, 0x3fa4bfad, + 0x2ce49d3a, 0x500d9278, 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, 0x82f5afc3, 0x9fbe805d, + 0x697c93d0, 0x6fa92dd5, 0xcfb31225, 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, 0x6ef41859, + 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, + 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, + 0xf14a9804, 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, + 0x4c1b886a, 0xc1b81f2c, 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, 0xb35a1d67, 0x9252d2db, + 0xe9335610, 0x6d1347d6, 0x9a8c61d7, 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, 0xe1ede51c, + 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, + 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, + 0x08deb30c, 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, 0xd04257b8 }; + + private final int shift(int r, int shift) + { + return (((r >>> shift) | (r << (32 - shift)))); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private final int FFmulX(int x) + { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + * The following defines provide alternative definitions of FFmulX that + * might give improved performance if a fast 32-bit multiply is not + * available. + * + * private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & + * m2) < < 1) ^ ((u >>> 3) | (u >>> 6)); } private static final int m4 = + * 0x1b1b1b1b; private int FFmulX(int x) { int u = x & m1; return ((x & m2) < < + * 1) ^ ((u - (u >>> 7)) & m4); } + * + */ + + private final int inv_mcol(int x) + { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + private final int subWord(int x) + { + return (S[x & 255] & 255 | ((S[(x >> 8) & 255] & 255) << 8) | ((S[(x >> 16) & 255] & 255) << 16) | S[(x >> 24) & 255] << 24); + } + + /** + * Calculate the necessary round keys The number of calculations depends on + * key size and block size AES specified a fixed block size of 128 bits and + * key sizes 128/192/256 bits This code is written assuming those are the + * only possible values + */ + private final int[][] generateWorkingKey(byte[] key, boolean forEncryption) + { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) + { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized + // Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS + 1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + for (int i = 0; i < key.length; t++) + { + W[t >> 2][t & 3] = (key[i] & 0xff) | ((key[i + 1] & 0xff) << 8) | ((key[i + 2] & 0xff) << 16) + | (key[i + 3] << 24); + i += 4; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (int i = KC; (i < k); i++) + { + int temp = W[(i - 1) >> 2][(i - 1) & 3]; + if ((i % KC) == 0) + { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; + } + else if ((KC > 6) && ((i % KC) == 4)) + { + temp = subWord(temp); + } + + W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + for (int i = 0; i < 4; i++) + { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean doEncrypt; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AES() + { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption + * whether or not we are for encryption. + * @param key + * the key required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + + public final void init(boolean forEncryption, byte[] key) + { + WorkingKey = generateWorkingKey(key, forEncryption); + this.doEncrypt = forEncryption; + } + + public final String getAlgorithmName() + { + return "AES"; + } + + public final int getBlockSize() + { + return BLOCK_SIZE; + } + + public final int processBlock(byte[] in, int inOff, byte[] out, int outOff) + { + if (WorkingKey == null) + { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) + { + throw new IllegalArgumentException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) + { + throw new IllegalArgumentException("output buffer too short"); + } + + if (doEncrypt) + { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public final void reset() + { + } + + private final void unpackBlock(byte[] bytes, int off) + { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private final void packBlock(byte[] bytes, int off) + { + int index = off; + + bytes[index++] = (byte) C0; + bytes[index++] = (byte) (C0 >> 8); + bytes[index++] = (byte) (C0 >> 16); + bytes[index++] = (byte) (C0 >> 24); + + bytes[index++] = (byte) C1; + bytes[index++] = (byte) (C1 >> 8); + bytes[index++] = (byte) (C1 >> 16); + bytes[index++] = (byte) (C1 >> 24); + + bytes[index++] = (byte) C2; + bytes[index++] = (byte) (C2 >> 8); + bytes[index++] = (byte) (C2 >> 16); + bytes[index++] = (byte) (C2 >> 24); + + bytes[index++] = (byte) C3; + bytes[index++] = (byte) (C3 >> 8); + bytes[index++] = (byte) (C3 >> 16); + bytes[index++] = (byte) (C3 >> 24); + } + + private final void encryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + for (r = 1; r < ROUNDS - 1;) + { + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + C0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[(r3 >> 24) & 255] ^ KW[r][0]; + C1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[(r0 >> 24) & 255] ^ KW[r][1]; + C2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[(r1 >> 24) & 255] ^ KW[r][2]; + C3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[(r2 >> 24) & 255] ^ KW[r++][3]; + } + + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + + // the final round's table is a simple function of S so we don't use a + // whole other four tables for it + + C0 = (S[r0 & 255] & 255) ^ ((S[(r1 >> 8) & 255] & 255) << 8) ^ ((S[(r2 >> 16) & 255] & 255) << 16) + ^ (S[(r3 >> 24) & 255] << 24) ^ KW[r][0]; + C1 = (S[r1 & 255] & 255) ^ ((S[(r2 >> 8) & 255] & 255) << 8) ^ ((S[(r3 >> 16) & 255] & 255) << 16) + ^ (S[(r0 >> 24) & 255] << 24) ^ KW[r][1]; + C2 = (S[r2 & 255] & 255) ^ ((S[(r3 >> 8) & 255] & 255) << 8) ^ ((S[(r0 >> 16) & 255] & 255) << 16) + ^ (S[(r1 >> 24) & 255] << 24) ^ KW[r][2]; + C3 = (S[r3 & 255] & 255) ^ ((S[(r0 >> 8) & 255] & 255) << 8) ^ ((S[(r1 >> 16) & 255] & 255) << 16) + ^ (S[(r2 >> 24) & 255] << 24) ^ KW[r][3]; + + } + + private final void decryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + for (r = ROUNDS - 1; r > 1;) + { + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] + ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] + ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] + ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] + ^ KW[r--][3]; + C0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[(r1 >> 24) & 255] + ^ KW[r][0]; + C1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[(r2 >> 24) & 255] + ^ KW[r][1]; + C2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[(r3 >> 24) & 255] + ^ KW[r][2]; + C3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[(r0 >> 24) & 255] + ^ KW[r--][3]; + } + + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] ^ KW[r--][3]; + + // the final round's table is a simple function of Si so we don't use a + // whole other four tables for it + + C0 = (Si[r0 & 255] & 255) ^ ((Si[(r3 >> 8) & 255] & 255) << 8) ^ ((Si[(r2 >> 16) & 255] & 255) << 16) + ^ (Si[(r1 >> 24) & 255] << 24) ^ KW[0][0]; + C1 = (Si[r1 & 255] & 255) ^ ((Si[(r0 >> 8) & 255] & 255) << 8) ^ ((Si[(r3 >> 16) & 255] & 255) << 16) + ^ (Si[(r2 >> 24) & 255] << 24) ^ KW[0][1]; + C2 = (Si[r2 & 255] & 255) ^ ((Si[(r1 >> 8) & 255] & 255) << 8) ^ ((Si[(r0 >> 16) & 255] & 255) << 16) + ^ (Si[(r3 >> 24) & 255] << 24) ^ KW[0][2]; + C3 = (Si[r3 & 255] & 255) ^ ((Si[(r2 >> 8) & 255] & 255) << 8) ^ ((Si[(r1 >> 16) & 255] & 255) << 16) + ^ (Si[(r0 >> 24) & 255] << 24) ^ KW[0][3]; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) + { + processBlock(src, srcoff, dst, dstoff); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipher.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipher.java new file mode 100644 index 0000000..4cc28ab --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipher.java @@ -0,0 +1,16 @@ +package com.trilead.ssh2.crypto.cipher; + +/** + * BlockCipher. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: BlockCipher.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public interface BlockCipher +{ + public void init(boolean forEncryption, byte[] key); + + public int getBlockSize(); + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff); +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipherFactory.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipherFactory.java new file mode 100644 index 0000000..6e386a5 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipherFactory.java @@ -0,0 +1,115 @@ + +package com.trilead.ssh2.crypto.cipher; + +import java.util.Vector; + +/** + * BlockCipherFactory. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: BlockCipherFactory.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class BlockCipherFactory +{ + static class CipherEntry + { + String type; + int blocksize; + int keysize; + String cipherClass; + + public CipherEntry(String type, int blockSize, int keySize, String cipherClass) + { + this.type = type; + this.blocksize = blockSize; + this.keysize = keySize; + this.cipherClass = cipherClass; + } + } + + static Vector<CipherEntry> ciphers = new Vector<CipherEntry>(); + + static + { + /* Higher Priority First */ + + ciphers.addElement(new CipherEntry("aes256-ctr", 16, 32, "com.trilead.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("aes192-ctr", 16, 24, "com.trilead.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("aes128-ctr", 16, 16, "com.trilead.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("blowfish-ctr", 8, 16, "com.trilead.ssh2.crypto.cipher.BlowFish")); + + ciphers.addElement(new CipherEntry("aes256-cbc", 16, 32, "com.trilead.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("aes192-cbc", 16, 24, "com.trilead.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("aes128-cbc", 16, 16, "com.trilead.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("blowfish-cbc", 8, 16, "com.trilead.ssh2.crypto.cipher.BlowFish")); + + ciphers.addElement(new CipherEntry("3des-ctr", 8, 24, "com.trilead.ssh2.crypto.cipher.DESede")); + ciphers.addElement(new CipherEntry("3des-cbc", 8, 24, "com.trilead.ssh2.crypto.cipher.DESede")); + } + + public static String[] getDefaultCipherList() + { + String list[] = new String[ciphers.size()]; + for (int i = 0; i < ciphers.size(); i++) + { + CipherEntry ce = ciphers.elementAt(i); + list[i] = new String(ce.type); + } + return list; + } + + public static void checkCipherList(String[] cipherCandidates) + { + for (int i = 0; i < cipherCandidates.length; i++) + getEntry(cipherCandidates[i]); + } + + public static BlockCipher createCipher(String type, boolean encrypt, byte[] key, byte[] iv) + { + try + { + CipherEntry ce = getEntry(type); + Class cc = Class.forName(ce.cipherClass); + BlockCipher bc = (BlockCipher) cc.newInstance(); + + if (type.endsWith("-cbc")) + { + bc.init(encrypt, key); + return new CBCMode(bc, iv, encrypt); + } + else if (type.endsWith("-ctr")) + { + bc.init(true, key); + return new CTRMode(bc, iv, encrypt); + } + throw new IllegalArgumentException("Cannot instantiate " + type); + } + catch (Exception e) + { + throw new IllegalArgumentException("Cannot instantiate " + type); + } + } + + private static CipherEntry getEntry(String type) + { + for (int i = 0; i < ciphers.size(); i++) + { + CipherEntry ce = ciphers.elementAt(i); + if (ce.type.equals(type)) + return ce; + } + throw new IllegalArgumentException("Unkown algorithm " + type); + } + + public static int getBlockSize(String type) + { + CipherEntry ce = getEntry(type); + return ce.blocksize; + } + + public static int getKeySize(String type) + { + CipherEntry ce = getEntry(type); + return ce.keysize; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlowFish.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlowFish.java new file mode 100644 index 0000000..0b2f295 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/BlowFish.java @@ -0,0 +1,403 @@ + +package com.trilead.ssh2.crypto.cipher; + +/* + This file was shamelessly taken from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * A class that provides Blowfish key encryption operations, such as encoding + * data and generating keys. All the algorithms herein are from Applied + * Cryptography and implement a simplified cryptography interface. + * + * @author See comments in the source file + * @version $Id: BlowFish.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class BlowFish implements BlockCipher +{ + + private final static int[] KP = { 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, + 0xB5470917, 0x9216D5D9, 0x8979FB1B }, + + KS0 = { 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, + 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, + 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, + 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, + 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, + 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, + 0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, + 0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, + 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, + 0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, + 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, + 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, + 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, + 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, + 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, + 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, + 0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, + 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, + 0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, + 0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, + 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, + 0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A }, + + KS1 = { 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, + 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, + 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6, + 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, + 0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, + 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, + 0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718, + 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, + 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6, + 0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6, + 0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, + 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1, + 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90, + 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, + 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E, + 0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, + 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, + 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A, + 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092, + 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, + 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705, + 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 }, + + KS2 = { 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471, + 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, + 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6, + 0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35, + 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, + 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7, + 0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, + 0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, + 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62, + 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, + 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, + 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF, + 0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, + 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, + 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187, + 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E, + 0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, + 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, + 0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7, + 0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, + 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3, + 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 }, + + KS3 = { 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D, + 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, + 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, + 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, + 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, + 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, + 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15, + 0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, + 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862, + 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, + 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, + 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671, + 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1, + 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, + 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, + 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532, + 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, + 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5, + 0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, + 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, + 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, + 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 }; + + // ==================================== + // Useful constants + // ==================================== + + private static final int ROUNDS = 16; + private static final int BLOCK_SIZE = 8; // bytes = 64 bits + private static final int SBOX_SK = 256; + private static final int P_SZ = ROUNDS + 2; + + private final int[] S0, S1, S2, S3; // the s-boxes + private final int[] P; // the p-array + + private boolean doEncrypt = false; + + private byte[] workingKey = null; + + public BlowFish() + { + S0 = new int[SBOX_SK]; + S1 = new int[SBOX_SK]; + S2 = new int[SBOX_SK]; + S3 = new int[SBOX_SK]; + P = new int[P_SZ]; + } + + /** + * initialise a Blowfish cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the key required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) + { + this.doEncrypt = encrypting; + this.workingKey = key; + setKey(this.workingKey); + } + + public String getAlgorithmName() + { + return "Blowfish"; + } + + public final void transformBlock(byte[] in, int inOff, byte[] out, int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("Blowfish not initialised"); + } + + if (doEncrypt) + { + encryptBlock(in, inOff, out, outOff); + } + else + { + decryptBlock(in, inOff, out, outOff); + } + } + + public void reset() + { + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + // ================================== + // Private Implementation + // ================================== + + private int F(int x) + { + return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); + } + + /** + * apply the encryption cycle to each value pair in the table. + */ + private void processTable(int xl, int xr, int[] table) + { + int size = table.length; + + for (int s = 0; s < size; s += 2) + { + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + + table[s] = xr; + table[s + 1] = xl; + + xr = xl; // end of cycle swap + xl = table[s]; + } + } + + private void setKey(byte[] key) + { + /* + * - comments are from _Applied Crypto_, Schneier, p338 please be + * careful comparing the two, AC numbers the arrays from 1, the enclosed + * code from 0. + * + * (1) Initialise the S-boxes and the P-array, with a fixed string This + * string contains the hexadecimal digits of pi (3.141...) + */ + System.arraycopy(KS0, 0, S0, 0, SBOX_SK); + System.arraycopy(KS1, 0, S1, 0, SBOX_SK); + System.arraycopy(KS2, 0, S2, 0, SBOX_SK); + System.arraycopy(KS3, 0, S3, 0, SBOX_SK); + + System.arraycopy(KP, 0, P, 0, P_SZ); + + /* + * (2) Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with + * the second 32-bits of the key, and so on for all bits of the key (up + * to P[17]). Repeatedly cycle through the key bits until the entire + * P-array has been XOR-ed with the key bits + */ + int keyLength = key.length; + int keyIndex = 0; + + for (int i = 0; i < P_SZ; i++) + { + // get the 32 bits of the key, in 4 * 8 bit chunks + int data = 0x0000000; + for (int j = 0; j < 4; j++) + { + // create a 32 bit block + data = (data << 8) | (key[keyIndex++] & 0xff); + + // wrap when we get to the end of the key + if (keyIndex >= keyLength) + { + keyIndex = 0; + } + } + // XOR the newly created 32 bit chunk onto the P-array + P[i] ^= data; + } + + /* + * (3) Encrypt the all-zero string with the Blowfish algorithm, using + * the subkeys described in (1) and (2) + * + * (4) Replace P1 and P2 with the output of step (3) + * + * (5) Encrypt the output of step(3) using the Blowfish algorithm, with + * the modified subkeys. + * + * (6) Replace P3 and P4 with the output of step (5) + * + * (7) Continue the process, replacing all elements of the P-array and + * then all four S-boxes in order, with the output of the continuously + * changing Blowfish algorithm + */ + + processTable(0, 0, P); + processTable(P[P_SZ - 2], P[P_SZ - 1], S0); + processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); + processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); + processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); + } + + /** + * Encrypt the given input starting at the given offset and place the result + * in the provided buffer starting at the given offset. The input will be an + * exact multiple of our blocksize. + */ + private void encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) + { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + /** + * Decrypt the given input starting at the given offset and place the result + * in the provided buffer starting at the given offset. The input will be an + * exact multiple of our blocksize. + */ + private void decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) + { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + + xl ^= P[ROUNDS + 1]; + + for (int i = ROUNDS; i > 0; i -= 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i - 1]; + } + + xr ^= P[0]; + + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + private int BytesTo32bits(byte[] b, int i) + { + return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff)); + } + + private void Bits32ToBytes(int in, byte[] b, int offset) + { + b[offset + 3] = (byte) in; + b[offset + 2] = (byte) (in >> 8); + b[offset + 1] = (byte) (in >> 16); + b[offset] = (byte) (in >> 24); + } + +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/CBCMode.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/CBCMode.java new file mode 100644 index 0000000..0ae51b3 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/CBCMode.java @@ -0,0 +1,78 @@ +package com.trilead.ssh2.crypto.cipher; + +/** + * CBCMode. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: CBCMode.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class CBCMode implements BlockCipher +{ + BlockCipher tc; + int blockSize; + boolean doEncrypt; + + byte[] cbc_vector; + byte[] tmp_vector; + + public void init(boolean forEncryption, byte[] key) + { + } + + public CBCMode(BlockCipher tc, byte[] iv, boolean doEncrypt) + throws IllegalArgumentException + { + this.tc = tc; + this.blockSize = tc.getBlockSize(); + this.doEncrypt = doEncrypt; + + if (this.blockSize != iv.length) + throw new IllegalArgumentException("IV must be " + blockSize + + " bytes long! (currently " + iv.length + ")"); + + this.cbc_vector = new byte[blockSize]; + this.tmp_vector = new byte[blockSize]; + System.arraycopy(iv, 0, cbc_vector, 0, blockSize); + } + + public int getBlockSize() + { + return blockSize; + } + + private void encryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) + { + for (int i = 0; i < blockSize; i++) + cbc_vector[i] ^= src[srcoff + i]; + + tc.transformBlock(cbc_vector, 0, dst, dstoff); + + System.arraycopy(dst, dstoff, cbc_vector, 0, blockSize); + } + + private void decryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) + { + /* Assume the worst, src and dst are overlapping... */ + + System.arraycopy(src, srcoff, tmp_vector, 0, blockSize); + + tc.transformBlock(src, srcoff, dst, dstoff); + + for (int i = 0; i < blockSize; i++) + dst[dstoff + i] ^= cbc_vector[i]; + + /* ...that is why we need a tmp buffer. */ + + byte[] swap = cbc_vector; + cbc_vector = tmp_vector; + tmp_vector = swap; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) + { + if (doEncrypt) + encryptBlock(src, srcoff, dst, dstoff); + else + decryptBlock(src, srcoff, dst, dstoff); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/CTRMode.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/CTRMode.java new file mode 100644 index 0000000..8541c8d --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/CTRMode.java @@ -0,0 +1,62 @@ + +package com.trilead.ssh2.crypto.cipher; + +/** + * This is CTR mode as described in draft-ietf-secsh-newmodes-XY.txt + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: CTRMode.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class CTRMode implements BlockCipher +{ + byte[] X; + byte[] Xenc; + + BlockCipher bc; + int blockSize; + boolean doEncrypt; + + int count = 0; + + public void init(boolean forEncryption, byte[] key) + { + } + + public CTRMode(BlockCipher tc, byte[] iv, boolean doEnc) throws IllegalArgumentException + { + bc = tc; + blockSize = bc.getBlockSize(); + doEncrypt = doEnc; + + if (blockSize != iv.length) + throw new IllegalArgumentException("IV must be " + blockSize + " bytes long! (currently " + iv.length + ")"); + + X = new byte[blockSize]; + Xenc = new byte[blockSize]; + + System.arraycopy(iv, 0, X, 0, blockSize); + } + + public final int getBlockSize() + { + return blockSize; + } + + public final void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) + { + bc.transformBlock(X, 0, Xenc, 0); + + for (int i = 0; i < blockSize; i++) + { + dst[dstoff + i] = (byte) (src[srcoff + i] ^ Xenc[i]); + } + + for (int i = (blockSize - 1); i >= 0; i--) + { + X[i]++; + if (X[i] != 0) + break; + + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/CipherInputStream.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/CipherInputStream.java new file mode 100644 index 0000000..c9055ab --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/CipherInputStream.java @@ -0,0 +1,144 @@ + +package com.trilead.ssh2.crypto.cipher; + +import java.io.IOException; +import java.io.InputStream; + +/** + * CipherInputStream. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: CipherInputStream.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class CipherInputStream +{ + BlockCipher currentCipher; + InputStream bi; + byte[] buffer; + byte[] enc; + int blockSize; + int pos; + + /* + * We cannot use java.io.BufferedInputStream, since that is not available in + * J2ME. Everything could be improved alot here. + */ + + final int BUFF_SIZE = 2048; + byte[] input_buffer = new byte[BUFF_SIZE]; + int input_buffer_pos = 0; + int input_buffer_size = 0; + + public CipherInputStream(BlockCipher tc, InputStream bi) + { + this.bi = bi; + changeCipher(tc); + } + + private int fill_buffer() throws IOException + { + input_buffer_pos = 0; + input_buffer_size = bi.read(input_buffer, 0, BUFF_SIZE); + return input_buffer_size; + } + + private int internal_read(byte[] b, int off, int len) throws IOException + { + if (input_buffer_size < 0) + return -1; + + if (input_buffer_pos >= input_buffer_size) + { + if (fill_buffer() <= 0) + return -1; + } + + int avail = input_buffer_size - input_buffer_pos; + int thiscopy = (len > avail) ? avail : len; + + System.arraycopy(input_buffer, input_buffer_pos, b, off, thiscopy); + input_buffer_pos += thiscopy; + + return thiscopy; + } + + public void changeCipher(BlockCipher bc) + { + this.currentCipher = bc; + blockSize = bc.getBlockSize(); + buffer = new byte[blockSize]; + enc = new byte[blockSize]; + pos = blockSize; + } + + private void getBlock() throws IOException + { + int n = 0; + while (n < blockSize) + { + int len = internal_read(enc, n, blockSize - n); + if (len < 0) + throw new IOException("Cannot read full block, EOF reached."); + n += len; + } + + try + { + currentCipher.transformBlock(enc, 0, buffer, 0); + } + catch (Exception e) + { + throw new IOException("Error while decrypting block."); + } + pos = 0; + } + + public int read(byte[] dst) throws IOException + { + return read(dst, 0, dst.length); + } + + public int read(byte[] dst, int off, int len) throws IOException + { + int count = 0; + + while (len > 0) + { + if (pos >= blockSize) + getBlock(); + + int avail = blockSize - pos; + int copy = Math.min(avail, len); + System.arraycopy(buffer, pos, dst, off, copy); + pos += copy; + off += copy; + len -= copy; + count += copy; + } + return count; + } + + public int read() throws IOException + { + if (pos >= blockSize) + { + getBlock(); + } + return buffer[pos++] & 0xff; + } + + public int readPlain(byte[] b, int off, int len) throws IOException + { + if (pos != blockSize) + throw new IOException("Cannot read plain since crypto buffer is not aligned."); + int n = 0; + while (n < len) + { + int cnt = internal_read(b, off + n, len - n); + if (cnt < 0) + throw new IOException("Cannot fill buffer, EOF reached."); + n += cnt; + } + return n; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/CipherOutputStream.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/CipherOutputStream.java new file mode 100644 index 0000000..cf0db4a --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/CipherOutputStream.java @@ -0,0 +1,142 @@ + +package com.trilead.ssh2.crypto.cipher; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * CipherOutputStream. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: CipherOutputStream.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class CipherOutputStream +{ + BlockCipher currentCipher; + OutputStream bo; + byte[] buffer; + byte[] enc; + int blockSize; + int pos; + + /* + * We cannot use java.io.BufferedOutputStream, since that is not available + * in J2ME. Everything could be improved here alot. + */ + + final int BUFF_SIZE = 2048; + byte[] out_buffer = new byte[BUFF_SIZE]; + int out_buffer_pos = 0; + + public CipherOutputStream(BlockCipher tc, OutputStream bo) + { + this.bo = bo; + changeCipher(tc); + } + + private void internal_write(byte[] src, int off, int len) throws IOException + { + while (len > 0) + { + int space = BUFF_SIZE - out_buffer_pos; + int copy = (len > space) ? space : len; + + System.arraycopy(src, off, out_buffer, out_buffer_pos, copy); + + off += copy; + out_buffer_pos += copy; + len -= copy; + + if (out_buffer_pos >= BUFF_SIZE) + { + bo.write(out_buffer, 0, BUFF_SIZE); + out_buffer_pos = 0; + } + } + } + + private void internal_write(int b) throws IOException + { + out_buffer[out_buffer_pos++] = (byte) b; + if (out_buffer_pos >= BUFF_SIZE) + { + bo.write(out_buffer, 0, BUFF_SIZE); + out_buffer_pos = 0; + } + } + + public void flush() throws IOException + { + if (pos != 0) + throw new IOException("FATAL: cannot flush since crypto buffer is not aligned."); + + if (out_buffer_pos > 0) + { + bo.write(out_buffer, 0, out_buffer_pos); + out_buffer_pos = 0; + } + bo.flush(); + } + + public void changeCipher(BlockCipher bc) + { + this.currentCipher = bc; + blockSize = bc.getBlockSize(); + buffer = new byte[blockSize]; + enc = new byte[blockSize]; + pos = 0; + } + + private void writeBlock() throws IOException + { + try + { + currentCipher.transformBlock(buffer, 0, enc, 0); + } + catch (Exception e) + { + throw (IOException) new IOException("Error while decrypting block.").initCause(e); + } + + internal_write(enc, 0, blockSize); + pos = 0; + } + + public void write(byte[] src, int off, int len) throws IOException + { + while (len > 0) + { + int avail = blockSize - pos; + int copy = Math.min(avail, len); + + System.arraycopy(src, off, buffer, pos, copy); + pos += copy; + off += copy; + len -= copy; + + if (pos >= blockSize) + writeBlock(); + } + } + + public void write(int b) throws IOException + { + buffer[pos++] = (byte) b; + if (pos >= blockSize) + writeBlock(); + } + + public void writePlain(int b) throws IOException + { + if (pos != 0) + throw new IOException("Cannot write plain since crypto buffer is not aligned."); + internal_write(b); + } + + public void writePlain(byte[] b, int off, int len) throws IOException + { + if (pos != 0) + throw new IOException("Cannot write plain since crypto buffer is not aligned."); + internal_write(b, off, len); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/DES.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/DES.java new file mode 100644 index 0000000..6588459 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/DES.java @@ -0,0 +1,373 @@ + +package com.trilead.ssh2.crypto.cipher; + +/* + This file is based on the 3DES implementation from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * DES. + * + * @author See comments in the source file + * @version $Id: DES.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class DES implements BlockCipher +{ + private int[] workingKey = null; + + /** + * standard constructor. + */ + public DES() + { + } + + /** + * initialise a DES cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the parameters required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) + { + this.workingKey = generateWorkingKey(encrypting, key, 0); + } + + public String getAlgorithmName() + { + return "DES"; + } + + public int getBlockSize() + { + return 8; + } + + public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("DES engine not initialised!"); + } + + desFunc(workingKey, in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * what follows is mainly taken from "Applied Cryptography", by Bruce + * Schneier, however it also bears great resemblance to Richard + * Outerbridge's D3DES... + */ + + static short[] Df_Key = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, + 0x10, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67 }; + + static short[] bytebit = { 0200, 0100, 040, 020, 010, 04, 02, 01 }; + + static int[] bigbyte = { 0x800000, 0x400000, 0x200000, 0x100000, 0x80000, 0x40000, 0x20000, 0x10000, 0x8000, + 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 }; + + /* + * Use the key schedule specified in the Standard (ANSI X3.92-1981). + */ + + static byte[] pc1 = { 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, + 4, 27, 19, 11, 3 }; + + static byte[] totrot = { 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 }; + + static byte[] pc2 = { 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, + 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 }; + + static int[] SP1 = { 0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004, + 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, 0x00010400, 0x01010000, 0x01010000, 0x01000404, 0x00010004, + 0x01000004, 0x01000004, 0x00010004, 0x00000000, 0x00000404, 0x00010404, 0x01000000, 0x00010000, 0x01010404, + 0x00000004, 0x01010000, 0x01010400, 0x01000000, 0x01000000, 0x00000400, 0x01010004, 0x00010000, 0x00010400, + 0x01000004, 0x00000400, 0x00000004, 0x01000404, 0x00010404, 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, 0x00000404, 0x01000400, 0x01000400, 0x00000000, 0x00010004, + 0x00010400, 0x00000000, 0x01010004 }; + + static int[] SP2 = { 0x80108020, 0x80008000, 0x00008000, 0x00108020, 0x00100000, 0x00000020, 0x80100020, + 0x80008020, 0x80000020, 0x80108020, 0x80108000, 0x80000000, 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, 0x80000000, 0x00008000, 0x00108020, 0x80100000, 0x00100020, + 0x80000020, 0x00000000, 0x00108000, 0x00008020, 0x80108000, 0x80100000, 0x00008020, 0x00000000, 0x00108020, + 0x80100020, 0x00100000, 0x80008020, 0x80100000, 0x80108000, 0x00008000, 0x80100000, 0x80008000, 0x00000020, + 0x80108020, 0x00108020, 0x00000020, 0x00008000, 0x80000000, 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, 0x00108000, 0x00000000, 0x80008000, 0x00008020, 0x80000000, + 0x80100020, 0x80108020, 0x00108000 }; + + static int[] SP3 = { 0x00000208, 0x08020200, 0x00000000, 0x08020008, 0x08000200, 0x00000000, 0x00020208, + 0x08000200, 0x00020008, 0x08000008, 0x08000008, 0x00020000, 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, 0x00020200, 0x08020000, 0x08020008, 0x00020208, 0x08000208, + 0x00020200, 0x00020000, 0x08000208, 0x00000008, 0x08020208, 0x00000200, 0x08000000, 0x08020200, 0x08000000, + 0x00020008, 0x00000208, 0x00020000, 0x08020200, 0x08000200, 0x00000000, 0x00000200, 0x00020008, 0x08020208, + 0x08000200, 0x08000008, 0x00000200, 0x00000000, 0x08020008, 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, 0x08020000, 0x08000208, 0x00000208, 0x08020000, 0x00020208, + 0x00000008, 0x08020008, 0x00020200 }; + + static int[] SP4 = { 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802080, 0x00800081, 0x00800001, + 0x00002001, 0x00000000, 0x00802000, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002001, 0x00002080, 0x00800081, + 0x00000001, 0x00002080, 0x00800080, 0x00002000, 0x00802080, 0x00802081, 0x00000081, 0x00800080, 0x00800001, + 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00000000, 0x00802000, 0x00002080, 0x00800080, 0x00800081, + 0x00000001, 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, 0x00002001, 0x00002080, 0x00800000, 0x00802001, 0x00000080, + 0x00800000, 0x00002000, 0x00802080 }; + + static int[] SP5 = { 0x00000100, 0x02080100, 0x02080000, 0x42000100, 0x00080000, 0x00000100, 0x40000000, + 0x02080000, 0x40080100, 0x00080000, 0x02000100, 0x40080100, 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, 0x40000100, 0x42080100, 0x42080100, 0x02000100, 0x42080000, + 0x40000100, 0x00000000, 0x42000000, 0x02080100, 0x02000000, 0x42000000, 0x00080100, 0x00080000, 0x42000100, + 0x00000100, 0x02000000, 0x40000000, 0x02080000, 0x42000100, 0x40080100, 0x02000100, 0x40000000, 0x42080000, + 0x02080100, 0x40080100, 0x00000100, 0x02000000, 0x42080000, 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, 0x00080100, 0x02000100, 0x40000100, 0x00080000, 0x00000000, + 0x40080000, 0x02080100, 0x40000100 }; + + static int[] SP6 = { 0x20000010, 0x20400000, 0x00004000, 0x20404010, 0x20400000, 0x00000010, 0x20404010, + 0x00400000, 0x20004000, 0x00404010, 0x00400000, 0x20000010, 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, 0x00404000, 0x20004010, 0x00000010, 0x20400010, 0x20400010, + 0x00000000, 0x00404010, 0x20404000, 0x00004010, 0x00404000, 0x20404000, 0x20000000, 0x20004000, 0x00000010, + 0x20400010, 0x00404000, 0x20404010, 0x00400000, 0x00004010, 0x20000010, 0x00400000, 0x20004000, 0x20000000, + 0x00004010, 0x20000010, 0x20404010, 0x00404000, 0x20400000, 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, 0x00004000, 0x00400010, 0x20004010, 0x00000000, 0x20404000, + 0x20000000, 0x00400010, 0x20004010 }; + + static int[] SP7 = { 0x00200000, 0x04200002, 0x04000802, 0x00000000, 0x00000800, 0x04000802, 0x00200802, + 0x04200800, 0x04200802, 0x00200000, 0x00000000, 0x04000002, 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, 0x04000002, 0x04200000, 0x04200800, 0x00200002, 0x04200000, + 0x00000800, 0x00000802, 0x04200802, 0x00200800, 0x00000002, 0x04000000, 0x00200800, 0x04000000, 0x00200800, + 0x00200000, 0x04000802, 0x04000802, 0x04200002, 0x04200002, 0x00000002, 0x00200002, 0x04000000, 0x04000800, + 0x00200000, 0x04200800, 0x00000802, 0x00200802, 0x04200800, 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, 0x00000000, 0x00200802, 0x04200000, 0x00000800, 0x04000002, + 0x04000800, 0x00000800, 0x00200002 }; + + static int[] SP8 = { 0x10001040, 0x00001000, 0x00040000, 0x10041040, 0x10000000, 0x10001040, 0x00000040, + 0x10000000, 0x00040040, 0x10040000, 0x10041040, 0x00041000, 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, 0x00041000, 0x00040040, 0x10040040, 0x10041000, 0x00001040, + 0x00000000, 0x00000000, 0x10040040, 0x10000040, 0x10001000, 0x00041040, 0x00040000, 0x00041040, 0x00040000, + 0x10041000, 0x00001000, 0x00000040, 0x10040040, 0x00001000, 0x00041040, 0x10001000, 0x00000040, 0x10000040, + 0x10040000, 0x10040040, 0x10000000, 0x00040000, 0x10001040, 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, 0x10041040, 0x00041000, 0x00041000, 0x00001040, 0x00001040, + 0x00040040, 0x10000000, 0x10041000 }; + + /** + * generate an integer based working key based on our secret key and what we + * processing we are planning to do. + * + * Acknowledgements for this routine go to James Gillogly & Phil Karn. + * (whoever, and wherever they are!). + */ + protected int[] generateWorkingKey(boolean encrypting, byte[] key, int off) + { + int[] newKey = new int[32]; + boolean[] pc1m = new boolean[56], pcr = new boolean[56]; + + for (int j = 0; j < 56; j++) + { + int l = pc1[j]; + + pc1m[j] = ((key[off + (l >>> 3)] & bytebit[l & 07]) != 0); + } + + for (int i = 0; i < 16; i++) + { + int l, m, n; + + if (encrypting) + { + m = i << 1; + } + else + { + m = (15 - i) << 1; + } + + n = m + 1; + newKey[m] = newKey[n] = 0; + + for (int j = 0; j < 28; j++) + { + l = j + totrot[i]; + if (l < 28) + { + pcr[j] = pc1m[l]; + } + else + { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 28; j < 56; j++) + { + l = j + totrot[i]; + if (l < 56) + { + pcr[j] = pc1m[l]; + } + else + { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 0; j < 24; j++) + { + if (pcr[pc2[j]]) + { + newKey[m] |= bigbyte[j]; + } + + if (pcr[pc2[j + 24]]) + { + newKey[n] |= bigbyte[j]; + } + } + } + + // + // store the processed key + // + for (int i = 0; i != 32; i += 2) + { + int i1, i2; + + i1 = newKey[i]; + i2 = newKey[i + 1]; + + newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) | ((i2 & 0x00fc0000) >>> 10) + | ((i2 & 0x00000fc0) >>> 6); + + newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) | ((i2 & 0x0003f000) >>> 4) + | (i2 & 0x0000003f); + } + + return newKey; + } + + /** + * the DES engine. + */ + protected void desFunc(int[] wKey, byte[] in, int inOff, byte[] out, int outOff) + { + int work, right, left; + + left = (in[inOff + 0] & 0xff) << 24; + left |= (in[inOff + 1] & 0xff) << 16; + left |= (in[inOff + 2] & 0xff) << 8; + left |= (in[inOff + 3] & 0xff); + + right = (in[inOff + 4] & 0xff) << 24; + right |= (in[inOff + 5] & 0xff) << 16; + right |= (in[inOff + 6] & 0xff) << 8; + right |= (in[inOff + 7] & 0xff); + + work = ((left >>> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + left ^= (work << 4); + work = ((left >>> 16) ^ right) & 0x0000ffff; + right ^= work; + left ^= (work << 16); + work = ((right >>> 2) ^ left) & 0x33333333; + left ^= work; + right ^= (work << 2); + work = ((right >>> 8) ^ left) & 0x00ff00ff; + left ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff; + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff; + + for (int round = 0; round < 8; round++) + { + int fval; + + work = (right << 28) | (right >>> 4); + work ^= wKey[round * 4 + 0]; + fval = SP7[work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = right ^ wKey[round * 4 + 1]; + fval |= SP8[work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + left ^= fval; + work = (left << 28) | (left >>> 4); + work ^= wKey[round * 4 + 2]; + fval = SP7[work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = left ^ wKey[round * 4 + 3]; + fval |= SP8[work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + right ^= fval; + } + + right = (right << 31) | (right >>> 1); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 31) | (left >>> 1); + work = ((left >>> 8) ^ right) & 0x00ff00ff; + right ^= work; + left ^= (work << 8); + work = ((left >>> 2) ^ right) & 0x33333333; + right ^= work; + left ^= (work << 2); + work = ((right >>> 16) ^ left) & 0x0000ffff; + left ^= work; + right ^= (work << 16); + work = ((right >>> 4) ^ left) & 0x0f0f0f0f; + left ^= work; + right ^= (work << 4); + + out[outOff + 0] = (byte) ((right >>> 24) & 0xff); + out[outOff + 1] = (byte) ((right >>> 16) & 0xff); + out[outOff + 2] = (byte) ((right >>> 8) & 0xff); + out[outOff + 3] = (byte) (right & 0xff); + out[outOff + 4] = (byte) ((left >>> 24) & 0xff); + out[outOff + 5] = (byte) ((left >>> 16) & 0xff); + out[outOff + 6] = (byte) ((left >>> 8) & 0xff); + out[outOff + 7] = (byte) (left & 0xff); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/DESede.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/DESede.java new file mode 100644 index 0000000..f47a636 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/DESede.java @@ -0,0 +1,105 @@ + +package com.trilead.ssh2.crypto.cipher; + +/* + This file was shamelessly taken (and modified) from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * DESede. + * + * @author See comments in the source file + * @version $Id: DESede.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class DESede extends DES +{ + private int[] key1 = null; + private int[] key2 = null; + private int[] key3 = null; + + private boolean encrypt; + + /** + * standard constructor. + */ + public DESede() + { + } + + /** + * initialise a DES cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the parameters required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) + { + key1 = generateWorkingKey(encrypting, key, 0); + key2 = generateWorkingKey(!encrypting, key, 8); + key3 = generateWorkingKey(encrypting, key, 16); + + encrypt = encrypting; + } + + public String getAlgorithmName() + { + return "DESede"; + } + + public int getBlockSize() + { + return 8; + } + + public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) + { + if (key1 == null) + { + throw new IllegalStateException("DESede engine not initialised!"); + } + + if (encrypt) + { + desFunc(key1, in, inOff, out, outOff); + desFunc(key2, out, outOff, out, outOff); + desFunc(key3, out, outOff, out, outOff); + } + else + { + desFunc(key3, in, inOff, out, outOff); + desFunc(key2, out, outOff, out, outOff); + desFunc(key1, out, outOff, out, outOff); + } + } + + public void reset() + { + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/cipher/NullCipher.java b/app/src/main/java/com/trilead/ssh2/crypto/cipher/NullCipher.java new file mode 100644 index 0000000..38f8215 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/cipher/NullCipher.java @@ -0,0 +1,35 @@ +package com.trilead.ssh2.crypto.cipher; + +/** + * NullCipher. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: NullCipher.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class NullCipher implements BlockCipher +{ + private int blockSize = 8; + + public NullCipher() + { + } + + public NullCipher(int blockSize) + { + this.blockSize = blockSize; + } + + public void init(boolean forEncryption, byte[] key) + { + } + + public int getBlockSize() + { + return blockSize; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) + { + System.arraycopy(src, srcoff, dst, dstoff, blockSize); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/dh/DhExchange.java b/app/src/main/java/com/trilead/ssh2/crypto/dh/DhExchange.java new file mode 100644 index 0000000..3acde25 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/dh/DhExchange.java @@ -0,0 +1,132 @@ +/** + * + */ +package com.trilead.ssh2.crypto.dh; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.KeyAgreement; +import javax.crypto.interfaces.DHPrivateKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPublicKeySpec; + +/** + * @author kenny + * + */ +public class DhExchange extends GenericDhExchange { + + /* Given by the standard */ + + private static final BigInteger P1 = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + + "FFFFFFFFFFFFFFFF", 16); + + private static final BigInteger P14 = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); + + private static final BigInteger G = BigInteger.valueOf(2); + + /* Client public and private */ + + private DHPrivateKey clientPrivate; + private DHPublicKey clientPublic; + + /* Server public */ + + private DHPublicKey serverPublic; + + @Override + public void init(String name) throws IOException { + final DHParameterSpec spec; + if ("diffie-hellman-group1-sha1".equals(name)) { + spec = new DHParameterSpec(P1, G); + } else if ("diffie-hellman-group14-sha1".equals(name)) { + spec = new DHParameterSpec(P14, G); + } else { + throw new IllegalArgumentException("Unknown DH group " + name); + } + + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH"); + kpg.initialize(spec); + KeyPair pair = kpg.generateKeyPair(); + clientPrivate = (DHPrivateKey) pair.getPrivate(); + clientPublic = (DHPublicKey) pair.getPublic(); + } catch (NoSuchAlgorithmException e) { + throw (IOException) new IOException("No DH keypair generator").initCause(e); + } catch (InvalidAlgorithmParameterException e) { + throw (IOException) new IOException("Invalid DH parameters").initCause(e); + } + } + + @Override + public byte[] getE() { + if (clientPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + return clientPublic.getY().toByteArray(); + } + + @Override + protected byte[] getServerE() { + if (serverPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + return serverPublic.getY().toByteArray(); + } + + @Override + public void setF(byte[] f) throws IOException { + if (clientPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + final KeyAgreement ka; + try { + KeyFactory kf = KeyFactory.getInstance("DH"); + DHParameterSpec params = clientPublic.getParams(); + this.serverPublic = (DHPublicKey) kf.generatePublic(new DHPublicKeySpec( + new BigInteger(f), params.getP(), params.getG())); + + ka = KeyAgreement.getInstance("DH"); + ka.init(clientPrivate); + ka.doPhase(serverPublic, true); + } catch (NoSuchAlgorithmException e) { + throw (IOException) new IOException("No DH key agreement method").initCause(e); + } catch (InvalidKeyException e) { + throw (IOException) new IOException("Invalid DH key").initCause(e); + } catch (InvalidKeySpecException e) { + throw (IOException) new IOException("Invalid DH key").initCause(e); + } + + sharedSecret = new BigInteger(ka.generateSecret()); + } + + @Override + public String getHashAlgo() { + return "SHA1"; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/dh/DhGroupExchange.java b/app/src/main/java/com/trilead/ssh2/crypto/dh/DhGroupExchange.java new file mode 100644 index 0000000..a888950 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/dh/DhGroupExchange.java @@ -0,0 +1,113 @@ + +package com.trilead.ssh2.crypto.dh; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.trilead.ssh2.DHGexParameters; +import com.trilead.ssh2.crypto.digest.HashForSSH2Types; + + +/** + * DhGroupExchange. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DhGroupExchange.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class DhGroupExchange +{ + /* Given by the standard */ + + private BigInteger p; + private BigInteger g; + + /* Client public and private */ + + private BigInteger e; + private BigInteger x; + + /* Server public */ + + private BigInteger f; + + /* Shared secret */ + + private BigInteger k; + + public DhGroupExchange(BigInteger p, BigInteger g) + { + this.p = p; + this.g = g; + } + + public void init(SecureRandom rnd) + { + k = null; + + x = new BigInteger(p.bitLength() - 1, rnd); + e = g.modPow(x, p); + } + + /** + * @return Returns the e. + */ + public BigInteger getE() + { + if (e == null) + throw new IllegalStateException("Not initialized!"); + + return e; + } + + /** + * @return Returns the shared secret k. + */ + public BigInteger getK() + { + if (k == null) + throw new IllegalStateException("Shared secret not yet known, need f first!"); + + return k; + } + + /** + * Sets f and calculates the shared secret. + */ + public void setF(BigInteger f) + { + if (e == null) + throw new IllegalStateException("Not initialized!"); + + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) + throw new IllegalArgumentException("Invalid f specified!"); + + this.f = f; + this.k = f.modPow(x, p); + } + + public byte[] calculateH(String hashAlgo, byte[] clientversion, byte[] serverversion, + byte[] clientKexPayload, byte[] serverKexPayload, byte[] hostKey, DHGexParameters para) + { + HashForSSH2Types hash = new HashForSSH2Types(hashAlgo); + + hash.updateByteString(clientversion); + hash.updateByteString(serverversion); + hash.updateByteString(clientKexPayload); + hash.updateByteString(serverKexPayload); + hash.updateByteString(hostKey); + if (para.getMin_group_len() > 0) + hash.updateUINT32(para.getMin_group_len()); + hash.updateUINT32(para.getPref_group_len()); + if (para.getMax_group_len() > 0) + hash.updateUINT32(para.getMax_group_len()); + hash.updateBigInt(p); + hash.updateBigInt(g); + hash.updateBigInt(e); + hash.updateBigInt(f); + hash.updateBigInt(k); + + return hash.getDigest(); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/dh/EcDhExchange.java b/app/src/main/java/com/trilead/ssh2/crypto/dh/EcDhExchange.java new file mode 100644 index 0000000..43d31ad --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/dh/EcDhExchange.java @@ -0,0 +1,106 @@ +/** + * + */ +package com.trilead.ssh2.crypto.dh; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.KeyAgreement; + +import com.trilead.ssh2.signature.ECDSASHA2Verify; + +/** + * @author kenny + * + */ +public class EcDhExchange extends GenericDhExchange { + private ECPrivateKey clientPrivate; + private ECPublicKey clientPublic; + private ECPublicKey serverPublic; + + @Override + public void init(String name) throws IOException { + final ECParameterSpec spec; + + if ("ecdh-sha2-nistp256".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp256; + } else if ("ecdh-sha2-nistp384".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp384; + } else if ("ecdh-sha2-nistp521".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp521; + } else { + throw new IllegalArgumentException("Unknown EC curve " + name); + } + + KeyPairGenerator kpg; + try { + kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(spec); + KeyPair pair = kpg.generateKeyPair(); + clientPrivate = (ECPrivateKey) pair.getPrivate(); + clientPublic = (ECPublicKey) pair.getPublic(); + } catch (NoSuchAlgorithmException e) { + throw (IOException) new IOException("No DH keypair generator").initCause(e); + } catch (InvalidAlgorithmParameterException e) { + throw (IOException) new IOException("Invalid DH parameters").initCause(e); + } + } + + @Override + public byte[] getE() { + return ECDSASHA2Verify.encodeECPoint(clientPublic.getW(), clientPublic.getParams() + .getCurve()); + } + + @Override + protected byte[] getServerE() { + return ECDSASHA2Verify.encodeECPoint(serverPublic.getW(), serverPublic.getParams() + .getCurve()); + } + + @Override + public void setF(byte[] f) throws IOException { + + if (clientPublic == null) + throw new IllegalStateException("DhDsaExchange not initialized!"); + + final KeyAgreement ka; + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + ECParameterSpec params = clientPublic.getParams(); + ECPoint serverPoint = ECDSASHA2Verify.decodeECPoint(f, params.getCurve()); + this.serverPublic = (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(serverPoint, + params)); + + ka = KeyAgreement.getInstance("ECDH"); + ka.init(clientPrivate); + ka.doPhase(serverPublic, true); + } catch (NoSuchAlgorithmException e) { + throw (IOException) new IOException("No ECDH key agreement method").initCause(e); + } catch (InvalidKeyException e) { + throw (IOException) new IOException("Invalid ECDH key").initCause(e); + } catch (InvalidKeySpecException e) { + throw (IOException) new IOException("Invalid ECDH key").initCause(e); + } + + sharedSecret = new BigInteger(ka.generateSecret()); + } + + @Override + public String getHashAlgo() { + return ECDSASHA2Verify.getDigestAlgorithmForParams(clientPublic.getParams()); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java b/app/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java new file mode 100644 index 0000000..039ff75 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java @@ -0,0 +1,93 @@ + +package com.trilead.ssh2.crypto.dh; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import com.trilead.ssh2.crypto.digest.HashForSSH2Types; +import com.trilead.ssh2.log.Logger; + + +/** + * DhExchange. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DhExchange.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public abstract class GenericDhExchange +{ + private static final Logger log = Logger.getLogger(GenericDhExchange.class); + + /* Shared secret */ + + BigInteger sharedSecret; + + protected GenericDhExchange() + { + } + + public static GenericDhExchange getInstance(String algo) { + if (algo.startsWith("ecdh-sha2-")) { + return new EcDhExchange(); + } else { + return new DhExchange(); + } + } + + public abstract void init(String name) throws IOException; + + /** + * @return Returns the e (public value) + * @throws IllegalStateException + */ + public abstract byte[] getE(); + + /** + * @return Returns the server's e (public value) + * @throws IllegalStateException + */ + protected abstract byte[] getServerE(); + + /** + * @return Returns the shared secret k. + * @throws IllegalStateException + */ + public BigInteger getK() + { + if (sharedSecret == null) + throw new IllegalStateException("Shared secret not yet known, need f first!"); + + return sharedSecret; + } + + /** + * @param f + */ + public abstract void setF(byte[] f) throws IOException; + + public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload, + byte[] serverKexPayload, byte[] hostKey) throws UnsupportedEncodingException + { + HashForSSH2Types hash = new HashForSSH2Types(getHashAlgo()); + + if (log.isEnabled()) + { + log.log(90, "Client: '" + new String(clientversion, "ISO-8859-1") + "'"); + log.log(90, "Server: '" + new String(serverversion, "ISO-8859-1") + "'"); + } + + hash.updateByteString(clientversion); + hash.updateByteString(serverversion); + hash.updateByteString(clientKexPayload); + hash.updateByteString(serverKexPayload); + hash.updateByteString(hostKey); + hash.updateByteString(getE()); + hash.updateByteString(getServerE()); + hash.updateBigInt(sharedSecret); + + return hash.getDigest(); + } + + public abstract String getHashAlgo(); +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/digest/HashForSSH2Types.java b/app/src/main/java/com/trilead/ssh2/crypto/digest/HashForSSH2Types.java new file mode 100644 index 0000000..6b0d6e3 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/digest/HashForSSH2Types.java @@ -0,0 +1,91 @@ + +package com.trilead.ssh2.crypto.digest; + +import java.math.BigInteger; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * HashForSSH2Types. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: HashForSSH2Types.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class HashForSSH2Types +{ + MessageDigest md; + + public HashForSSH2Types(String type) + { + try { + md = MessageDigest.getInstance(type); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Unsupported algorithm " + type); + } + } + + public void updateByte(byte b) + { + /* HACK - to test it with J2ME */ + byte[] tmp = new byte[1]; + tmp[0] = b; + md.update(tmp); + } + + public void updateBytes(byte[] b) + { + md.update(b); + } + + public void updateUINT32(int v) + { + md.update((byte) (v >> 24)); + md.update((byte) (v >> 16)); + md.update((byte) (v >> 8)); + md.update((byte) (v)); + } + + public void updateByteString(byte[] b) + { + updateUINT32(b.length); + updateBytes(b); + } + + public void updateBigInt(BigInteger b) + { + updateByteString(b.toByteArray()); + } + + public void reset() + { + md.reset(); + } + + public int getDigestLength() + { + return md.getDigestLength(); + } + + public byte[] getDigest() + { + byte[] tmp = new byte[md.getDigestLength()]; + getDigest(tmp); + return tmp; + } + + public void getDigest(byte[] out) + { + getDigest(out, 0); + } + + public void getDigest(byte[] out, int off) + { + try { + md.digest(out, off, out.length - off); + } catch (DigestException e) { + // TODO is this right?! + throw new RuntimeException("Unable to digest", e); + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/crypto/digest/MAC.java b/app/src/main/java/com/trilead/ssh2/crypto/digest/MAC.java new file mode 100644 index 0000000..561599c --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/crypto/digest/MAC.java @@ -0,0 +1,157 @@ + +package com.trilead.ssh2.crypto.digest; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; + +/** + * MAC. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: MAC.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public final class MAC +{ + /** + * From http://tools.ietf.org/html/rfc4253 + */ + private static final String HMAC_MD5 = "hmac-md5"; + + /** + * From http://tools.ietf.org/html/rfc4253 + */ + private static final String HMAC_MD5_96 = "hmac-md5-96"; + + /** + * From http://tools.ietf.org/html/rfc4253 + */ + private static final String HMAC_SHA1 = "hmac-sha1"; + + /** + * From http://tools.ietf.org/html/rfc4253 + */ + private static final String HMAC_SHA1_96 = "hmac-sha1-96"; + + /** + * From http://tools.ietf.org/html/rfc6668 + */ + private static final String HMAC_SHA2_256 = "hmac-sha2-256"; + + /** + * From http://tools.ietf.org/html/rfc6668 + */ + private static final String HMAC_SHA2_512 = "hmac-sha2-512"; + + Mac mac; + int outSize; + int macSize; + byte[] buffer; + + /* Higher Priority First */ + private static final String[] MAC_LIST = { + HMAC_SHA2_256, HMAC_SHA2_512, + HMAC_SHA1_96, HMAC_SHA1, HMAC_MD5_96, HMAC_MD5 + }; + + public final static String[] getMacList() + { + return MAC_LIST; + } + + public final static void checkMacList(String[] macs) + { + for (int i = 0; i < macs.length; i++) + getKeyLen(macs[i]); + } + + public final static int getKeyLen(String type) + { + if (HMAC_SHA1.equals(type) || HMAC_SHA1_96.equals(type)) + return 20; + if (HMAC_MD5.equals(type) || HMAC_MD5_96.equals(type)) + return 16; + if (HMAC_SHA2_256.equals(type)) + return 32; + if (HMAC_SHA2_512.equals(type)) + return 64; + throw new IllegalArgumentException("Unkown algorithm " + type); + } + + public MAC(String type, byte[] key) + { + try { + if (HMAC_SHA1.equals(type) || HMAC_SHA1_96.equals(type)) + { + mac = Mac.getInstance("HmacSHA1"); + } + else if (HMAC_MD5.equals(type) || HMAC_MD5_96.equals(type)) + { + mac = Mac.getInstance("HmacMD5"); + } + else if (HMAC_SHA2_256.equals(type)) + { + mac = Mac.getInstance("HmacSHA256"); + } + else if (HMAC_SHA2_512.equals(type)) + { + mac = Mac.getInstance("HmacSHA512"); + } + else + throw new IllegalArgumentException("Unkown algorithm " + type); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Unknown algorithm " + type, e); + } + + macSize = mac.getMacLength(); + if (type.endsWith("-96")) { + outSize = 12; + buffer = new byte[macSize]; + } else { + outSize = macSize; + buffer = null; + } + + try { + mac.init(new SecretKeySpec(key, type)); + } catch (InvalidKeyException e) { + throw new IllegalArgumentException(e); + } + } + + public final void initMac(int seq) + { + mac.reset(); + mac.update((byte) (seq >> 24)); + mac.update((byte) (seq >> 16)); + mac.update((byte) (seq >> 8)); + mac.update((byte) (seq)); + } + + public final void update(byte[] packetdata, int off, int len) + { + mac.update(packetdata, off, len); + } + + public final void getMac(byte[] out, int off) + { + try { + if (buffer != null) { + mac.doFinal(buffer, 0); + System.arraycopy(buffer, 0, out, off, out.length - off); + } else { + mac.doFinal(out, off); + } + } catch (ShortBufferException e) { + throw new IllegalStateException(e); + } + } + + public final int size() + { + return outSize; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/log/Logger.java b/app/src/main/java/com/trilead/ssh2/log/Logger.java new file mode 100644 index 0000000..20ab397 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/log/Logger.java @@ -0,0 +1,54 @@ + +package com.trilead.ssh2.log; + +import com.trilead.ssh2.DebugLogger; + +/** + * Logger - a very simple logger, mainly used during development. + * Is not based on log4j (to reduce external dependencies). + * However, if needed, something like log4j could easily be + * hooked in. + * <p> + * For speed reasons, the static variables are not protected + * with semaphores. In other words, if you dynamicaly change the + * logging settings, then some threads may still use the old setting. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Logger.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $ + */ + +public class Logger +{ + public static boolean enabled = false; + public static DebugLogger logger = null; + + private String className; + + public final static Logger getLogger(Class x) + { + return new Logger(x); + } + + public Logger(Class x) + { + this.className = x.getName(); + } + + public final boolean isEnabled() + { + return enabled; + } + + public final void log(int level, String message) + { + if (!enabled) + return; + + DebugLogger target = logger; + + if (target == null) + return; + + target.log(level, className, message); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketChannelAuthAgentReq.java b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelAuthAgentReq.java new file mode 100644 index 0000000..95fa396 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelAuthAgentReq.java @@ -0,0 +1,33 @@ +package com.trilead.ssh2.packets; + +/** + * PacketGlobalAuthAgent. + * + * @author Kenny Root, kenny@the-b.org + * @version $Id$ + */ +public class PacketChannelAuthAgentReq +{ + byte[] payload; + + public int recipientChannelID; + + public PacketChannelAuthAgentReq(int recipientChannelID) + { + this.recipientChannelID = recipientChannelID; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("auth-agent-req@openssh.com"); + tw.writeBoolean(true); // want reply + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketChannelOpenConfirmation.java b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelOpenConfirmation.java new file mode 100644 index 0000000..bd2ea3f --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelOpenConfirmation.java @@ -0,0 +1,66 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketChannelOpenConfirmation. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketChannelOpenConfirmation.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketChannelOpenConfirmation +{ + byte[] payload; + + public int recipientChannelID; + public int senderChannelID; + public int initialWindowSize; + public int maxPacketSize; + + public PacketChannelOpenConfirmation(int recipientChannelID, int senderChannelID, int initialWindowSize, + int maxPacketSize) + { + this.recipientChannelID = recipientChannelID; + this.senderChannelID = senderChannelID; + this.initialWindowSize = initialWindowSize; + this.maxPacketSize = maxPacketSize; + } + + public PacketChannelOpenConfirmation(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) + throw new IOException( + "This is not a SSH_MSG_CHANNEL_OPEN_CONFIRMATION! (" + + packet_type + ")"); + + recipientChannelID = tr.readUINT32(); + senderChannelID = tr.readUINT32(); + initialWindowSize = tr.readUINT32(); + maxPacketSize = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(senderChannelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketChannelOpenFailure.java b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelOpenFailure.java new file mode 100644 index 0000000..1370355 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelOpenFailure.java @@ -0,0 +1,66 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketChannelOpenFailure. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketChannelOpenFailure.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketChannelOpenFailure +{ + byte[] payload; + + public int recipientChannelID; + public int reasonCode; + public String description; + public String languageTag; + + public PacketChannelOpenFailure(int recipientChannelID, int reasonCode, String description, + String languageTag) + { + this.recipientChannelID = recipientChannelID; + this.reasonCode = reasonCode; + this.description = description; + this.languageTag = languageTag; + } + + public PacketChannelOpenFailure(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_FAILURE) + throw new IOException( + "This is not a SSH_MSG_CHANNEL_OPEN_FAILURE! (" + + packet_type + ")"); + + recipientChannelID = tr.readUINT32(); + reasonCode = tr.readUINT32(); + description = tr.readString(); + languageTag = tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN_FAILURE packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_FAILURE); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(reasonCode); + tw.writeString(description); + tw.writeString(languageTag); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketChannelTrileadPing.java b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelTrileadPing.java new file mode 100644 index 0000000..c337930 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelTrileadPing.java @@ -0,0 +1,35 @@ + +package com.trilead.ssh2.packets; + +/** + * PacketChannelTrileadPing. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketChannelTrileadPing.java,v 1.1 2008/03/03 07:01:36 + * cplattne Exp $ + */ +public class PacketChannelTrileadPing +{ + byte[] payload; + + public int recipientChannelID; + + public PacketChannelTrileadPing(int recipientChannelID) + { + this.recipientChannelID = recipientChannelID; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("trilead-ping"); + tw.writeBoolean(true); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketChannelWindowAdjust.java b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelWindowAdjust.java new file mode 100644 index 0000000..37ec081 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketChannelWindowAdjust.java @@ -0,0 +1,57 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketChannelWindowAdjust. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketChannelWindowAdjust.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketChannelWindowAdjust +{ + byte[] payload; + + public int recipientChannelID; + public int windowChange; + + public PacketChannelWindowAdjust(int recipientChannelID, int windowChange) + { + this.recipientChannelID = recipientChannelID; + this.windowChange = windowChange; + } + + public PacketChannelWindowAdjust(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST) + throw new IOException( + "This is not a SSH_MSG_CHANNEL_WINDOW_ADJUST! (" + + packet_type + ")"); + + recipientChannelID = tr.readUINT32(); + windowChange = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_CHANNEL_WINDOW_ADJUST packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(windowChange); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketDisconnect.java b/app/src/main/java/com/trilead/ssh2/packets/PacketDisconnect.java new file mode 100644 index 0000000..50d6ec2 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketDisconnect.java @@ -0,0 +1,57 @@ + +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketDisconnect. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketDisconnect.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class PacketDisconnect +{ + byte[] payload; + + int reason; + String desc; + String lang; + + public PacketDisconnect(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_DISCONNECT) + throw new IOException("This is not a Disconnect Packet! (" + packet_type + ")"); + + reason = tr.readUINT32(); + desc = tr.readString(); + lang = tr.readString(); + } + + public PacketDisconnect(int reason, String desc, String lang) + { + this.reason = reason; + this.desc = desc; + this.lang = lang; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_DISCONNECT); + tw.writeUINT32(reason); + tw.writeString(desc); + tw.writeString(lang); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalCancelForwardRequest.java b/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalCancelForwardRequest.java new file mode 100644 index 0000000..20bd558 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalCancelForwardRequest.java @@ -0,0 +1,42 @@ + +package com.trilead.ssh2.packets; + +/** + * PacketGlobalCancelForwardRequest. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketGlobalCancelForwardRequest.java,v 1.1 2007/10/15 12:49:55 + * cplattne Exp $ + */ +public class PacketGlobalCancelForwardRequest +{ + byte[] payload; + + public boolean wantReply; + public String bindAddress; + public int bindPort; + + public PacketGlobalCancelForwardRequest(boolean wantReply, String bindAddress, int bindPort) + { + this.wantReply = wantReply; + this.bindAddress = bindAddress; + this.bindPort = bindPort; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); + + tw.writeString("cancel-tcpip-forward"); + tw.writeBoolean(wantReply); + tw.writeString(bindAddress); + tw.writeUINT32(bindPort); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalForwardRequest.java b/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalForwardRequest.java new file mode 100644 index 0000000..55257e9 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalForwardRequest.java @@ -0,0 +1,41 @@ + +package com.trilead.ssh2.packets; + +/** + * PacketGlobalForwardRequest. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketGlobalForwardRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketGlobalForwardRequest +{ + byte[] payload; + + public boolean wantReply; + public String bindAddress; + public int bindPort; + + public PacketGlobalForwardRequest(boolean wantReply, String bindAddress, int bindPort) + { + this.wantReply = wantReply; + this.bindAddress = bindAddress; + this.bindPort = bindPort; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); + + tw.writeString("tcpip-forward"); + tw.writeBoolean(wantReply); + tw.writeString(bindAddress); + tw.writeUINT32(bindPort); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalTrileadPing.java b/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalTrileadPing.java new file mode 100644 index 0000000..3d8930e --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketGlobalTrileadPing.java @@ -0,0 +1,32 @@ + +package com.trilead.ssh2.packets; + +/** + * PacketGlobalTrileadPing. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketGlobalTrileadPing.java,v 1.1 2008/03/03 07:01:36 cplattne Exp $ + */ +public class PacketGlobalTrileadPing +{ + byte[] payload; + + public PacketGlobalTrileadPing() + { + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); + + tw.writeString("trilead-ping"); + tw.writeBoolean(true); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketIgnore.java b/app/src/main/java/com/trilead/ssh2/packets/PacketIgnore.java new file mode 100644 index 0000000..2b4d917 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketIgnore.java @@ -0,0 +1,59 @@ + +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketIgnore. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketIgnore.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketIgnore +{ + byte[] payload; + + byte[] data; + + public void setData(byte[] data) + { + this.data = data; + payload = null; + } + + public PacketIgnore() + { + } + + public PacketIgnore(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_IGNORE) + throw new IOException("This is not a SSH_MSG_IGNORE packet! (" + packet_type + ")"); + + /* Could parse String body */ + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_IGNORE); + + if (data != null) + tw.writeString(data, 0, data.length); + else + tw.writeString(""); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketKexDHInit.java b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDHInit.java new file mode 100644 index 0000000..0092516 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDHInit.java @@ -0,0 +1,31 @@ +package com.trilead.ssh2.packets; + +/** + * PacketKexDHInit. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketKexDHInit.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketKexDHInit +{ + byte[] payload; + + byte[] publicKey; + + public PacketKexDHInit(byte[] publicKey) + { + this.publicKey = publicKey; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXDH_INIT); + tw.writeString(publicKey, 0, publicKey.length); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketKexDHReply.java b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDHReply.java new file mode 100644 index 0000000..51f2bda --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDHReply.java @@ -0,0 +1,53 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketKexDHReply. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketKexDHReply.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketKexDHReply +{ + byte[] payload; + + byte[] hostKey; + byte[] publicKey; + byte[] signature; + + public PacketKexDHReply(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXDH_REPLY) + throw new IOException("This is not a SSH_MSG_KEXDH_REPLY! (" + + packet_type + ")"); + + hostKey = tr.readByteString(); + publicKey = tr.readByteString(); + signature = tr.readByteString(); + + if (tr.remain() != 0) throw new IOException("PADDING IN SSH_MSG_KEXDH_REPLY!"); + } + + public byte[] getF() + { + return publicKey; + } + + public byte[] getHostKey() + { + return hostKey; + } + + public byte[] getSignature() + { + return signature; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexGroup.java b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexGroup.java new file mode 100644 index 0000000..db85b61 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexGroup.java @@ -0,0 +1,50 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +import java.math.BigInteger; + +/** + * PacketKexDhGexGroup. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketKexDhGexGroup.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketKexDhGexGroup +{ + byte[] payload; + + BigInteger p; + BigInteger g; + + public PacketKexDhGexGroup(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_GROUP) + throw new IllegalArgumentException( + "This is not a SSH_MSG_KEX_DH_GEX_GROUP! (" + packet_type + + ")"); + + p = tr.readMPINT(); + g = tr.readMPINT(); + + if (tr.remain() != 0) + throw new IOException("PADDING IN SSH_MSG_KEX_DH_GEX_GROUP!"); + } + + public BigInteger getG() + { + return g; + } + + public BigInteger getP() + { + return p; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexInit.java b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexInit.java new file mode 100644 index 0000000..8b34230 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexInit.java @@ -0,0 +1,33 @@ +package com.trilead.ssh2.packets; + +import java.math.BigInteger; + +/** + * PacketKexDhGexInit. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketKexDhGexInit.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketKexDhGexInit +{ + byte[] payload; + + BigInteger e; + + public PacketKexDhGexInit(BigInteger e) + { + this.e = e; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_INIT); + tw.writeMPInt(e); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexReply.java b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexReply.java new file mode 100644 index 0000000..382b3b7 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexReply.java @@ -0,0 +1,56 @@ + +package com.trilead.ssh2.packets; + +import java.io.IOException; + +import java.math.BigInteger; + +/** + * PacketKexDhGexReply. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketKexDhGexReply.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketKexDhGexReply +{ + byte[] payload; + + byte[] hostKey; + BigInteger f; + byte[] signature; + + public PacketKexDhGexReply(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_REPLY) + throw new IOException("This is not a SSH_MSG_KEX_DH_GEX_REPLY! (" + packet_type + ")"); + + hostKey = tr.readByteString(); + f = tr.readMPINT(); + signature = tr.readByteString(); + + if (tr.remain() != 0) + throw new IOException("PADDING IN SSH_MSG_KEX_DH_GEX_REPLY!"); + } + + public BigInteger getF() + { + return f; + } + + public byte[] getHostKey() + { + return hostKey; + } + + public byte[] getSignature() + { + return signature; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexRequest.java b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexRequest.java new file mode 100644 index 0000000..50369df --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexRequest.java @@ -0,0 +1,39 @@ +package com.trilead.ssh2.packets; + +import com.trilead.ssh2.DHGexParameters; + +/** + * PacketKexDhGexRequest. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketKexDhGexRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketKexDhGexRequest +{ + byte[] payload; + + int min; + int n; + int max; + + public PacketKexDhGexRequest(DHGexParameters para) + { + this.min = para.getMin_group_len(); + this.n = para.getPref_group_len(); + this.max = para.getMax_group_len(); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST); + tw.writeUINT32(min); + tw.writeUINT32(n); + tw.writeUINT32(max); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexRequestOld.java b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexRequestOld.java new file mode 100644 index 0000000..327f379 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketKexDhGexRequestOld.java @@ -0,0 +1,34 @@ + +package com.trilead.ssh2.packets; + +import com.trilead.ssh2.DHGexParameters; + +/** + * PacketKexDhGexRequestOld. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketKexDhGexRequestOld.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketKexDhGexRequestOld +{ + byte[] payload; + + int n; + + public PacketKexDhGexRequestOld(DHGexParameters para) + { + this.n = para.getPref_group_len(); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST_OLD); + tw.writeUINT32(n); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketKexInit.java b/app/src/main/java/com/trilead/ssh2/packets/PacketKexInit.java new file mode 100644 index 0000000..087d658 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketKexInit.java @@ -0,0 +1,165 @@ + +package com.trilead.ssh2.packets; + +import java.io.IOException; +import java.security.SecureRandom; + +import com.trilead.ssh2.crypto.CryptoWishList; +import com.trilead.ssh2.transport.KexParameters; + + +/** + * PacketKexInit. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketKexInit.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketKexInit +{ + byte[] payload; + + KexParameters kp = new KexParameters(); + + public PacketKexInit(CryptoWishList cwl) + { + kp.cookie = new byte[16]; + new SecureRandom().nextBytes(kp.cookie); + + kp.kex_algorithms = cwl.kexAlgorithms; + kp.server_host_key_algorithms = cwl.serverHostKeyAlgorithms; + kp.encryption_algorithms_client_to_server = cwl.c2s_enc_algos; + kp.encryption_algorithms_server_to_client = cwl.s2c_enc_algos; + kp.mac_algorithms_client_to_server = cwl.c2s_mac_algos; + kp.mac_algorithms_server_to_client = cwl.s2c_mac_algos; + kp.compression_algorithms_client_to_server = cwl.c2s_comp_algos; + kp.compression_algorithms_server_to_client = cwl.s2c_comp_algos; + kp.languages_client_to_server = new String[] {}; + kp.languages_server_to_client = new String[] {}; + kp.first_kex_packet_follows = false; + kp.reserved_field1 = 0; + } + + public PacketKexInit(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXINIT) + throw new IOException("This is not a KexInitPacket! (" + packet_type + ")"); + + kp.cookie = tr.readBytes(16); + kp.kex_algorithms = tr.readNameList(); + kp.server_host_key_algorithms = tr.readNameList(); + kp.encryption_algorithms_client_to_server = tr.readNameList(); + kp.encryption_algorithms_server_to_client = tr.readNameList(); + kp.mac_algorithms_client_to_server = tr.readNameList(); + kp.mac_algorithms_server_to_client = tr.readNameList(); + kp.compression_algorithms_client_to_server = tr.readNameList(); + kp.compression_algorithms_server_to_client = tr.readNameList(); + kp.languages_client_to_server = tr.readNameList(); + kp.languages_server_to_client = tr.readNameList(); + kp.first_kex_packet_follows = tr.readBoolean(); + kp.reserved_field1 = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Padding in KexInitPacket!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXINIT); + tw.writeBytes(kp.cookie, 0, 16); + tw.writeNameList(kp.kex_algorithms); + tw.writeNameList(kp.server_host_key_algorithms); + tw.writeNameList(kp.encryption_algorithms_client_to_server); + tw.writeNameList(kp.encryption_algorithms_server_to_client); + tw.writeNameList(kp.mac_algorithms_client_to_server); + tw.writeNameList(kp.mac_algorithms_server_to_client); + tw.writeNameList(kp.compression_algorithms_client_to_server); + tw.writeNameList(kp.compression_algorithms_server_to_client); + tw.writeNameList(kp.languages_client_to_server); + tw.writeNameList(kp.languages_server_to_client); + tw.writeBoolean(kp.first_kex_packet_follows); + tw.writeUINT32(kp.reserved_field1); + payload = tw.getBytes(); + } + return payload; + } + + public KexParameters getKexParameters() + { + return kp; + } + + public String[] getCompression_algorithms_client_to_server() + { + return kp.compression_algorithms_client_to_server; + } + + public String[] getCompression_algorithms_server_to_client() + { + return kp.compression_algorithms_server_to_client; + } + + public byte[] getCookie() + { + return kp.cookie; + } + + public String[] getEncryption_algorithms_client_to_server() + { + return kp.encryption_algorithms_client_to_server; + } + + public String[] getEncryption_algorithms_server_to_client() + { + return kp.encryption_algorithms_server_to_client; + } + + public boolean isFirst_kex_packet_follows() + { + return kp.first_kex_packet_follows; + } + + public String[] getKex_algorithms() + { + return kp.kex_algorithms; + } + + public String[] getLanguages_client_to_server() + { + return kp.languages_client_to_server; + } + + public String[] getLanguages_server_to_client() + { + return kp.languages_server_to_client; + } + + public String[] getMac_algorithms_client_to_server() + { + return kp.mac_algorithms_client_to_server; + } + + public String[] getMac_algorithms_server_to_client() + { + return kp.mac_algorithms_server_to_client; + } + + public int getReserved_field1() + { + return kp.reserved_field1; + } + + public String[] getServer_host_key_algorithms() + { + return kp.server_host_key_algorithms; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketNewKeys.java b/app/src/main/java/com/trilead/ssh2/packets/PacketNewKeys.java new file mode 100644 index 0000000..3ca6503 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketNewKeys.java @@ -0,0 +1,46 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketNewKeys. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketNewKeys.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketNewKeys +{ + byte[] payload; + + public PacketNewKeys() + { + } + + public PacketNewKeys(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_NEWKEYS) + throw new IOException("This is not a SSH_MSG_NEWKEYS! (" + + packet_type + ")"); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_NEWKEYS packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_NEWKEYS); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketOpenDirectTCPIPChannel.java b/app/src/main/java/com/trilead/ssh2/packets/PacketOpenDirectTCPIPChannel.java new file mode 100644 index 0000000..da6cbef --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketOpenDirectTCPIPChannel.java @@ -0,0 +1,56 @@ +package com.trilead.ssh2.packets; + + +/** + * PacketOpenDirectTCPIPChannel. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketOpenDirectTCPIPChannel.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketOpenDirectTCPIPChannel +{ + byte[] payload; + + int channelID; + int initialWindowSize; + int maxPacketSize; + + String host_to_connect; + int port_to_connect; + String originator_IP_address; + int originator_port; + + public PacketOpenDirectTCPIPChannel(int channelID, int initialWindowSize, int maxPacketSize, + String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) + { + this.channelID = channelID; + this.initialWindowSize = initialWindowSize; + this.maxPacketSize = maxPacketSize; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + this.originator_IP_address = originator_IP_address; + this.originator_port = originator_port; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); + tw.writeString("direct-tcpip"); + tw.writeUINT32(channelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + tw.writeString(host_to_connect); + tw.writeUINT32(port_to_connect); + tw.writeString(originator_IP_address); + tw.writeUINT32(originator_port); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketOpenSessionChannel.java b/app/src/main/java/com/trilead/ssh2/packets/PacketOpenSessionChannel.java new file mode 100644 index 0000000..a75ea63 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketOpenSessionChannel.java @@ -0,0 +1,62 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketOpenSessionChannel. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketOpenSessionChannel.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketOpenSessionChannel +{ + byte[] payload; + + int channelID; + int initialWindowSize; + int maxPacketSize; + + public PacketOpenSessionChannel(int channelID, int initialWindowSize, + int maxPacketSize) + { + this.channelID = channelID; + this.initialWindowSize = initialWindowSize; + this.maxPacketSize = maxPacketSize; + } + + public PacketOpenSessionChannel(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN) + throw new IOException("This is not a SSH_MSG_CHANNEL_OPEN! (" + + packet_type + ")"); + + channelID = tr.readUINT32(); + initialWindowSize = tr.readUINT32(); + maxPacketSize = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); + tw.writeString("session"); + tw.writeUINT32(channelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketServiceAccept.java b/app/src/main/java/com/trilead/ssh2/packets/PacketServiceAccept.java new file mode 100644 index 0000000..d5c9a90 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketServiceAccept.java @@ -0,0 +1,61 @@ + +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketServiceAccept. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketServiceAccept.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class PacketServiceAccept +{ + byte[] payload; + + String serviceName; + + public PacketServiceAccept(String serviceName) + { + this.serviceName = serviceName; + } + + public PacketServiceAccept(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_SERVICE_ACCEPT) + throw new IOException("This is not a SSH_MSG_SERVICE_ACCEPT! (" + packet_type + ")"); + + /* Be clever in case the server is not. Some servers seem to violate RFC4253 */ + + if (tr.remain() > 0) + { + serviceName = tr.readString(); + } + else + { + serviceName = ""; + } + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_SERVICE_ACCEPT packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_SERVICE_ACCEPT); + tw.writeString(serviceName); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketServiceRequest.java b/app/src/main/java/com/trilead/ssh2/packets/PacketServiceRequest.java new file mode 100644 index 0000000..c2d2065 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketServiceRequest.java @@ -0,0 +1,52 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketServiceRequest. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketServiceRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketServiceRequest +{ + byte[] payload; + + String serviceName; + + public PacketServiceRequest(String serviceName) + { + this.serviceName = serviceName; + } + + public PacketServiceRequest(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_SERVICE_REQUEST) + throw new IOException("This is not a SSH_MSG_SERVICE_REQUEST! (" + + packet_type + ")"); + + serviceName = tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_SERVICE_REQUEST packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_SERVICE_REQUEST); + tw.writeString(serviceName); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketSessionExecCommand.java b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionExecCommand.java new file mode 100644 index 0000000..84efa5d --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionExecCommand.java @@ -0,0 +1,39 @@ +package com.trilead.ssh2.packets; + + +/** + * PacketSessionExecCommand. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketSessionExecCommand.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketSessionExecCommand +{ + byte[] payload; + + public int recipientChannelID; + public boolean wantReply; + public String command; + + public PacketSessionExecCommand(int recipientChannelID, boolean wantReply, String command) + { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + this.command = command; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("exec"); + tw.writeBoolean(wantReply); + tw.writeString(command); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketSessionPtyRequest.java b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionPtyRequest.java new file mode 100644 index 0000000..d9c3d59 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionPtyRequest.java @@ -0,0 +1,57 @@ +package com.trilead.ssh2.packets; + + +/** + * PacketSessionPtyRequest. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketSessionPtyRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketSessionPtyRequest +{ + byte[] payload; + + public int recipientChannelID; + public boolean wantReply; + public String term; + public int character_width; + public int character_height; + public int pixel_width; + public int pixel_height; + public byte[] terminal_modes; + + public PacketSessionPtyRequest(int recipientChannelID, boolean wantReply, String term, + int character_width, int character_height, int pixel_width, int pixel_height, + byte[] terminal_modes) + { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + this.term = term; + this.character_width = character_width; + this.character_height = character_height; + this.pixel_width = pixel_width; + this.pixel_height = pixel_height; + this.terminal_modes = terminal_modes; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("pty-req"); + tw.writeBoolean(wantReply); + tw.writeString(term); + tw.writeUINT32(character_width); + tw.writeUINT32(character_height); + tw.writeUINT32(pixel_width); + tw.writeUINT32(pixel_height); + tw.writeString(terminal_modes, 0, terminal_modes.length); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketSessionPtyResize.java b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionPtyResize.java new file mode 100644 index 0000000..1e3b558 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionPtyResize.java @@ -0,0 +1,40 @@ +package com.trilead.ssh2.packets; + +public class PacketSessionPtyResize { + byte[] payload; + + public int recipientChannelID; + public int width; + public int height; + public int pixelWidth; + public int pixelHeight; + + public PacketSessionPtyResize(int recipientChannelID, int width, int height, int pixelWidth, int pixelHeight) { + this.recipientChannelID = recipientChannelID; + this.width = width; + this.height = height; + this.pixelWidth = pixelWidth; + this.pixelHeight = pixelHeight; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("window-change"); + tw.writeBoolean(false); + tw.writeUINT32(width); + tw.writeUINT32(height); + tw.writeUINT32(pixelWidth); + tw.writeUINT32(pixelHeight); + + payload = tw.getBytes(); + } + return payload; + } +} + + diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketSessionStartShell.java b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionStartShell.java new file mode 100644 index 0000000..e5add01 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionStartShell.java @@ -0,0 +1,36 @@ + +package com.trilead.ssh2.packets; + +/** + * PacketSessionStartShell. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketSessionStartShell.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketSessionStartShell +{ + byte[] payload; + + public int recipientChannelID; + public boolean wantReply; + + public PacketSessionStartShell(int recipientChannelID, boolean wantReply) + { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("shell"); + tw.writeBoolean(wantReply); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketSessionSubsystemRequest.java b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionSubsystemRequest.java new file mode 100644 index 0000000..cdc3a8c --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionSubsystemRequest.java @@ -0,0 +1,40 @@ +package com.trilead.ssh2.packets; + + +/** + * PacketSessionSubsystemRequest. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketSessionSubsystemRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketSessionSubsystemRequest +{ + byte[] payload; + + public int recipientChannelID; + public boolean wantReply; + public String subsystem; + + public PacketSessionSubsystemRequest(int recipientChannelID, boolean wantReply, String subsystem) + { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + this.subsystem = subsystem; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("subsystem"); + tw.writeBoolean(wantReply); + tw.writeString(subsystem); + payload = tw.getBytes(); + tw.getBytes(payload); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketSessionX11Request.java b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionX11Request.java new file mode 100644 index 0000000..26479c7 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketSessionX11Request.java @@ -0,0 +1,53 @@ + +package com.trilead.ssh2.packets; + +/** + * PacketSessionX11Request. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketSessionX11Request.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketSessionX11Request +{ + byte[] payload; + + public int recipientChannelID; + public boolean wantReply; + + public boolean singleConnection; + String x11AuthenticationProtocol; + String x11AuthenticationCookie; + int x11ScreenNumber; + + public PacketSessionX11Request(int recipientChannelID, boolean wantReply, boolean singleConnection, + String x11AuthenticationProtocol, String x11AuthenticationCookie, int x11ScreenNumber) + { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + + this.singleConnection = singleConnection; + this.x11AuthenticationProtocol = x11AuthenticationProtocol; + this.x11AuthenticationCookie = x11AuthenticationCookie; + this.x11ScreenNumber = x11ScreenNumber; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("x11-req"); + tw.writeBoolean(wantReply); + + tw.writeBoolean(singleConnection); + tw.writeString(x11AuthenticationProtocol); + tw.writeString(x11AuthenticationCookie); + tw.writeUINT32(x11ScreenNumber); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthBanner.java b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthBanner.java new file mode 100644 index 0000000..8ad8c3b --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthBanner.java @@ -0,0 +1,60 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketUserauthBanner. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketUserauthBanner.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketUserauthBanner +{ + byte[] payload; + + String message; + String language; + + public PacketUserauthBanner(String message, String language) + { + this.message = message; + this.language = language; + } + + public String getBanner() + { + return message; + } + + public PacketUserauthBanner(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_BANNER) + throw new IOException("This is not a SSH_MSG_USERAUTH_BANNER! (" + packet_type + ")"); + + message = tr.readString("UTF-8"); + language = tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_BANNER); + tw.writeString(message); + tw.writeString(language); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthFailure.java b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthFailure.java new file mode 100644 index 0000000..fd4a726 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthFailure.java @@ -0,0 +1,53 @@ + +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketUserauthBanner. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketUserauthFailure.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketUserauthFailure +{ + byte[] payload; + + String[] authThatCanContinue; + boolean partialSuccess; + + public PacketUserauthFailure(String[] authThatCanContinue, boolean partialSuccess) + { + this.authThatCanContinue = authThatCanContinue; + this.partialSuccess = partialSuccess; + } + + public PacketUserauthFailure(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_FAILURE) + throw new IOException("This is not a SSH_MSG_USERAUTH_FAILURE! (" + packet_type + ")"); + + authThatCanContinue = tr.readNameList(); + partialSuccess = tr.readBoolean(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_FAILURE packet!"); + } + + public String[] getAuthThatCanContinue() + { + return authThatCanContinue; + } + + public boolean isPartialSuccess() + { + return partialSuccess; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthInfoRequest.java b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthInfoRequest.java new file mode 100644 index 0000000..e1606d1 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthInfoRequest.java @@ -0,0 +1,84 @@ + +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketUserauthInfoRequest. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketUserauthInfoRequest.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketUserauthInfoRequest +{ + byte[] payload; + + String name; + String instruction; + String languageTag; + int numPrompts; + + String prompt[]; + boolean echo[]; + + public PacketUserauthInfoRequest(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_INFO_REQUEST) + throw new IOException("This is not a SSH_MSG_USERAUTH_INFO_REQUEST! (" + packet_type + ")"); + + name = tr.readString(); + instruction = tr.readString(); + languageTag = tr.readString(); + + numPrompts = tr.readUINT32(); + + prompt = new String[numPrompts]; + echo = new boolean[numPrompts]; + + for (int i = 0; i < numPrompts; i++) + { + prompt[i] = tr.readString(); + echo[i] = tr.readBoolean(); + } + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_INFO_REQUEST packet!"); + } + + public boolean[] getEcho() + { + return echo; + } + + public String getInstruction() + { + return instruction; + } + + public String getLanguageTag() + { + return languageTag; + } + + public String getName() + { + return name; + } + + public int getNumPrompts() + { + return numPrompts; + } + + public String[] getPrompt() + { + return prompt; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthInfoResponse.java b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthInfoResponse.java new file mode 100644 index 0000000..e8795d4 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthInfoResponse.java @@ -0,0 +1,35 @@ + +package com.trilead.ssh2.packets; + +/** + * PacketUserauthInfoResponse. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketUserauthInfoResponse.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketUserauthInfoResponse +{ + byte[] payload; + + String[] responses; + + public PacketUserauthInfoResponse(String[] responses) + { + this.responses = responses; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_INFO_RESPONSE); + tw.writeUINT32(responses.length); + for (int i = 0; i < responses.length; i++) + tw.writeString(responses[i]); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestInteractive.java b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestInteractive.java new file mode 100644 index 0000000..83e9f49 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestInteractive.java @@ -0,0 +1,42 @@ + +package com.trilead.ssh2.packets; + +/** + * PacketUserauthRequestInteractive. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketUserauthRequestInteractive.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketUserauthRequestInteractive +{ + byte[] payload; + + String userName; + String serviceName; + String[] submethods; + + public PacketUserauthRequestInteractive(String serviceName, String user, String[] submethods) + { + this.serviceName = serviceName; + this.userName = user; + this.submethods = submethods; + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(userName); + tw.writeString(serviceName); + tw.writeString("keyboard-interactive"); + tw.writeString(""); // draft-ietf-secsh-newmodes-04.txt says that + // the language tag should be empty. + tw.writeNameList(submethods); + + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestNone.java b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestNone.java new file mode 100644 index 0000000..d786003 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestNone.java @@ -0,0 +1,61 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketUserauthRequestPassword. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketUserauthRequestNone.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketUserauthRequestNone +{ + byte[] payload; + + String userName; + String serviceName; + + public PacketUserauthRequestNone(String serviceName, String user) + { + this.serviceName = serviceName; + this.userName = user; + } + + public PacketUserauthRequestNone(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + packet_type + ")"); + + userName = tr.readString(); + serviceName = tr.readString(); + + String method = tr.readString(); + + if (method.equals("none") == false) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST with type none!"); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(userName); + tw.writeString(serviceName); + tw.writeString("none"); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestPassword.java b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestPassword.java new file mode 100644 index 0000000..83047dd --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestPassword.java @@ -0,0 +1,67 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketUserauthRequestPassword. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketUserauthRequestPassword.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketUserauthRequestPassword +{ + byte[] payload; + + String userName; + String serviceName; + String password; + + public PacketUserauthRequestPassword(String serviceName, String user, String pass) + { + this.serviceName = serviceName; + this.userName = user; + this.password = pass; + } + + public PacketUserauthRequestPassword(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + packet_type + ")"); + + userName = tr.readString(); + serviceName = tr.readString(); + + String method = tr.readString(); + + if (method.equals("password") == false) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST with type password!"); + + /* ... */ + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(userName); + tw.writeString(serviceName); + tw.writeString("password"); + tw.writeBoolean(false); + tw.writeString(password); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestPublicKey.java b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestPublicKey.java new file mode 100644 index 0000000..6462864 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/PacketUserauthRequestPublicKey.java @@ -0,0 +1,65 @@ +package com.trilead.ssh2.packets; + +import java.io.IOException; + +/** + * PacketUserauthRequestPublicKey. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PacketUserauthRequestPublicKey.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class PacketUserauthRequestPublicKey +{ + byte[] payload; + + String userName; + String serviceName; + String password; + String pkAlgoName; + byte[] pk; + byte[] sig; + + public PacketUserauthRequestPublicKey(String serviceName, String user, + String pkAlgorithmName, byte[] pk, byte[] sig) + { + this.serviceName = serviceName; + this.userName = user; + this.pkAlgoName = pkAlgorithmName; + this.pk = pk; + this.sig = sig; + } + + public PacketUserauthRequestPublicKey(byte payload[], int off, int len) throws IOException + { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + + packet_type + ")"); + + throw new IOException("Not implemented!"); + } + + public byte[] getPayload() + { + if (payload == null) + { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(userName); + tw.writeString(serviceName); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(pkAlgoName); + tw.writeString(pk, 0, pk.length); + tw.writeString(sig, 0, sig.length); + payload = tw.getBytes(); + } + return payload; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/Packets.java b/app/src/main/java/com/trilead/ssh2/packets/Packets.java new file mode 100644 index 0000000..6989286 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/Packets.java @@ -0,0 +1,149 @@ + +package com.trilead.ssh2.packets; + +/** + * Packets. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Packets.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class Packets +{ + public static final int SSH_MSG_DISCONNECT = 1; + public static final int SSH_MSG_IGNORE = 2; + public static final int SSH_MSG_UNIMPLEMENTED = 3; + public static final int SSH_MSG_DEBUG = 4; + public static final int SSH_MSG_SERVICE_REQUEST = 5; + public static final int SSH_MSG_SERVICE_ACCEPT = 6; + + public static final int SSH_MSG_KEXINIT = 20; + public static final int SSH_MSG_NEWKEYS = 21; + + public static final int SSH_MSG_KEXDH_INIT = 30; + public static final int SSH_MSG_KEXDH_REPLY = 31; + + public static final int SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30; + public static final int SSH_MSG_KEX_DH_GEX_REQUEST = 34; + public static final int SSH_MSG_KEX_DH_GEX_GROUP = 31; + public static final int SSH_MSG_KEX_DH_GEX_INIT = 32; + public static final int SSH_MSG_KEX_DH_GEX_REPLY = 33; + + public static final int SSH_MSG_USERAUTH_REQUEST = 50; + public static final int SSH_MSG_USERAUTH_FAILURE = 51; + public static final int SSH_MSG_USERAUTH_SUCCESS = 52; + public static final int SSH_MSG_USERAUTH_BANNER = 53; + public static final int SSH_MSG_USERAUTH_INFO_REQUEST = 60; + public static final int SSH_MSG_USERAUTH_INFO_RESPONSE = 61; + + public static final int SSH_MSG_GLOBAL_REQUEST = 80; + public static final int SSH_MSG_REQUEST_SUCCESS = 81; + public static final int SSH_MSG_REQUEST_FAILURE = 82; + + public static final int SSH_MSG_CHANNEL_OPEN = 90; + public static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91; + public static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92; + public static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93; + public static final int SSH_MSG_CHANNEL_DATA = 94; + public static final int SSH_MSG_CHANNEL_EXTENDED_DATA = 95; + public static final int SSH_MSG_CHANNEL_EOF = 96; + public static final int SSH_MSG_CHANNEL_CLOSE = 97; + public static final int SSH_MSG_CHANNEL_REQUEST = 98; + public static final int SSH_MSG_CHANNEL_SUCCESS = 99; + public static final int SSH_MSG_CHANNEL_FAILURE = 100; + + public static final int SSH_EXTENDED_DATA_STDERR = 1; + + public static final int SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1; + public static final int SSH_DISCONNECT_PROTOCOL_ERROR = 2; + public static final int SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3; + public static final int SSH_DISCONNECT_RESERVED = 4; + public static final int SSH_DISCONNECT_MAC_ERROR = 5; + public static final int SSH_DISCONNECT_COMPRESSION_ERROR = 6; + public static final int SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7; + public static final int SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8; + public static final int SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9; + public static final int SSH_DISCONNECT_CONNECTION_LOST = 10; + public static final int SSH_DISCONNECT_BY_APPLICATION = 11; + public static final int SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12; + public static final int SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13; + public static final int SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14; + public static final int SSH_DISCONNECT_ILLEGAL_USER_NAME = 15; + + public static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1; + public static final int SSH_OPEN_CONNECT_FAILED = 2; + public static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3; + public static final int SSH_OPEN_RESOURCE_SHORTAGE = 4; + + private static final String[] reverseNames = new String[101]; + + static + { + reverseNames[1] = "SSH_MSG_DISCONNECT"; + reverseNames[2] = "SSH_MSG_IGNORE"; + reverseNames[3] = "SSH_MSG_UNIMPLEMENTED"; + reverseNames[4] = "SSH_MSG_DEBUG"; + reverseNames[5] = "SSH_MSG_SERVICE_REQUEST"; + reverseNames[6] = "SSH_MSG_SERVICE_ACCEPT"; + + reverseNames[20] = "SSH_MSG_KEXINIT"; + reverseNames[21] = "SSH_MSG_NEWKEYS"; + + reverseNames[30] = "SSH_MSG_KEXDH_INIT"; + reverseNames[31] = "SSH_MSG_KEXDH_REPLY/SSH_MSG_KEX_DH_GEX_GROUP"; + reverseNames[32] = "SSH_MSG_KEX_DH_GEX_INIT"; + reverseNames[33] = "SSH_MSG_KEX_DH_GEX_REPLY"; + reverseNames[34] = "SSH_MSG_KEX_DH_GEX_REQUEST"; + + reverseNames[50] = "SSH_MSG_USERAUTH_REQUEST"; + reverseNames[51] = "SSH_MSG_USERAUTH_FAILURE"; + reverseNames[52] = "SSH_MSG_USERAUTH_SUCCESS"; + reverseNames[53] = "SSH_MSG_USERAUTH_BANNER"; + + reverseNames[60] = "SSH_MSG_USERAUTH_INFO_REQUEST"; + reverseNames[61] = "SSH_MSG_USERAUTH_INFO_RESPONSE"; + + reverseNames[80] = "SSH_MSG_GLOBAL_REQUEST"; + reverseNames[81] = "SSH_MSG_REQUEST_SUCCESS"; + reverseNames[82] = "SSH_MSG_REQUEST_FAILURE"; + + reverseNames[90] = "SSH_MSG_CHANNEL_OPEN"; + reverseNames[91] = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION"; + reverseNames[92] = "SSH_MSG_CHANNEL_OPEN_FAILURE"; + reverseNames[93] = "SSH_MSG_CHANNEL_WINDOW_ADJUST"; + reverseNames[94] = "SSH_MSG_CHANNEL_DATA"; + reverseNames[95] = "SSH_MSG_CHANNEL_EXTENDED_DATA"; + reverseNames[96] = "SSH_MSG_CHANNEL_EOF"; + reverseNames[97] = "SSH_MSG_CHANNEL_CLOSE"; + reverseNames[98] = "SSH_MSG_CHANNEL_REQUEST"; + reverseNames[99] = "SSH_MSG_CHANNEL_SUCCESS"; + reverseNames[100] = "SSH_MSG_CHANNEL_FAILURE"; + } + + public static final String getMessageName(int type) + { + String res = null; + + if ((type >= 0) && (type < reverseNames.length)) + { + res = reverseNames[type]; + } + + return (res == null) ? ("UNKNOWN MSG " + type) : res; + } + + // public static final void debug(String tag, byte[] msg) + // { + // System.err.println(tag + " Type: " + msg[0] + ", LEN: " + msg.length); + // + // for (int i = 0; i < msg.length; i++) + // { + // if (((msg[i] >= 'a') && (msg[i] <= 'z')) || ((msg[i] >= 'A') && (msg[i] <= 'Z')) + // || ((msg[i] >= '0') && (msg[i] <= '9')) || (msg[i] == ' ')) + // System.err.print((char) msg[i]); + // else + // System.err.print("."); + // } + // System.err.println(); + // System.err.flush(); + // } +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/TypesReader.java b/app/src/main/java/com/trilead/ssh2/packets/TypesReader.java new file mode 100644 index 0000000..28f5363 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/TypesReader.java @@ -0,0 +1,177 @@ + +package com.trilead.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import com.trilead.ssh2.util.Tokenizer; + + +/** + * TypesReader. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: TypesReader.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class TypesReader +{ + byte[] arr; + int pos = 0; + int max = 0; + + public TypesReader(byte[] arr) + { + this.arr = arr; + pos = 0; + max = arr.length; + } + + public TypesReader(byte[] arr, int off) + { + this.arr = arr; + this.pos = off; + this.max = arr.length; + + if ((pos < 0) || (pos > arr.length)) + throw new IllegalArgumentException("Illegal offset."); + } + + public TypesReader(byte[] arr, int off, int len) + { + this.arr = arr; + this.pos = off; + this.max = off + len; + + if ((pos < 0) || (pos > arr.length)) + throw new IllegalArgumentException("Illegal offset."); + + if ((max < 0) || (max > arr.length)) + throw new IllegalArgumentException("Illegal length."); + } + + public int readByte() throws IOException + { + if (pos >= max) + throw new IOException("Packet too short."); + + return (arr[pos++] & 0xff); + } + + public byte[] readBytes(int len) throws IOException + { + if ((pos + len) > max) + throw new IOException("Packet too short."); + + byte[] res = new byte[len]; + + System.arraycopy(arr, pos, res, 0, len); + pos += len; + + return res; + } + + public void readBytes(byte[] dst, int off, int len) throws IOException + { + if ((pos + len) > max) + throw new IOException("Packet too short."); + + System.arraycopy(arr, pos, dst, off, len); + pos += len; + } + + public boolean readBoolean() throws IOException + { + if (pos >= max) + throw new IOException("Packet too short."); + + return (arr[pos++] != 0); + } + + public int readUINT32() throws IOException + { + if ((pos + 4) > max) + throw new IOException("Packet too short."); + + return ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); + } + + public long readUINT64() throws IOException + { + if ((pos + 8) > max) + throw new IOException("Packet too short."); + + long high = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); /* sign extension may take place - will be shifted away =) */ + + long low = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); /* sign extension may take place - handle below */ + + return (high << 32) | (low & 0xffffffffl); /* see Java language spec (15.22.1, 5.6.2) */ + } + + public BigInteger readMPINT() throws IOException + { + BigInteger b; + + byte raw[] = readByteString(); + + if (raw.length == 0) + b = BigInteger.ZERO; + else + b = new BigInteger(raw); + + return b; + } + + public byte[] readByteString() throws IOException + { + int len = readUINT32(); + + if ((len + pos) > max) + throw new IOException("Malformed SSH byte string."); + + byte[] res = new byte[len]; + System.arraycopy(arr, pos, res, 0, len); + pos += len; + return res; + } + + public String readString(String charsetName) throws IOException + { + int len = readUINT32(); + + if ((len + pos) > max) + throw new IOException("Malformed SSH string."); + + String res = (charsetName == null) ? new String(arr, pos, len) : new String(arr, pos, len, charsetName); + pos += len; + + return res; + } + + public String readString() throws IOException + { + int len = readUINT32(); + + if ((len + pos) > max) + throw new IOException("Malformed SSH string."); + + String res = new String(arr, pos, len, "ISO-8859-1"); + + pos += len; + + return res; + } + + public String[] readNameList() throws IOException + { + return Tokenizer.parseTokens(readString(), ','); + } + + public int remain() + { + return max - pos; + } + +} diff --git a/app/src/main/java/com/trilead/ssh2/packets/TypesWriter.java b/app/src/main/java/com/trilead/ssh2/packets/TypesWriter.java new file mode 100644 index 0000000..9f7336b --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/packets/TypesWriter.java @@ -0,0 +1,169 @@ + +package com.trilead.ssh2.packets; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +/** + * TypesWriter. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: TypesWriter.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class TypesWriter +{ + byte arr[]; + int pos; + + public TypesWriter() + { + arr = new byte[256]; + pos = 0; + } + + private void resize(int len) + { + byte new_arr[] = new byte[len]; + System.arraycopy(arr, 0, new_arr, 0, arr.length); + arr = new_arr; + } + + public int length() + { + return pos; + } + + public byte[] getBytes() + { + byte[] dst = new byte[pos]; + System.arraycopy(arr, 0, dst, 0, pos); + return dst; + } + + public void getBytes(byte dst[]) + { + System.arraycopy(arr, 0, dst, 0, pos); + } + + public void writeUINT32(int val, int off) + { + if ((off + 4) > arr.length) + resize(off + 32); + + arr[off++] = (byte) (val >> 24); + arr[off++] = (byte) (val >> 16); + arr[off++] = (byte) (val >> 8); + arr[off++] = (byte) val; + } + + public void writeUINT32(int val) + { + writeUINT32(val, pos); + pos += 4; + } + + public void writeUINT64(long val) + { + if ((pos + 8) > arr.length) + resize(arr.length + 32); + + arr[pos++] = (byte) (val >> 56); + arr[pos++] = (byte) (val >> 48); + arr[pos++] = (byte) (val >> 40); + arr[pos++] = (byte) (val >> 32); + arr[pos++] = (byte) (val >> 24); + arr[pos++] = (byte) (val >> 16); + arr[pos++] = (byte) (val >> 8); + arr[pos++] = (byte) val; + } + + public void writeBoolean(boolean v) + { + if ((pos + 1) > arr.length) + resize(arr.length + 32); + + arr[pos++] = v ? (byte) 1 : (byte) 0; + } + + public void writeByte(int v, int off) + { + if ((off + 1) > arr.length) + resize(off + 32); + + arr[off] = (byte) v; + } + + public void writeByte(int v) + { + writeByte(v, pos); + pos++; + } + + public void writeMPInt(BigInteger b) + { + byte raw[] = b.toByteArray(); + + if ((raw.length == 1) && (raw[0] == 0)) + writeUINT32(0); /* String with zero bytes of data */ + else + writeString(raw, 0, raw.length); + } + + public void writeBytes(byte[] buff) + { + writeBytes(buff, 0, buff.length); + } + + public void writeBytes(byte[] buff, int off, int len) + { + if ((pos + len) > arr.length) + resize(arr.length + len + 32); + + System.arraycopy(buff, off, arr, pos, len); + pos += len; + } + + public void writeString(byte[] buff, int off, int len) + { + writeUINT32(len); + writeBytes(buff, off, len); + } + + public void writeString(String v) + { + byte[] b; + + try + { + /* All Java JVMs must support ISO-8859-1 */ + b = v.getBytes("ISO-8859-1"); + } + catch (UnsupportedEncodingException ignore) + { + b = v.getBytes(); + } + + writeUINT32(b.length); + writeBytes(b, 0, b.length); + } + + public void writeString(String v, String charsetName) throws UnsupportedEncodingException + { + byte[] b = (charsetName == null) ? v.getBytes() : v.getBytes(charsetName); + + writeUINT32(b.length); + writeBytes(b, 0, b.length); + } + + public void writeNameList(String v[]) + { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < v.length; i++) + { + if (i > 0) + sb.append(','); + sb.append(v[i]); + } + writeString(sb.toString()); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/sftp/AttrTextHints.java b/app/src/main/java/com/trilead/ssh2/sftp/AttrTextHints.java new file mode 100644 index 0000000..19f0525 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/sftp/AttrTextHints.java @@ -0,0 +1,38 @@ + +package com.trilead.ssh2.sftp; + +/** + * + * Values for the 'text-hint' field in the SFTP ATTRS data type. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: AttrTextHints.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class AttrTextHints +{ + /** + * The server knows the file is a text file, and should be opened + * using the SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_KNOWN_TEXT = 0x00; + + /** + * The server has applied a heuristic or other mechanism and + * believes that the file should be opened with the + * SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_GUESSED_TEXT = 0x01; + + /** + * The server knows the file has binary content. + */ + public static final int SSH_FILEXFER_ATTR_KNOWN_BINARY = 0x02; + + /** + * The server has applied a heuristic or other mechanism and + * believes has binary content, and should not be opened with the + * SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_GUESSED_BINARY = 0x03; +} diff --git a/app/src/main/java/com/trilead/ssh2/sftp/AttribBits.java b/app/src/main/java/com/trilead/ssh2/sftp/AttribBits.java new file mode 100644 index 0000000..b143613 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/sftp/AttribBits.java @@ -0,0 +1,129 @@ + +package com.trilead.ssh2.sftp; + +/** + * + * SFTP Attribute Bits for the "attrib-bits" and "attrib-bits-valid" fields + * of the SFTP ATTR data type. + * <p> + * Yes, these are the "attrib-bits", even though they have "_FLAGS_" in + * their name. Don't ask - I did not invent it. + * <p> + * "<i>These fields, taken together, reflect various attributes of the file + * or directory, on the server. Bits not set in 'attrib-bits-valid' MUST be + * ignored in the 'attrib-bits' field. This allows both the server and the + * client to communicate only the bits it knows about without inadvertently + * twiddling bits they don't understand.</i>" + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: AttribBits.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class AttribBits +{ + /** + * Advisory, read-only bit. This bit is not part of the access + * control information on the file, but is rather an advisory field + * indicating that the file should not be written. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001; + + /** + * The file is part of the operating system. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002; + + /** + * File SHOULD NOT be shown to user unless specifically requested. + * For example, most UNIX systems SHOULD set this bit if the filename + * begins with a 'period'. This bit may be read-only (see section 5.4 of + * the SFTP standard draft). Most UNIX systems will not allow this to be + * changed. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004; + + /** + * This attribute applies only to directories. This attribute is + * always read-only, and cannot be modified. This attribute means + * that files and directory names in this directory should be compared + * without regard to case. + * <p> + * It is recommended that where possible, the server's filesystem be + * allowed to do comparisons. For example, if a client wished to prompt + * a user before overwriting a file, it should not compare the new name + * with the previously retrieved list of names in the directory. Rather, + * it should first try to create the new file by specifying + * SSH_FXF_CREATE_NEW flag. Then, if this fails and returns + * SSH_FX_FILE_ALREADY_EXISTS, it should prompt the user and then retry + * the create specifying SSH_FXF_CREATE_TRUNCATE. + * <p> + * Unless otherwise specified, filenames are assumed to be case sensitive. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008; + + /** + * The file should be included in backup / archive operations. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010; + + /** + * The file is stored on disk using file-system level transparent + * encryption. This flag does not affect the file data on the wire + * (for either READ or WRITE requests.) + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020; + + /** + * The file is stored on disk using file-system level transparent + * compression. This flag does not affect the file data on the wire. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040; + + /** + * The file is a sparse file; this means that file blocks that have + * not been explicitly written are not stored on disk. For example, if + * a client writes a buffer at 10 M from the beginning of the file, + * the blocks between the previous EOF marker and the 10 M offset would + * not consume physical disk space. + * <p> + * Some servers may store all files as sparse files, in which case + * this bit will be unconditionally set. Other servers may not have + * a mechanism for determining if the file is sparse, and so the file + * MAY be stored sparse even if this flag is not set. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080; + + /** + * Opening the file without either the SSH_FXF_ACCESS_APPEND_DATA or + * the SSH_FXF_ACCESS_APPEND_DATA_ATOMIC flag (see section 8.1.1.3 + * of the SFTP standard draft) MUST result in an + * SSH_FX_INVALID_PARAMETER error. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100; + + /** + * The file cannot be deleted or renamed, no hard link can be created + * to this file, and no data can be written to the file. + * <p> + * This bit implies a stronger level of protection than + * SSH_FILEXFER_ATTR_FLAGS_READONLY, the file permission mask or ACLs. + * Typically even the superuser cannot write to immutable files, and + * only the superuser can set or remove the bit. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200; + + /** + * When the file is modified, the changes are written synchronously + * to the disk. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400; + + /** + * The server MAY include this bit in a directory listing or realpath + * response. It indicates there was a failure in the translation to UTF-8. + * If this flag is included, the server SHOULD also include the + * UNTRANSLATED_NAME attribute. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_TRANSLATION_ERR = 0x00000800; + +} diff --git a/app/src/main/java/com/trilead/ssh2/sftp/AttribFlags.java b/app/src/main/java/com/trilead/ssh2/sftp/AttribFlags.java new file mode 100644 index 0000000..ea27871 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/sftp/AttribFlags.java @@ -0,0 +1,112 @@ + +package com.trilead.ssh2.sftp; + +/** + * + * Attribute Flags. The 'valid-attribute-flags' field in + * the SFTP ATTRS data type specifies which of the fields are actually present. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: AttribFlags.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class AttribFlags +{ + /** + * Indicates that the 'allocation-size' field is present. + */ + public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001; + + /** Protocol version 6: + * 0x00000002 was used in a previous version of this protocol. + * It is now a reserved value and MUST NOT appear in the mask. + * Some future version of this protocol may reuse this value. + */ + public static final int SSH_FILEXFER_ATTR_V3_UIDGID = 0x00000002; + + /** + * Indicates that the 'permissions' field is present. + */ + public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; + + /** + * Indicates that the 'atime' and 'mtime' field are present + * (protocol v3). + */ + public static final int SSH_FILEXFER_ATTR_V3_ACMODTIME = 0x00000008; + + /** + * Indicates that the 'atime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; + + /** + * Indicates that the 'createtime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; + + /** + * Indicates that the 'mtime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; + + /** + * Indicates that the 'acl' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; + + /** + * Indicates that the 'owner' and 'group' fields are present. + */ + public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; + + /** + * Indicates that additionally to the 'atime', 'createtime', + * 'mtime' and 'ctime' fields (if present), there is also + * 'atime-nseconds', 'createtime-nseconds', 'mtime-nseconds' + * and 'ctime-nseconds'. + */ + public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; + + /** + * Indicates that the 'attrib-bits' and 'attrib-bits-valid' + * fields are present. + */ + public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; + + /** + * Indicates that the 'allocation-size' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; + + /** + * Indicates that the 'text-hint' field is present. + */ + public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; + + /** + * Indicates that the 'mime-type' field is present. + */ + public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; + + /** + * Indicates that the 'link-count' field is present. + */ + public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; + + /** + * Indicates that the 'untranslated-name' field is present. + */ + public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; + + /** + * Indicates that the 'ctime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; + + /** + * Indicates that the 'extended-count' field (and probablby some + * 'extensions') is present. + */ + public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; +} diff --git a/app/src/main/java/com/trilead/ssh2/sftp/AttribPermissions.java b/app/src/main/java/com/trilead/ssh2/sftp/AttribPermissions.java new file mode 100644 index 0000000..558aa6f --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/sftp/AttribPermissions.java @@ -0,0 +1,32 @@ + +package com.trilead.ssh2.sftp; + +/** + * + * Permissions for the 'permissions' field in the SFTP ATTRS data type. + * <p> + * "<i>The 'permissions' field contains a bit mask specifying file permissions. + * These permissions correspond to the st_mode field of the stat structure + * defined by POSIX [IEEE.1003-1.1996].</i>" + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: AttribPermissions.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class AttribPermissions +{ + /* Octal values! */ + + public static final int S_IRUSR = 0400; + public static final int S_IWUSR = 0200; + public static final int S_IXUSR = 0100; + public static final int S_IRGRP = 0040; + public static final int S_IWGRP = 0020; + public static final int S_IXGRP = 0010; + public static final int S_IROTH = 0004; + public static final int S_IWOTH = 0002; + public static final int S_IXOTH = 0001; + public static final int S_ISUID = 04000; + public static final int S_ISGID = 02000; + public static final int S_ISVTX = 01000; +} diff --git a/app/src/main/java/com/trilead/ssh2/sftp/AttribTypes.java b/app/src/main/java/com/trilead/ssh2/sftp/AttribTypes.java new file mode 100644 index 0000000..e2f4169 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/sftp/AttribTypes.java @@ -0,0 +1,28 @@ + +package com.trilead.ssh2.sftp; + +/** + * + * Types for the 'type' field in the SFTP ATTRS data type. + * <p> + * "<i>On a POSIX system, these values would be derived from the mode field + * of the stat structure. SPECIAL should be used for files that are of + * a known type which cannot be expressed in the protocol. UNKNOWN + * should be used if the type is not known.</i>" + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: AttribTypes.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class AttribTypes +{ + public static final int SSH_FILEXFER_TYPE_REGULAR = 1; + public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2; + public static final int SSH_FILEXFER_TYPE_SYMLINK = 3; + public static final int SSH_FILEXFER_TYPE_SPECIAL = 4; + public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5; + public static final int SSH_FILEXFER_TYPE_SOCKET = 6; + public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; + public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; + public static final int SSH_FILEXFER_TYPE_FIFO = 9; +} diff --git a/app/src/main/java/com/trilead/ssh2/sftp/ErrorCodes.java b/app/src/main/java/com/trilead/ssh2/sftp/ErrorCodes.java new file mode 100644 index 0000000..7317a00 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/sftp/ErrorCodes.java @@ -0,0 +1,104 @@ + +package com.trilead.ssh2.sftp; + +/** + * + * SFTP Error Codes + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ErrorCodes.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class ErrorCodes +{ + public static final int SSH_FX_OK = 0; + public static final int SSH_FX_EOF = 1; + public static final int SSH_FX_NO_SUCH_FILE = 2; + public static final int SSH_FX_PERMISSION_DENIED = 3; + public static final int SSH_FX_FAILURE = 4; + public static final int SSH_FX_BAD_MESSAGE = 5; + public static final int SSH_FX_NO_CONNECTION = 6; + public static final int SSH_FX_CONNECTION_LOST = 7; + public static final int SSH_FX_OP_UNSUPPORTED = 8; + public static final int SSH_FX_INVALID_HANDLE = 9; + public static final int SSH_FX_NO_SUCH_PATH = 10; + public static final int SSH_FX_FILE_ALREADY_EXISTS = 11; + public static final int SSH_FX_WRITE_PROTECT = 12; + public static final int SSH_FX_NO_MEDIA = 13; + public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14; + public static final int SSH_FX_QUOTA_EXCEEDED = 15; + public static final int SSH_FX_UNKNOWN_PRINCIPAL = 16; + public static final int SSH_FX_LOCK_CONFLICT = 17; + public static final int SSH_FX_DIR_NOT_EMPTY = 18; + public static final int SSH_FX_NOT_A_DIRECTORY = 19; + public static final int SSH_FX_INVALID_FILENAME = 20; + public static final int SSH_FX_LINK_LOOP = 21; + public static final int SSH_FX_CANNOT_DELETE = 22; + public static final int SSH_FX_INVALID_PARAMETER = 23; + public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24; + public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25; + public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26; + public static final int SSH_FX_DELETE_PENDING = 27; + public static final int SSH_FX_FILE_CORRUPT = 28; + public static final int SSH_FX_OWNER_INVALID = 29; + public static final int SSH_FX_GROUP_INVALID = 30; + public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31; + + private static final String[][] messages = { + + { "SSH_FX_OK", "Indicates successful completion of the operation." }, + { "SSH_FX_EOF", + "An attempt to read past the end-of-file was made; or, there are no more directory entries to return." }, + { "SSH_FX_NO_SUCH_FILE", "A reference was made to a file which does not exist." }, + { "SSH_FX_PERMISSION_DENIED", "The user does not have sufficient permissions to perform the operation." }, + { "SSH_FX_FAILURE", "An error occurred, but no specific error code exists to describe the failure." }, + { "SSH_FX_BAD_MESSAGE", "A badly formatted packet or other SFTP protocol incompatibility was detected." }, + { "SSH_FX_NO_CONNECTION", "There is no connection to the server." }, + { "SSH_FX_CONNECTION_LOST", "The connection to the server was lost." }, + { "SSH_FX_OP_UNSUPPORTED", + "An attempted operation could not be completed by the server because the server does not support the operation." }, + { "SSH_FX_INVALID_HANDLE", "The handle value was invalid." }, + { "SSH_FX_NO_SUCH_PATH", "The file path does not exist or is invalid." }, + { "SSH_FX_FILE_ALREADY_EXISTS", "The file already exists." }, + { "SSH_FX_WRITE_PROTECT", "The file is on read-only media, or the media is write protected." }, + { "SSH_FX_NO_MEDIA", + "The requested operation cannot be completed because there is no media available in the drive." }, + { "SSH_FX_NO_SPACE_ON_FILESYSTEM", + "The requested operation cannot be completed because there is insufficient free space on the filesystem." }, + { "SSH_FX_QUOTA_EXCEEDED", + "The operation cannot be completed because it would exceed the user's storage quota." }, + { + "SSH_FX_UNKNOWN_PRINCIPAL", + "A principal referenced by the request (either the 'owner', 'group', or 'who' field of an ACL), was unknown. The error specific data contains the problematic names." }, + { "SSH_FX_LOCK_CONFLICT", "The file could not be opened because it is locked by another process." }, + { "SSH_FX_DIR_NOT_EMPTY", "The directory is not empty." }, + { "SSH_FX_NOT_A_DIRECTORY", "The specified file is not a directory." }, + { "SSH_FX_INVALID_FILENAME", "The filename is not valid." }, + { "SSH_FX_LINK_LOOP", + "Too many symbolic links encountered or, an SSH_FXF_NOFOLLOW open encountered a symbolic link as the final component." }, + { "SSH_FX_CANNOT_DELETE", + "The file cannot be deleted. One possible reason is that the advisory READONLY attribute-bit is set." }, + { "SSH_FX_INVALID_PARAMETER", + "One of the parameters was out of range, or the parameters specified cannot be used together." }, + { "SSH_FX_FILE_IS_A_DIRECTORY", + "The specified file was a directory in a context where a directory cannot be used." }, + { "SSH_FX_BYTE_RANGE_LOCK_CONFLICT", + " A read or write operation failed because another process's mandatory byte-range lock overlaps with the request." }, + { "SSH_FX_BYTE_RANGE_LOCK_REFUSED", "A request for a byte range lock was refused." }, + { "SSH_FX_DELETE_PENDING", "An operation was attempted on a file for which a delete operation is pending." }, + { "SSH_FX_FILE_CORRUPT", "The file is corrupt; an filesystem integrity check should be run." }, + { "SSH_FX_OWNER_INVALID", "The principal specified can not be assigned as an owner of a file." }, + { "SSH_FX_GROUP_INVALID", "The principal specified can not be assigned as the primary group of a file." }, + { "SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK", + "The requested operation could not be completed because the specifed byte range lock has not been granted." }, + + }; + + public static final String[] getDescription(int errorCode) + { + if ((errorCode < 0) || (errorCode >= messages.length)) + return null; + + return messages[errorCode]; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/sftp/OpenFlags.java b/app/src/main/java/com/trilead/ssh2/sftp/OpenFlags.java new file mode 100644 index 0000000..b2979b9 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/sftp/OpenFlags.java @@ -0,0 +1,223 @@ + +package com.trilead.ssh2.sftp; + +/** + * + * SFTP Open Flags. + * + * The following table is provided to assist in mapping POSIX semantics + * to equivalent SFTP file open parameters: + * <p> + * TODO: This comment should be moved to the open method. + * <p> + * <ul> + * <li>O_RDONLY + * <ul><li>desired-access = READ_DATA | READ_ATTRIBUTES</li></ul> + * </li> + * </ul> + * <ul> + * <li>O_WRONLY + * <ul><li>desired-access = WRITE_DATA | WRITE_ATTRIBUTES</li></ul> + * </li> + * </ul> + * <ul> + * <li>O_RDWR + * <ul><li>desired-access = READ_DATA | READ_ATTRIBUTES | WRITE_DATA | WRITE_ATTRIBUTES</li></ul> + * </li> + * </ul> + * <ul> + * <li>O_APPEND + * <ul> + * <li>desired-access = WRITE_DATA | WRITE_ATTRIBUTES | APPEND_DATA</li> + * <li>flags = SSH_FXF_ACCESS_APPEND_DATA and or SSH_FXF_ACCESS_APPEND_DATA_ATOMIC</li> + * </ul> + * </li> + * </ul> + * <ul> + * <li>O_CREAT + * <ul> + * <li>flags = SSH_FXF_OPEN_OR_CREATE</li> + * </ul> + * </li> + * </ul> + * <ul> + * <li>O_TRUNC + * <ul> + * <li>flags = SSH_FXF_TRUNCATE_EXISTING</li> + * </ul> + * </li> + * </ul> + * <ul> + * <li>O_TRUNC|O_CREATE + * <ul> + * <li>flags = SSH_FXF_CREATE_TRUNCATE</li> + * </ul> + * </li> + * </ul> + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: OpenFlags.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + */ +public class OpenFlags +{ + /** + * Disposition is a 3 bit field that controls how the file is opened. + * The server MUST support these bits (possible enumaration values: + * SSH_FXF_CREATE_NEW, SSH_FXF_CREATE_TRUNCATE, SSH_FXF_OPEN_EXISTING, + * SSH_FXF_OPEN_OR_CREATE or SSH_FXF_TRUNCATE_EXISTING). + */ + public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007; + + /** + * A new file is created; if the file already exists, the server + * MUST return status SSH_FX_FILE_ALREADY_EXISTS. + */ + public static final int SSH_FXF_CREATE_NEW = 0x00000000; + + /** + * A new file is created; if the file already exists, it is opened + * and truncated. + */ + public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001; + + /** + * An existing file is opened. If the file does not exist, the + * server MUST return SSH_FX_NO_SUCH_FILE. If a directory in the + * path does not exist, the server SHOULD return + * SSH_FX_NO_SUCH_PATH. It is also acceptable if the server + * returns SSH_FX_NO_SUCH_FILE in this case. + */ + public static final int SSH_FXF_OPEN_EXISTING = 0x00000002; + + /** + * If the file exists, it is opened. If the file does not exist, + * it is created. + */ + public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003; + + /** + * An existing file is opened and truncated. If the file does not + * exist, the server MUST return the same error codes as defined + * for SSH_FXF_OPEN_EXISTING. + */ + public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004; + + /** + * Data is always written at the end of the file. The offset field + * of the SSH_FXP_WRITE requests are ignored. + * <p> + * Data is not required to be appended atomically. This means that + * if multiple writers attempt to append data simultaneously, data + * from the first may be lost. However, data MAY be appended + * atomically. + */ + public static final int SSH_FXF_ACCESS_APPEND_DATA = 0x00000008; + + /** + * Data is always written at the end of the file. The offset field + * of the SSH_FXP_WRITE requests are ignored. + * <p> + * Data MUST be written atomically so that there is no chance that + * multiple appenders can collide and result in data being lost. + * <p> + * If both append flags are specified, the server SHOULD use atomic + * append if it is available, but SHOULD use non-atomic appends + * otherwise. The server SHOULD NOT fail the request in this case. + */ + public static final int SSH_FXF_ACCESS_APPEND_DATA_ATOMIC = 0x00000010; + + /** + * Indicates that the server should treat the file as text and + * convert it to the canonical newline convention in use. + * (See Determining Server Newline Convention in section 5.3 in the + * SFTP standard draft). + * <p> + * When a file is opened with this flag, the offset field in the read + * and write functions is ignored. + * <p> + * Servers MUST process multiple, parallel reads and writes correctly + * in this mode. Naturally, it is permissible for them to do this by + * serializing the requests. + * <p> + * Clients SHOULD use the SSH_FXF_ACCESS_APPEND_DATA flag to append + * data to a text file rather then using write with a calculated offset. + */ + public static final int SSH_FXF_ACCESS_TEXT_MODE = 0x00000020; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_READ_DATA access, and that no other handle will be + * opened with ACE4_READ_DATA access until the client closes the + * handle. (This MUST apply both to other clients and to other + * processes on the server.) + * <p> + * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + * <p> + * Other handles MAY be opened for ACE4_WRITE_DATA or any other + * combination of accesses, as long as ACE4_READ_DATA is not included + * in the mask. + */ + public static final int SSH_FXF_ACCESS_BLOCK_READ = 0x00000040; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_WRITE_DATA or ACE4_APPEND_DATA access, and that no other + * handle will be opened with ACE4_WRITE_DATA or ACE4_APPEND_DATA + * access until the client closes the handle. (This MUST apply both + * to other clients and to other processes on the server.) + * <p> + * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + * <p> + * Other handles MAY be opened for ACE4_READ_DATA or any other + * combination of accesses, as long as neither ACE4_WRITE_DATA nor + * ACE4_APPEND_DATA are included in the mask. + */ + public static final int SSH_FXF_ACCESS_BLOCK_WRITE = 0x00000080; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_DELETE access, opened with the + * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that no other handle + * will be opened with ACE4_DELETE access or with the + * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that the file itself + * is not deleted in any other way until the client closes the handle. + * <p> + * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + */ + public static final int SSH_FXF_ACCESS_BLOCK_DELETE = 0x00000100; + + /** + * If this bit is set, the above BLOCK modes are advisory. In advisory + * mode, only other accesses that specify a BLOCK mode need be + * considered when determining whether the BLOCK can be granted, + * and the server need not prevent I/O operations that violate the + * block mode. + * <p> + * The server MAY perform mandatory locking even if the BLOCK_ADVISORY + * bit is set. + */ + public static final int SSH_FXF_ACCESS_BLOCK_ADVISORY = 0x00000200; + + /** + * If the final component of the path is a symlink, then the open + * MUST fail, and the error SSH_FX_LINK_LOOP MUST be returned. + */ + public static final int SSH_FXF_ACCESS_NOFOLLOW = 0x00000400; + + /** + * The file should be deleted when the last handle to it is closed. + * (The last handle may not be an sftp-handle.) This MAY be emulated + * by a server if the OS doesn't support it by deleting the file when + * this handle is closed. + * <p> + * It is implementation specific whether the directory entry is + * removed immediately or when the handle is closed. + */ + public static final int SSH_FXF_ACCESS_DELETE_ON_CLOSE = 0x00000800; +} diff --git a/app/src/main/java/com/trilead/ssh2/sftp/Packet.java b/app/src/main/java/com/trilead/ssh2/sftp/Packet.java new file mode 100644 index 0000000..444af90 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/sftp/Packet.java @@ -0,0 +1,43 @@ + +package com.trilead.ssh2.sftp; + +/** + * + * SFTP Paket Types + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Packet.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $ + * + */ +public class Packet +{ + public static final int SSH_FXP_INIT = 1; + public static final int SSH_FXP_VERSION = 2; + public static final int SSH_FXP_OPEN = 3; + public static final int SSH_FXP_CLOSE = 4; + public static final int SSH_FXP_READ = 5; + public static final int SSH_FXP_WRITE = 6; + public static final int SSH_FXP_LSTAT = 7; + public static final int SSH_FXP_FSTAT = 8; + public static final int SSH_FXP_SETSTAT = 9; + public static final int SSH_FXP_FSETSTAT = 10; + public static final int SSH_FXP_OPENDIR = 11; + public static final int SSH_FXP_READDIR = 12; + public static final int SSH_FXP_REMOVE = 13; + public static final int SSH_FXP_MKDIR = 14; + public static final int SSH_FXP_RMDIR = 15; + public static final int SSH_FXP_REALPATH = 16; + public static final int SSH_FXP_STAT = 17; + public static final int SSH_FXP_RENAME = 18; + public static final int SSH_FXP_READLINK = 19; + public static final int SSH_FXP_SYMLINK = 20; + + public static final int SSH_FXP_STATUS = 101; + public static final int SSH_FXP_HANDLE = 102; + public static final int SSH_FXP_DATA = 103; + public static final int SSH_FXP_NAME = 104; + public static final int SSH_FXP_ATTRS = 105; + + public static final int SSH_FXP_EXTENDED = 200; + public static final int SSH_FXP_EXTENDED_REPLY = 201; +} diff --git a/app/src/main/java/com/trilead/ssh2/signature/DSASHA1Verify.java b/app/src/main/java/com/trilead/ssh2/signature/DSASHA1Verify.java new file mode 100644 index 0000000..6fb6ddb --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/signature/DSASHA1Verify.java @@ -0,0 +1,255 @@ + +package com.trilead.ssh2.signature; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.packets.TypesWriter; + + +/** + * DSASHA1Verify. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DSASHA1Verify.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class DSASHA1Verify +{ + private static final Logger log = Logger.getLogger(DSASHA1Verify.class); + + public static DSAPublicKey decodeSSHDSAPublicKey(byte[] key) throws IOException + { + TypesReader tr = new TypesReader(key); + + String key_format = tr.readString(); + + if (key_format.equals("ssh-dss") == false) + throw new IllegalArgumentException("This is not a ssh-dss public key!"); + + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + BigInteger g = tr.readMPINT(); + BigInteger y = tr.readMPINT(); + + if (tr.remain() != 0) + throw new IOException("Padding in DSA public key!"); + + try { + KeyFactory kf = KeyFactory.getInstance("DSA"); + + KeySpec ks = new DSAPublicKeySpec(y, p, q, g); + return (DSAPublicKey) kf.generatePublic(ks); + } catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (InvalidKeySpecException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static byte[] encodeSSHDSAPublicKey(DSAPublicKey pk) throws IOException + { + TypesWriter tw = new TypesWriter(); + + tw.writeString("ssh-dss"); + + DSAParams params = pk.getParams(); + tw.writeMPInt(params.getP()); + tw.writeMPInt(params.getQ()); + tw.writeMPInt(params.getG()); + tw.writeMPInt(pk.getY()); + + return tw.getBytes(); + } + + /** + * Convert from Java's signature ASN.1 encoding to the SSH spec. + * <p> + * Java ASN.1 encoding: + * <pre> + * SEQUENCE ::= { + * r INTEGER, + * s INTEGER + * } + * </pre> + */ + public static byte[] encodeSSHDSASignature(byte[] ds) + { + TypesWriter tw = new TypesWriter(); + + tw.writeString("ssh-dss"); + + int len, index; + + index = 3; + len = ds[index++] & 0xff; + byte[] r = new byte[len]; + System.arraycopy(ds, index, r, 0, r.length); + + index = index + len + 1; + len = ds[index++] & 0xff; + byte[] s = new byte[len]; + System.arraycopy(ds, index, s, 0, s.length); + + byte[] a40 = new byte[40]; + + /* Patch (unsigned) r and s into the target array. */ + + int r_copylen = (r.length < 20) ? r.length : 20; + int s_copylen = (s.length < 20) ? s.length : 20; + + System.arraycopy(r, r.length - r_copylen, a40, 20 - r_copylen, r_copylen); + System.arraycopy(s, s.length - s_copylen, a40, 40 - s_copylen, s_copylen); + + tw.writeString(a40, 0, 40); + + return tw.getBytes(); + } + + public static byte[] decodeSSHDSASignature(byte[] sig) throws IOException + { + byte[] rsArray = null; + + if (sig.length == 40) + { + /* OK, another broken SSH server. */ + rsArray = sig; + } + else + { + /* Hopefully a server obeying the standard... */ + TypesReader tr = new TypesReader(sig); + + String sig_format = tr.readString(); + if (sig_format.equals("ssh-dss") == false) + throw new IOException("Peer sent wrong signature format"); + + rsArray = tr.readByteString(); + + if (rsArray.length != 40) + throw new IOException("Peer sent corrupt signature"); + + if (tr.remain() != 0) + throw new IOException("Padding in DSA signature!"); + } + + int i = 0; + int j = 0; + byte[] tmp; + + if (rsArray[0] == 0 && rsArray[1] == 0 && rsArray[2] == 0) { + j = ((rsArray[i++] << 24) & 0xff000000) | ((rsArray[i++] << 16) & 0x00ff0000) + | ((rsArray[i++] << 8) & 0x0000ff00) | ((rsArray[i++]) & 0x000000ff); + i += j; + j = ((rsArray[i++] << 24) & 0xff000000) | ((rsArray[i++] << 16) & 0x00ff0000) + | ((rsArray[i++] << 8) & 0x0000ff00) | ((rsArray[i++]) & 0x000000ff); + tmp = new byte[j]; + System.arraycopy(rsArray, i, tmp, 0, j); + rsArray = tmp; + } + + /* ASN.1 */ + int frst = ((rsArray[0] & 0x80) != 0 ? 1 : 0); + int scnd = ((rsArray[20] & 0x80) != 0 ? 1 : 0); + + /* Calculate output length */ + int length = rsArray.length + 6 + frst + scnd; + tmp = new byte[length]; + + /* DER-encoding to match Java */ + tmp[0] = (byte) 0x30; + + if (rsArray.length != 40) + throw new IOException("Peer sent corrupt signature"); + /* Calculate length */ + tmp[1] = (byte) 0x2c; + tmp[1] += frst; + tmp[1] += scnd; + + /* First item */ + tmp[2] = (byte) 0x02; + + /* First item length */ + tmp[3] = (byte) 0x14; + tmp[3] += frst; + + /* Copy in the data for first item */ + System.arraycopy(rsArray, 0, tmp, 4 + frst, 20); + + /* Second item */ + tmp[4 + tmp[3]] = (byte) 0x02; + + /* Second item length */ + tmp[5 + tmp[3]] = (byte) 0x14; + tmp[5 + tmp[3]] += scnd; + + /* Copy in the data for the second item */ + System.arraycopy(rsArray, 20, tmp, 6 + tmp[3] + scnd, 20); + + /* Swap buffers */ + rsArray = tmp; + + return rsArray; + } + + public static boolean verifySignature(byte[] message, byte[] ds, DSAPublicKey dpk) throws IOException + { + try { + Signature s = Signature.getInstance("SHA1withDSA"); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } catch (NoSuchAlgorithmException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } catch (InvalidKeyException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static byte[] generateSignature(byte[] message, DSAPrivateKey pk, SecureRandom rnd) throws IOException + { + try { + Signature s = Signature.getInstance("SHA1withDSA"); + s.initSign(pk); + s.update(message); + return s.sign(); + } catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java b/app/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java new file mode 100644 index 0000000..f139cdf --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java @@ -0,0 +1,487 @@ +/** + * + */ +package com.trilead.ssh2.signature; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Map; +import java.util.TreeMap; + +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.packets.TypesWriter; + +/** + * @author Kenny Root + * + */ +public class ECDSASHA2Verify { + private static final Logger log = Logger.getLogger(ECDSASHA2Verify.class); + + public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-"; + + private static final String NISTP256 = "nistp256"; + private static final String NISTP256_OID = "1.2.840.10045.3.1.7"; + private static final String NISTP384 = "nistp384"; + private static final String NISTP384_OID = "1.3.132.0.34"; + private static final String NISTP521 = "nistp521"; + private static final String NISTP521_OID = "1.3.132.0.35"; + + private static final Map<String, ECParameterSpec> CURVES = new TreeMap<String, ECParameterSpec>(); + static { + CURVES.put(NISTP256, EllipticCurves.nistp256); + CURVES.put(NISTP384, EllipticCurves.nistp384); + CURVES.put(NISTP521, EllipticCurves.nistp521); + } + + private static final Map<Integer, String> CURVE_SIZES = new TreeMap<Integer, String>(); + static { + CURVE_SIZES.put(256, NISTP256); + CURVE_SIZES.put(384, NISTP384); + CURVE_SIZES.put(521, NISTP521); + } + + private static final Map<String, String> CURVE_OIDS = new TreeMap<String, String>(); + static { + CURVE_OIDS.put(NISTP256_OID, NISTP256); + CURVE_OIDS.put(NISTP384_OID, NISTP256); + CURVE_OIDS.put(NISTP521_OID, NISTP256); + } + + public static int[] getCurveSizes() { + int[] keys = new int[CURVE_SIZES.size()]; + int i = 0; + for (Integer n : CURVE_SIZES.keySet().toArray(new Integer[keys.length])) { + keys[i++] = n; + } + return keys; + } + + public static ECParameterSpec getCurveForSize(int size) { + final String name = CURVE_SIZES.get(size); + if (name == null) { + return null; + } + return CURVES.get(name); + } + + public static ECPublicKey decodeSSHECDSAPublicKey(byte[] key) throws IOException + { + TypesReader tr = new TypesReader(key); + + String key_format = tr.readString(); + + if (key_format.startsWith(ECDSA_SHA2_PREFIX) == false) + throw new IllegalArgumentException("This is not an ECDSA public key"); + + String curveName = tr.readString(); + byte[] groupBytes = tr.readByteString(); + + if (tr.remain() != 0) + throw new IOException("Padding in ECDSA public key!"); + + if (key_format.equals(ECDSA_SHA2_PREFIX + curveName) == false) { + throw new IOException("Key format is inconsistent with curve name: " + key_format + + " != " + curveName); + } + + ECParameterSpec params = CURVES.get(curveName); + if (params == null) { + throw new IOException("Curve is not supported: " + curveName); + } + + ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, params.getCurve()); + if (group == null) { + throw new IOException("Invalid ECDSA group"); + } + + KeySpec keySpec = new ECPublicKeySpec(group, params); + + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + return (ECPublicKey) kf.generatePublic(keySpec); + } catch (NoSuchAlgorithmException nsae) { + IOException ioe = new IOException("No EC KeyFactory available"); + ioe.initCause(nsae); + throw ioe; + } catch (InvalidKeySpecException ikse) { + IOException ioe = new IOException("No EC KeyFactory available"); + ioe.initCause(ikse); + throw ioe; + } + } + + public static byte[] encodeSSHECDSAPublicKey(ECPublicKey key) throws IOException { + TypesWriter tw = new TypesWriter(); + + String curveName = getCurveName(key.getParams()); + + String keyFormat = ECDSA_SHA2_PREFIX + curveName; + + tw.writeString(keyFormat); + + tw.writeString(curveName); + + byte[] encoded = encodeECPoint(key.getW(), key.getParams().getCurve()); + tw.writeString(encoded, 0, encoded.length); + + return tw.getBytes(); + } + + public static String getCurveName(ECParameterSpec params) throws IOException { + int fieldSize = getCurveSize(params); + final String curveName = getCurveName(fieldSize); + if (curveName == null) { + throw new IOException("invalid curve size " + fieldSize); + } + return curveName; + } + + public static String getCurveName(int fieldSize) { + String curveName = CURVE_SIZES.get(fieldSize); + if (curveName == null) { + return null; + } + return curveName; + } + + public static int getCurveSize(ECParameterSpec params) { + return params.getCurve().getField().getFieldSize(); + } + + public static ECParameterSpec getCurveForOID(String oid) { + String name = CURVE_OIDS.get(oid); + if (name == null) + return null; + return CURVES.get(name); + } + + public static byte[] decodeSSHECDSASignature(byte[] sig) throws IOException { + byte[] rsArray = null; + + TypesReader tr = new TypesReader(sig); + + String sig_format = tr.readString(); + if (sig_format.startsWith(ECDSA_SHA2_PREFIX) == false) + throw new IOException("Peer sent wrong signature format"); + + String curveName = sig_format.substring(ECDSA_SHA2_PREFIX.length()); + if (CURVES.containsKey(curveName) == false) { + throw new IOException("Unsupported curve: " + curveName); + } + + rsArray = tr.readByteString(); + + if (tr.remain() != 0) + throw new IOException("Padding in ECDSA signature!"); + + byte[] rArray; + byte[] sArray; + { + TypesReader rsReader = new TypesReader(rsArray); + rArray = rsReader.readMPINT().toByteArray(); + sArray = rsReader.readMPINT().toByteArray(); + } + + int first = rArray.length; + int second = sArray.length; + + /* We can't have the high bit set, so add an extra zero at the beginning if so. */ + if ((rArray[0] & 0x80) != 0) { + first++; + } + if ((sArray[0] & 0x80) != 0) { + second++; + } + + /* Calculate total output length */ + ByteArrayOutputStream os = new ByteArrayOutputStream(6 + first + second); + + /* ASN.1 SEQUENCE tag */ + os.write(0x30); + + /* Size of SEQUENCE */ + writeLength(4 + first + second, os); + + /* ASN.1 INTEGER tag */ + os.write(0x02); + + /* "r" INTEGER length */ + writeLength(first, os); + + /* Copy in the "r" INTEGER */ + if (first != rArray.length) { + os.write(0x00); + } + os.write(rArray); + + /* ASN.1 INTEGER tag */ + os.write(0x02); + + /* "s" INTEGER length */ + writeLength(second, os); + + /* Copy in the "s" INTEGER */ + if (second != sArray.length) { + os.write(0x00); + } + os.write(sArray); + + return os.toByteArray(); + } + + private static final void writeLength(int length, OutputStream os) throws IOException { + if (length <= 0x7F) { + os.write(length); + return; + } + + int numOctets = 0; + int lenCopy = length; + while (lenCopy != 0) { + lenCopy >>>= 8; + numOctets++; + } + + os.write(0x80 | numOctets); + + for (int i = (numOctets - 1) * 8; i >= 0; i -= 8) { + os.write((byte) (length >> i)); + } + } + + public static byte[] encodeSSHECDSASignature(byte[] sig, ECParameterSpec params) throws IOException + { + TypesWriter tw = new TypesWriter(); + + String curveName = getCurveName(params); + tw.writeString(ECDSA_SHA2_PREFIX + curveName); + + if ((sig[0] != 0x30) || (sig[1] != sig.length - 2) || (sig[2] != 0x02)) { + throw new IOException("Invalid signature format"); + } + + int rLength = sig[3]; + if ((rLength + 6 > sig.length) || (sig[4 + rLength] != 0x02)) { + throw new IOException("Invalid signature format"); + } + + int sLength = sig[5 + rLength]; + if (6 + rLength + sLength > sig.length) { + throw new IOException("Invalid signature format"); + } + + byte[] rArray = new byte[rLength]; + byte[] sArray = new byte[sLength]; + + System.arraycopy(sig, 4, rArray, 0, rLength); + System.arraycopy(sig, 6 + rLength, sArray, 0, sLength); + + BigInteger r = new BigInteger(rArray); + BigInteger s = new BigInteger(sArray); + + // Write the <r,s> to its own types writer. + TypesWriter rsWriter = new TypesWriter(); + rsWriter.writeMPInt(r); + rsWriter.writeMPInt(s); + byte[] encoded = rsWriter.getBytes(); + tw.writeString(encoded, 0, encoded.length); + + return tw.getBytes(); + } + + public static byte[] generateSignature(byte[] message, ECPrivateKey pk) throws IOException + { + final String algo = getSignatureAlgorithmForParams(pk.getParams()); + + try { + Signature s = Signature.getInstance(algo); + s.initSign(pk); + s.update(message); + return s.sign(); + } catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static boolean verifySignature(byte[] message, byte[] ds, ECPublicKey dpk) throws IOException + { + final String algo = getSignatureAlgorithmForParams(dpk.getParams()); + + try { + Signature s = Signature.getInstance(algo); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } catch (NoSuchAlgorithmException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } catch (InvalidKeyException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + private static String getSignatureAlgorithmForParams(ECParameterSpec params) { + int size = getCurveSize(params); + if (size <= 256) { + return "SHA256withECDSA"; + } else if (size <= 384) { + return "SHA384withECDSA"; + } else { + return "SHA512withECDSA"; + } + } + + public static String getDigestAlgorithmForParams(ECParameterSpec params) { + int size = getCurveSize(params); + if (size <= 256) { + return "SHA256"; + } else if (size <= 384) { + return "SHA384"; + } else { + return "SHA512"; + } + } + + /** + * Decode an OctetString to EllipticCurvePoint according to SECG 2.3.4 + */ + public static ECPoint decodeECPoint(byte[] M, EllipticCurve curve) { + if (M.length == 0) { + return null; + } + + // M has len 2 ceil(log_2(q)/8) + 1 ? + int elementSize = (curve.getField().getFieldSize() + 7) / 8; + if (M.length != 2 * elementSize + 1) { + return null; + } + + // step 3.2 + if (M[0] != 0x04) { + return null; + } + + // Step 3.3 + byte[] xp = new byte[elementSize]; + System.arraycopy(M, 1, xp, 0, elementSize); + + // Step 3.4 + byte[] yp = new byte[elementSize]; + System.arraycopy(M, 1 + elementSize, yp, 0, elementSize); + + ECPoint P = new ECPoint(new BigInteger(1, xp), new BigInteger(1, yp)); + + // TODO check point 3.5 + + // Step 3.6 + return P; + } + + /** + * Encode EllipticCurvePoint to an OctetString + */ + public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve) + { + // M has len 2 ceil(log_2(q)/8) + 1 ? + int elementSize = (curve.getField().getFieldSize() + 7) / 8; + byte[] M = new byte[2 * elementSize + 1]; + + // Uncompressed format + M[0] = 0x04; + + { + byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray()); + System.arraycopy(affineX, 0, M, 1 + elementSize - affineX.length, affineX.length); + } + + { + byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray()); + System.arraycopy(affineY, 0, M, 1 + elementSize + elementSize - affineY.length, + affineY.length); + } + + return M; + } + + private static byte[] removeLeadingZeroes(byte[] input) { + if (input[0] != 0x00) { + return input; + } + + int pos = 1; + while (pos < input.length - 1 && input[pos] == 0x00) { + pos++; + } + + byte[] output = new byte[input.length - pos]; + System.arraycopy(input, pos, output, 0, output.length); + return output; + } + + public static class EllipticCurves { + public static ECParameterSpec nistp256 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)), + new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16), + new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)), + new ECPoint(new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16), + new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)), + new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16), + 1); + + public static ECParameterSpec nistp384 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16)), + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16), + new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16)), + new ECPoint(new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16), + new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16)), + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16), + 1); + + public static ECParameterSpec nistp521 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)), + new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16), + new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16)), + new ECPoint(new BigInteger("00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", 16), + new BigInteger("011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", 16)), + new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16), + 1); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/signature/RSASHA1Verify.java b/app/src/main/java/com/trilead/ssh2/signature/RSASHA1Verify.java new file mode 100644 index 0000000..3406312 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/signature/RSASHA1Verify.java @@ -0,0 +1,180 @@ + +package com.trilead.ssh2.signature; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPublicKeySpec; + +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.packets.TypesWriter; + + +/** + * RSASHA1Verify. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RSASHA1Verify.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class RSASHA1Verify +{ + private static final Logger log = Logger.getLogger(RSASHA1Verify.class); + + public static RSAPublicKey decodeSSHRSAPublicKey(byte[] key) throws IOException + { + TypesReader tr = new TypesReader(key); + + String key_format = tr.readString(); + + if (key_format.equals("ssh-rsa") == false) + throw new IllegalArgumentException("This is not a ssh-rsa public key"); + + BigInteger e = tr.readMPINT(); + BigInteger n = tr.readMPINT(); + + if (tr.remain() != 0) + throw new IOException("Padding in RSA public key!"); + + KeySpec keySpec = new RSAPublicKeySpec(n, e); + + try { + KeyFactory kf = KeyFactory.getInstance("RSA"); + return (RSAPublicKey) kf.generatePublic(keySpec); + } catch (NoSuchAlgorithmException nsae) { + IOException ioe = new IOException("No RSA KeyFactory available"); + ioe.initCause(nsae); + throw ioe; + } catch (InvalidKeySpecException ikse) { + IOException ioe = new IOException("No RSA KeyFactory available"); + ioe.initCause(ikse); + throw ioe; + } + } + + public static byte[] encodeSSHRSAPublicKey(RSAPublicKey pk) throws IOException + { + TypesWriter tw = new TypesWriter(); + + tw.writeString("ssh-rsa"); + tw.writeMPInt(pk.getPublicExponent()); + tw.writeMPInt(pk.getModulus()); + + return tw.getBytes(); + } + + public static byte[] decodeSSHRSASignature(byte[] sig) throws IOException + { + TypesReader tr = new TypesReader(sig); + + String sig_format = tr.readString(); + + if (sig_format.equals("ssh-rsa") == false) + throw new IOException("Peer sent wrong signature format"); + + /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string + * containing s (which is an integer, without lengths or padding, unsigned and in + * network byte order)." See also below. + */ + + byte[] s = tr.readByteString(); + + if (s.length == 0) + throw new IOException("Error in RSA signature, S is empty."); + + if (log.isEnabled()) + { + log.log(80, "Decoding ssh-rsa signature string (length: " + s.length + ")"); + } + + if (tr.remain() != 0) + throw new IOException("Padding in RSA signature!"); + + if (s[0] == 0 && s[1] == 0 && s[2] == 0) { + int i = 0; + int j = ((s[i++] << 24) & 0xff000000) | ((s[i++] << 16) & 0x00ff0000) + | ((s[i++] << 8) & 0x0000ff00) | ((s[i++]) & 0x000000ff); + i += j; + j = ((s[i++] << 24) & 0xff000000) | ((s[i++] << 16) & 0x00ff0000) + | ((s[i++] << 8) & 0x0000ff00) | ((s[i++]) & 0x000000ff); + byte[] tmp = new byte[j]; + System.arraycopy(s, i, tmp, 0, j); + sig = tmp; + } + + return s; + } + + public static byte[] encodeSSHRSASignature(byte[] s) throws IOException + { + TypesWriter tw = new TypesWriter(); + + tw.writeString("ssh-rsa"); + + /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string + * containing s (which is an integer, without lengths or padding, unsigned and in + * network byte order)." + */ + + /* Remove first zero sign byte, if present */ + + if ((s.length > 1) && (s[0] == 0x00)) + tw.writeString(s, 1, s.length - 1); + else + tw.writeString(s, 0, s.length); + + return tw.getBytes(); + } + + public static byte[] generateSignature(byte[] message, RSAPrivateKey pk) throws IOException + { + try { + Signature s = Signature.getInstance("SHA1withRSA"); + s.initSign(pk); + s.update(message); + return s.sign(); + } catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static boolean verifySignature(byte[] message, byte[] ds, RSAPublicKey dpk) throws IOException + { + try { + Signature s = Signature.getInstance("SHA1withRSA"); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/ClientServerHello.java b/app/src/main/java/com/trilead/ssh2/transport/ClientServerHello.java new file mode 100644 index 0000000..d7a5ee5 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/ClientServerHello.java @@ -0,0 +1,125 @@ + +package com.trilead.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import com.trilead.ssh2.Connection; + +/** + * ClientServerHello. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ClientServerHello.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class ClientServerHello +{ + String server_line; + String client_line; + + String server_versioncomment; + + public final static int readLineRN(InputStream is, byte[] buffer) throws IOException + { + int pos = 0; + boolean need10 = false; + int len = 0; + while (true) + { + int c = is.read(); + if (c == -1) + throw new IOException("Premature connection close"); + + buffer[pos++] = (byte) c; + + if (c == 13) + { + need10 = true; + continue; + } + + if (c == 10) + break; + + if (need10 == true) + throw new IOException("Malformed line sent by the server, the line does not end correctly."); + + len++; + if (pos >= buffer.length) + throw new IOException("The server sent a too long line."); + } + + return len; + } + + public ClientServerHello(InputStream bi, OutputStream bo) throws IOException + { + client_line = "SSH-2.0-" + Connection.identification; + + bo.write((client_line + "\r\n").getBytes("ISO-8859-1")); + bo.flush(); + + byte[] serverVersion = new byte[512]; + + for (int i = 0; i < 50; i++) + { + int len = readLineRN(bi, serverVersion); + + server_line = new String(serverVersion, 0, len, "ISO-8859-1"); + + if (server_line.startsWith("SSH-")) + break; + } + + if (server_line.startsWith("SSH-") == false) + throw new IOException( + "Malformed server identification string. There was no line starting with 'SSH-' amongst the first 50 lines."); + + if (server_line.startsWith("SSH-1.99-")) + server_versioncomment = server_line.substring(9); + else if (server_line.startsWith("SSH-2.0-")) + server_versioncomment = server_line.substring(8); + else + throw new IOException("Server uses incompatible protocol, it is not SSH-2 compatible."); + } + + /** + * @return Returns the client_versioncomment. + */ + public byte[] getClientString() + { + byte[] result; + + try + { + result = client_line.getBytes("ISO-8859-1"); + } + catch (UnsupportedEncodingException ign) + { + result = client_line.getBytes(); + } + + return result; + } + + /** + * @return Returns the server_versioncomment. + */ + public byte[] getServerString() + { + byte[] result; + + try + { + result = server_line.getBytes("ISO-8859-1"); + } + catch (UnsupportedEncodingException ign) + { + result = server_line.getBytes(); + } + + return result; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/KexManager.java b/app/src/main/java/com/trilead/ssh2/transport/KexManager.java new file mode 100644 index 0000000..cd26530 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/KexManager.java @@ -0,0 +1,671 @@ + +package com.trilead.ssh2.transport; + +import java.io.IOException; +import java.security.SecureRandom; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Set; +import java.util.TreeSet; + +import com.trilead.ssh2.ConnectionInfo; +import com.trilead.ssh2.DHGexParameters; +import com.trilead.ssh2.ServerHostKeyVerifier; +import com.trilead.ssh2.compression.CompressionFactory; +import com.trilead.ssh2.compression.ICompressor; +import com.trilead.ssh2.crypto.CryptoWishList; +import com.trilead.ssh2.crypto.KeyMaterial; +import com.trilead.ssh2.crypto.cipher.BlockCipher; +import com.trilead.ssh2.crypto.cipher.BlockCipherFactory; +import com.trilead.ssh2.crypto.dh.DhGroupExchange; +import com.trilead.ssh2.crypto.dh.GenericDhExchange; +import com.trilead.ssh2.crypto.digest.MAC; +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.PacketKexDHInit; +import com.trilead.ssh2.packets.PacketKexDHReply; +import com.trilead.ssh2.packets.PacketKexDhGexGroup; +import com.trilead.ssh2.packets.PacketKexDhGexInit; +import com.trilead.ssh2.packets.PacketKexDhGexReply; +import com.trilead.ssh2.packets.PacketKexDhGexRequest; +import com.trilead.ssh2.packets.PacketKexDhGexRequestOld; +import com.trilead.ssh2.packets.PacketKexInit; +import com.trilead.ssh2.packets.PacketNewKeys; +import com.trilead.ssh2.packets.Packets; +import com.trilead.ssh2.signature.DSASHA1Verify; +import com.trilead.ssh2.signature.ECDSASHA2Verify; +import com.trilead.ssh2.signature.RSASHA1Verify; + + +/** + * KexManager. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: KexManager.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class KexManager +{ + private static final Logger log = Logger.getLogger(KexManager.class); + + private static final Set<String> HOSTKEY_ALGS = new TreeSet<String>(); + static { + HOSTKEY_ALGS.add("ecdsa-sha2-nistp256"); + HOSTKEY_ALGS.add("ecdsa-sha2-nistp384"); + HOSTKEY_ALGS.add("ecdsa-sha2-nistp521"); + HOSTKEY_ALGS.add("ssh-rsa"); + HOSTKEY_ALGS.add("ssh-dsa"); + } + + private static final Set<String> KEX_ALGS = new TreeSet<String>(); + static { + KEX_ALGS.add("ecdh-sha2-nistp256"); + KEX_ALGS.add("ecdh-sha2-nistp384"); + KEX_ALGS.add("ecdh-sha2-nistp521"); + KEX_ALGS.add("diffie-hellman-group-exchange-sha256"); + KEX_ALGS.add("diffie-hellman-group-exchange-sha1"); + KEX_ALGS.add("diffie-hellman-group14-sha1"); + KEX_ALGS.add("diffie-hellman-group1-sha1"); + } + + KexState kxs; + int kexCount = 0; + KeyMaterial km; + byte[] sessionId; + ClientServerHello csh; + + final Object accessLock = new Object(); + ConnectionInfo lastConnInfo = null; + + boolean connectionClosed = false; + + boolean ignore_next_kex_packet = false; + + final TransportManager tm; + + CryptoWishList nextKEXcryptoWishList; + DHGexParameters nextKEXdhgexParameters; + + ServerHostKeyVerifier verifier; + final String hostname; + final int port; + final SecureRandom rnd; + + public KexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, String hostname, int port, + ServerHostKeyVerifier keyVerifier, SecureRandom rnd) + { + this.tm = tm; + this.csh = csh; + this.nextKEXcryptoWishList = initialCwl; + this.nextKEXdhgexParameters = new DHGexParameters(); + this.hostname = hostname; + this.port = port; + this.verifier = keyVerifier; + this.rnd = rnd; + } + + public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount) throws IOException + { + synchronized (accessLock) + { + while (true) + { + if ((lastConnInfo != null) && (lastConnInfo.keyExchangeCounter >= minKexCount)) + return lastConnInfo; + + if (connectionClosed) + throw (IOException) new IOException("Key exchange was not finished, connection is closed.") + .initCause(tm.getReasonClosedCause()); + + try + { + accessLock.wait(); + } + catch (InterruptedException e) + { + } + } + } + } + + private String getFirstMatch(String[] client, String[] server) throws NegotiateException + { + if (client == null || server == null) + throw new IllegalArgumentException(); + + if (client.length == 0) + return null; + + for (int i = 0; i < client.length; i++) + { + for (int j = 0; j < server.length; j++) + { + if (client[i].equals(server[j])) + return client[i]; + } + } + throw new NegotiateException(); + } + + private boolean compareFirstOfNameList(String[] a, String[] b) + { + if (a == null || b == null) + throw new IllegalArgumentException(); + + if ((a.length == 0) && (b.length == 0)) + return true; + + if ((a.length == 0) || (b.length == 0)) + return false; + + return (a[0].equals(b[0])); + } + + private boolean isGuessOK(KexParameters cpar, KexParameters spar) + { + if (cpar == null || spar == null) + throw new IllegalArgumentException(); + + if (compareFirstOfNameList(cpar.kex_algorithms, spar.kex_algorithms) == false) + { + return false; + } + + if (compareFirstOfNameList(cpar.server_host_key_algorithms, spar.server_host_key_algorithms) == false) + { + return false; + } + + /* + * We do NOT check here if the other algorithms can be agreed on, this + * is just a check if kex_algorithms and server_host_key_algorithms were + * guessed right! + */ + + return true; + } + + private NegotiatedParameters mergeKexParameters(KexParameters client, KexParameters server) + { + NegotiatedParameters np = new NegotiatedParameters(); + + try + { + np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms); + + log.log(20, "kex_algo=" + np.kex_algo); + + np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms, + server.server_host_key_algorithms); + + log.log(20, "server_host_key_algo=" + np.server_host_key_algo); + + np.enc_algo_client_to_server = getFirstMatch(client.encryption_algorithms_client_to_server, + server.encryption_algorithms_client_to_server); + np.enc_algo_server_to_client = getFirstMatch(client.encryption_algorithms_server_to_client, + server.encryption_algorithms_server_to_client); + + log.log(20, "enc_algo_client_to_server=" + np.enc_algo_client_to_server); + log.log(20, "enc_algo_server_to_client=" + np.enc_algo_server_to_client); + + np.mac_algo_client_to_server = getFirstMatch(client.mac_algorithms_client_to_server, + server.mac_algorithms_client_to_server); + np.mac_algo_server_to_client = getFirstMatch(client.mac_algorithms_server_to_client, + server.mac_algorithms_server_to_client); + + log.log(20, "mac_algo_client_to_server=" + np.mac_algo_client_to_server); + log.log(20, "mac_algo_server_to_client=" + np.mac_algo_server_to_client); + + np.comp_algo_client_to_server = getFirstMatch(client.compression_algorithms_client_to_server, + server.compression_algorithms_client_to_server); + np.comp_algo_server_to_client = getFirstMatch(client.compression_algorithms_server_to_client, + server.compression_algorithms_server_to_client); + + log.log(20, "comp_algo_client_to_server=" + np.comp_algo_client_to_server); + log.log(20, "comp_algo_server_to_client=" + np.comp_algo_server_to_client); + + } + catch (NegotiateException e) + { + return null; + } + + try + { + np.lang_client_to_server = getFirstMatch(client.languages_client_to_server, + server.languages_client_to_server); + } + catch (NegotiateException e1) + { + np.lang_client_to_server = null; + } + + try + { + np.lang_server_to_client = getFirstMatch(client.languages_server_to_client, + server.languages_server_to_client); + } + catch (NegotiateException e2) + { + np.lang_server_to_client = null; + } + + if (isGuessOK(client, server)) + np.guessOK = true; + + return np; + } + + public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex) throws IOException + { + nextKEXcryptoWishList = cwl; + nextKEXdhgexParameters = dhgex; + + if (kxs == null) + { + kxs = new KexState(); + + kxs.dhgexParameters = nextKEXdhgexParameters; + PacketKexInit kp = new PacketKexInit(nextKEXcryptoWishList); + kxs.localKEX = kp; + tm.sendKexMessage(kp.getPayload()); + } + } + + private boolean establishKeyMaterial() + { + try + { + int mac_cs_key_len = MAC.getKeyLen(kxs.np.mac_algo_client_to_server); + int enc_cs_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_client_to_server); + int enc_cs_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_client_to_server); + + int mac_sc_key_len = MAC.getKeyLen(kxs.np.mac_algo_server_to_client); + int enc_sc_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_server_to_client); + int enc_sc_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_server_to_client); + + km = KeyMaterial.create(kxs.hashAlgo, kxs.H, kxs.K, sessionId, enc_cs_key_len, enc_cs_block_len, mac_cs_key_len, + enc_sc_key_len, enc_sc_block_len, mac_sc_key_len); + } + catch (IllegalArgumentException e) + { + return false; + } + return true; + } + + private void finishKex() throws IOException + { + if (sessionId == null) + sessionId = kxs.H; + + establishKeyMaterial(); + + /* Tell the other side that we start using the new material */ + + PacketNewKeys ign = new PacketNewKeys(); + tm.sendKexMessage(ign.getPayload()); + + BlockCipher cbc; + MAC mac; + ICompressor comp; + + try + { + cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_client_to_server, true, km.enc_key_client_to_server, + km.initial_iv_client_to_server); + + mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server); + + comp = CompressionFactory.createCompressor(kxs.np.comp_algo_client_to_server); + + } + catch (IllegalArgumentException e1) + { + throw new IOException("Fatal error during MAC startup!"); + } + + tm.changeSendCipher(cbc, mac); + tm.changeSendCompression(comp); + tm.kexFinished(); + } + + public static final String[] getDefaultServerHostkeyAlgorithmList() + { + return HOSTKEY_ALGS.toArray(new String[HOSTKEY_ALGS.size()]); + } + + public static final void checkServerHostkeyAlgorithmsList(String[] algos) + { + for (int i = 0; i < algos.length; i++) + { + if (!HOSTKEY_ALGS.contains(algos[i])) + throw new IllegalArgumentException("Unknown server host key algorithm '" + algos[i] + "'"); + } + } + + public static final String[] getDefaultKexAlgorithmList() + { + return KEX_ALGS.toArray(new String[KEX_ALGS.size()]); + } + + public static final void checkKexAlgorithmList(String[] algos) + { + for (int i = 0; i < algos.length; i++) + { + if (!KEX_ALGS.contains(algos[i])) + throw new IllegalArgumentException("Unknown kex algorithm '" + algos[i] + "'"); + } + } + + private boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException + { + if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) + { + byte[] rs = ECDSASHA2Verify.decodeSSHECDSASignature(sig); + ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(hostkey); + + log.log(50, "Verifying ecdsa signature"); + + return ECDSASHA2Verify.verifySignature(kxs.H, rs, epk); + } + + if (kxs.np.server_host_key_algo.equals("ssh-rsa")) + { + byte[] rs = RSASHA1Verify.decodeSSHRSASignature(sig); + RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey); + + log.log(50, "Verifying ssh-rsa signature"); + + return RSASHA1Verify.verifySignature(kxs.H, rs, rpk); + } + + if (kxs.np.server_host_key_algo.equals("ssh-dss")) + { + byte[] ds = DSASHA1Verify.decodeSSHDSASignature(sig); + DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(hostkey); + + log.log(50, "Verifying ssh-dss signature"); + + return DSASHA1Verify.verifySignature(kxs.H, ds, dpk); + } + + throw new IOException("Unknown server host key algorithm '" + kxs.np.server_host_key_algo + "'"); + } + + public synchronized void handleMessage(byte[] msg, int msglen) throws IOException + { + PacketKexInit kip; + + if (msg == null) + { + synchronized (accessLock) + { + connectionClosed = true; + accessLock.notifyAll(); + return; + } + } + + if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT)) + throw new IOException("Unexpected KEX message (type " + msg[0] + ")"); + + if (ignore_next_kex_packet) + { + ignore_next_kex_packet = false; + return; + } + + if (msg[0] == Packets.SSH_MSG_KEXINIT) + { + if ((kxs != null) && (kxs.state != 0)) + throw new IOException("Unexpected SSH_MSG_KEXINIT message during on-going kex exchange!"); + + if (kxs == null) + { + /* + * Ah, OK, peer wants to do KEX. Let's be nice and play + * together. + */ + kxs = new KexState(); + kxs.dhgexParameters = nextKEXdhgexParameters; + kip = new PacketKexInit(nextKEXcryptoWishList); + kxs.localKEX = kip; + tm.sendKexMessage(kip.getPayload()); + } + + kip = new PacketKexInit(msg, 0, msglen); + kxs.remoteKEX = kip; + + kxs.np = mergeKexParameters(kxs.localKEX.getKexParameters(), kxs.remoteKEX.getKexParameters()); + + if (kxs.np == null) + throw new IOException("Cannot negotiate, proposals do not match."); + + if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false)) + { + /* + * Guess was wrong, we need to ignore the next kex packet. + */ + + ignore_next_kex_packet = true; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1") + || kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha256")) + { + if (kxs.dhgexParameters.getMin_group_len() == 0 || csh.server_versioncomment.matches("OpenSSH_2\\.([0-4]\\.|5\\.[0-2]).*")) + { + PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(kxs.dhgexParameters); + tm.sendKexMessage(dhgexreq.getPayload()); + } + else + { + PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(kxs.dhgexParameters); + tm.sendKexMessage(dhgexreq.getPayload()); + } + if (kxs.np.kex_algo.endsWith("sha1")) { + kxs.hashAlgo = "SHA1"; + } else { + kxs.hashAlgo = "SHA-256"; + } + kxs.state = 1; + return; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") + || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") + || kxs.np.kex_algo.equals("ecdh-sha2-nistp256") + || kxs.np.kex_algo.equals("ecdh-sha2-nistp384") + || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo); + + kxs.dhx.init(kxs.np.kex_algo); + kxs.hashAlgo = kxs.dhx.getHashAlgo(); + + PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE()); + tm.sendKexMessage(kp.getPayload()); + kxs.state = 1; + return; + } + + throw new IllegalStateException("Unknown KEX method!"); + } + + if (msg[0] == Packets.SSH_MSG_NEWKEYS) + { + if (km == null) + throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!"); + + BlockCipher cbc; + MAC mac; + ICompressor comp; + + try + { + cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_server_to_client, false, + km.enc_key_server_to_client, km.initial_iv_server_to_client); + + mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client); + + comp = CompressionFactory.createCompressor(kxs.np.comp_algo_server_to_client); + } + catch (IllegalArgumentException e1) + { + throw new IOException("Fatal error during MAC startup!"); + } + + tm.changeRecvCipher(cbc, mac); + tm.changeRecvCompression(comp); + + ConnectionInfo sci = new ConnectionInfo(); + + kexCount++; + + sci.keyExchangeAlgorithm = kxs.np.kex_algo; + sci.keyExchangeCounter = kexCount; + sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server; + sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client; + sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server; + sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client; + sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo; + sci.serverHostKey = kxs.hostkey; + + synchronized (accessLock) + { + lastConnInfo = sci; + accessLock.notifyAll(); + } + + kxs = null; + return; + } + + if ((kxs == null) || (kxs.state == 0)) + throw new IOException("Unexpected Kex submessage!"); + + if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1") + || kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha256")) + { + if (kxs.state == 1) + { + PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(msg, 0, msglen); + kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(), dhgexgrp.getG()); + kxs.dhgx.init(rnd); + PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(kxs.dhgx.getE()); + tm.sendKexMessage(dhgexinit.getPayload()); + kxs.state = 2; + return; + } + + if (kxs.state == 2) + { + PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(msg, 0, msglen); + + kxs.hostkey = dhgexrpl.getHostKey(); + + if (verifier != null) + { + boolean vres = false; + + try + { + vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey); + } + catch (Exception e) + { + throw (IOException) new IOException( + "The server hostkey was not accepted by the verifier callback.").initCause(e); + } + + if (vres == false) + throw new IOException("The server hostkey was not accepted by the verifier callback"); + } + + kxs.dhgx.setF(dhgexrpl.getF()); + + try + { + kxs.H = kxs.dhgx.calculateH(kxs.hashAlgo, + csh.getClientString(), csh.getServerString(), + kxs.localKEX.getPayload(), kxs.remoteKEX.getPayload(), + dhgexrpl.getHostKey(), kxs.dhgexParameters); + } + catch (IllegalArgumentException e) + { + throw (IOException) new IOException("KEX error.").initCause(e); + } + + boolean res = verifySignature(dhgexrpl.getSignature(), kxs.hostkey); + + if (res == false) + throw new IOException("Hostkey signature sent by remote is wrong!"); + + kxs.K = kxs.dhgx.getK(); + + finishKex(); + kxs.state = -1; + return; + } + + throw new IllegalStateException("Illegal State in KEX Exchange!"); + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") + || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") + || kxs.np.kex_algo.equals("ecdh-sha2-nistp256") + || kxs.np.kex_algo.equals("ecdh-sha2-nistp384") + || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) + { + if (kxs.state == 1) + { + + PacketKexDHReply dhr = new PacketKexDHReply(msg, 0, msglen); + + kxs.hostkey = dhr.getHostKey(); + + if (verifier != null) + { + boolean vres = false; + + try + { + vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey); + } + catch (Exception e) + { + throw (IOException) new IOException( + "The server hostkey was not accepted by the verifier callback.").initCause(e); + } + + if (vres == false) + throw new IOException("The server hostkey was not accepted by the verifier callback"); + } + + kxs.dhx.setF(dhr.getF()); + + try + { + kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), kxs.localKEX.getPayload(), + kxs.remoteKEX.getPayload(), dhr.getHostKey()); + } + catch (IllegalArgumentException e) + { + throw (IOException) new IOException("KEX error.").initCause(e); + } + + boolean res = verifySignature(dhr.getSignature(), kxs.hostkey); + + if (res == false) + throw new IOException("Hostkey signature sent by remote is wrong!"); + + kxs.K = kxs.dhx.getK(); + + finishKex(); + kxs.state = -1; + return; + } + } + + throw new IllegalStateException("Unkown KEX method! (" + kxs.np.kex_algo + ")"); + } +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/KexParameters.java b/app/src/main/java/com/trilead/ssh2/transport/KexParameters.java new file mode 100644 index 0000000..70bcf3e --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/KexParameters.java @@ -0,0 +1,24 @@ +package com.trilead.ssh2.transport; + +/** + * KexParameters. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: KexParameters.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class KexParameters +{ + public byte[] cookie; + public String[] kex_algorithms; + public String[] server_host_key_algorithms; + public String[] encryption_algorithms_client_to_server; + public String[] encryption_algorithms_server_to_client; + public String[] mac_algorithms_client_to_server; + public String[] mac_algorithms_server_to_client; + public String[] compression_algorithms_client_to_server; + public String[] compression_algorithms_server_to_client; + public String[] languages_client_to_server; + public String[] languages_server_to_client; + public boolean first_kex_packet_follows; + public int reserved_field1; +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/KexState.java b/app/src/main/java/com/trilead/ssh2/transport/KexState.java new file mode 100644 index 0000000..8611f3f --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/KexState.java @@ -0,0 +1,33 @@ +package com.trilead.ssh2.transport; + + +import java.math.BigInteger; + +import com.trilead.ssh2.DHGexParameters; +import com.trilead.ssh2.crypto.dh.DhGroupExchange; +import com.trilead.ssh2.crypto.dh.GenericDhExchange; +import com.trilead.ssh2.packets.PacketKexInit; + +/** + * KexState. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: KexState.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class KexState +{ + public PacketKexInit localKEX; + public PacketKexInit remoteKEX; + public NegotiatedParameters np; + public int state = 0; + + public BigInteger K; + public byte[] H; + + public byte[] hostkey; + + public String hashAlgo; + public GenericDhExchange dhx; + public DhGroupExchange dhgx; + public DHGexParameters dhgexParameters; +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/MessageHandler.java b/app/src/main/java/com/trilead/ssh2/transport/MessageHandler.java new file mode 100644 index 0000000..039d473 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/MessageHandler.java @@ -0,0 +1,14 @@ +package com.trilead.ssh2.transport; + +import java.io.IOException; + +/** + * MessageHandler. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: MessageHandler.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public interface MessageHandler +{ + public void handleMessage(byte[] msg, int msglen) throws IOException; +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/NegotiateException.java b/app/src/main/java/com/trilead/ssh2/transport/NegotiateException.java new file mode 100644 index 0000000..ff53097 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/NegotiateException.java @@ -0,0 +1,12 @@ +package com.trilead.ssh2.transport; + +/** + * NegotiateException. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: NegotiateException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class NegotiateException extends Exception +{ + private static final long serialVersionUID = 3689910669428143157L; +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java b/app/src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java new file mode 100644 index 0000000..e9f3a0a --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java @@ -0,0 +1,22 @@ +package com.trilead.ssh2.transport; + +/** + * NegotiatedParameters. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: NegotiatedParameters.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class NegotiatedParameters +{ + public boolean guessOK; + public String kex_algo; + public String server_host_key_algo; + public String enc_algo_client_to_server; + public String enc_algo_server_to_client; + public String mac_algo_client_to_server; + public String mac_algo_server_to_client; + public String comp_algo_client_to_server; + public String comp_algo_server_to_client; + public String lang_client_to_server; + public String lang_server_to_client; +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/TransportConnection.java b/app/src/main/java/com/trilead/ssh2/transport/TransportConnection.java new file mode 100644 index 0000000..906c3c9 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/TransportConnection.java @@ -0,0 +1,343 @@ + +package com.trilead.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import com.trilead.ssh2.compression.ICompressor; +import com.trilead.ssh2.crypto.cipher.BlockCipher; +import com.trilead.ssh2.crypto.cipher.CipherInputStream; +import com.trilead.ssh2.crypto.cipher.CipherOutputStream; +import com.trilead.ssh2.crypto.cipher.NullCipher; +import com.trilead.ssh2.crypto.digest.MAC; +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.Packets; + + +/** + * TransportConnection. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: TransportConnection.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class TransportConnection +{ + private static final Logger log = Logger.getLogger(TransportConnection.class); + + int send_seq_number = 0; + + int recv_seq_number = 0; + + CipherInputStream cis; + + CipherOutputStream cos; + + boolean useRandomPadding = false; + + /* Depends on current MAC and CIPHER */ + + MAC send_mac; + + byte[] send_mac_buffer; + + int send_padd_blocksize = 8; + + MAC recv_mac; + + byte[] recv_mac_buffer; + + byte[] recv_mac_buffer_cmp; + + int recv_padd_blocksize = 8; + + ICompressor recv_comp = null; + + ICompressor send_comp = null; + + boolean can_recv_compress = false; + + boolean can_send_compress = false; + + byte[] recv_comp_buffer; + + byte[] send_comp_buffer; + + /* won't change */ + + final byte[] send_padding_buffer = new byte[256]; + + final byte[] send_packet_header_buffer = new byte[5]; + + final byte[] recv_padding_buffer = new byte[256]; + + final byte[] recv_packet_header_buffer = new byte[5]; + + boolean recv_packet_header_present = false; + + ClientServerHello csh; + + final SecureRandom rnd; + + public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd) + { + this.cis = new CipherInputStream(new NullCipher(), is); + this.cos = new CipherOutputStream(new NullCipher(), os); + this.rnd = rnd; + } + + public void changeRecvCipher(BlockCipher bc, MAC mac) + { + cis.changeCipher(bc); + recv_mac = mac; + recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null; + recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null; + recv_padd_blocksize = bc.getBlockSize(); + if (recv_padd_blocksize < 8) + recv_padd_blocksize = 8; + } + + public void changeSendCipher(BlockCipher bc, MAC mac) + { + if ((bc instanceof NullCipher) == false) + { + /* Only use zero byte padding for the first few packets */ + useRandomPadding = true; + /* Once we start encrypting, there is no way back */ + } + + cos.changeCipher(bc); + send_mac = mac; + send_mac_buffer = (mac != null) ? new byte[mac.size()] : null; + send_padd_blocksize = bc.getBlockSize(); + if (send_padd_blocksize < 8) + send_padd_blocksize = 8; + } + + public void changeRecvCompression(ICompressor comp) + { + recv_comp = comp; + + if (comp != null) { + recv_comp_buffer = new byte[comp.getBufferSize()]; + can_recv_compress |= recv_comp.canCompressPreauth(); + } + } + + public void changeSendCompression(ICompressor comp) + { + send_comp = comp; + + if (comp != null) { + send_comp_buffer = new byte[comp.getBufferSize()]; + can_send_compress |= send_comp.canCompressPreauth(); + } + } + + public void sendMessage(byte[] message) throws IOException + { + sendMessage(message, 0, message.length, 0); + } + + public void sendMessage(byte[] message, int off, int len) throws IOException + { + sendMessage(message, off, len, 0); + } + + public int getPacketOverheadEstimate() + { + // return an estimate for the paket overhead (for send operations) + return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length; + } + + public void sendMessage(byte[] message, int off, int len, int padd) throws IOException + { + if (padd < 4) + padd = 4; + else if (padd > 64) + padd = 64; + + if (send_comp != null && can_send_compress) { + if (send_comp_buffer.length < message.length + 1024) + send_comp_buffer = new byte[message.length + 1024]; + len = send_comp.compress(message, off, len, send_comp_buffer); + message = send_comp_buffer; + } + + int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ + + int slack = packet_len % send_padd_blocksize; + + if (slack != 0) + { + packet_len += (send_padd_blocksize - slack); + } + + if (packet_len < 16) + packet_len = 16; + + int padd_len = packet_len - (5 + len); + + if (useRandomPadding) + { + for (int i = 0; i < padd_len; i = i + 4) + { + /* + * don't waste calls to rnd.nextInt() (by using only 8bit of the + * output). just believe me: even though we may write here up to 3 + * bytes which won't be used, there is no "buffer overflow" (i.e., + * arrayindexoutofbounds). the padding buffer is big enough =) (256 + * bytes, and that is bigger than any current cipher block size + 64). + */ + + int r = rnd.nextInt(); + send_padding_buffer[i] = (byte) r; + send_padding_buffer[i + 1] = (byte) (r >> 8); + send_padding_buffer[i + 2] = (byte) (r >> 16); + send_padding_buffer[i + 3] = (byte) (r >> 24); + } + } + else + { + /* use zero padding for unencrypted traffic */ + for (int i = 0; i < padd_len; i++) + send_padding_buffer[i] = 0; + /* Actually this code is paranoid: we never filled any + * bytes into the padding buffer so far, therefore it should + * consist of zeros only. + */ + } + + send_packet_header_buffer[0] = (byte) ((packet_len - 4) >> 24); + send_packet_header_buffer[1] = (byte) ((packet_len - 4) >> 16); + send_packet_header_buffer[2] = (byte) ((packet_len - 4) >> 8); + send_packet_header_buffer[3] = (byte) ((packet_len - 4)); + send_packet_header_buffer[4] = (byte) padd_len; + + cos.write(send_packet_header_buffer, 0, 5); + cos.write(message, off, len); + cos.write(send_padding_buffer, 0, padd_len); + + if (send_mac != null) + { + send_mac.initMac(send_seq_number); + send_mac.update(send_packet_header_buffer, 0, 5); + send_mac.update(message, off, len); + send_mac.update(send_padding_buffer, 0, padd_len); + + send_mac.getMac(send_mac_buffer, 0); + cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length); + } + + cos.flush(); + + if (log.isEnabled()) + { + log.log(90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload"); + } + + send_seq_number++; + } + + public int peekNextMessageLength() throws IOException + { + if (recv_packet_header_present == false) + { + cis.read(recv_packet_header_buffer, 0, 5); + recv_packet_header_present = true; + } + + int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) + | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) + | ((recv_packet_header_buffer[3] & 0xff)); + + int padding_length = recv_packet_header_buffer[4] & 0xff; + + if (packet_length > 35000 || packet_length < 12) + throw new IOException("Illegal packet size! (" + packet_length + ")"); + + int payload_length = packet_length - padding_length - 1; + + if (payload_length < 0) + throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")"); + + return payload_length; + } + + public int receiveMessage(byte buffer[], int off, int len) throws IOException + { + if (recv_packet_header_present == false) + { + cis.read(recv_packet_header_buffer, 0, 5); + } + else + recv_packet_header_present = false; + + int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) + | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) + | ((recv_packet_header_buffer[3] & 0xff)); + + int padding_length = recv_packet_header_buffer[4] & 0xff; + + if (packet_length > 35000 || packet_length < 12) + throw new IOException("Illegal packet size! (" + packet_length + ")"); + + int payload_length = packet_length - padding_length - 1; + + if (payload_length < 0) + throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")"); + + if (payload_length >= len) + throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")"); + + cis.read(buffer, off, payload_length); + cis.read(recv_padding_buffer, 0, padding_length); + + if (recv_mac != null) + { + cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length); + + recv_mac.initMac(recv_seq_number); + recv_mac.update(recv_packet_header_buffer, 0, 5); + recv_mac.update(buffer, off, payload_length); + recv_mac.update(recv_padding_buffer, 0, padding_length); + recv_mac.getMac(recv_mac_buffer_cmp, 0); + + for (int i = 0; i < recv_mac_buffer.length; i++) + { + if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i]) + throw new IOException("Remote sent corrupt MAC."); + } + } + + recv_seq_number++; + + if (log.isEnabled()) + { + log.log(90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length + + " bytes payload"); + } + + if (recv_comp != null && can_recv_compress) { + int[] uncomp_len = new int[] { payload_length }; + buffer = recv_comp.uncompress(buffer, off, uncomp_len); + + if (buffer == null) { + throw new IOException("Error while inflating remote data"); + } else { + return uncomp_len[0]; + } + } else { + return payload_length; + } + } + + /** + * + */ + public void startCompression() { + can_recv_compress = true; + can_send_compress = true; + } +} diff --git a/app/src/main/java/com/trilead/ssh2/transport/TransportManager.java b/app/src/main/java/com/trilead/ssh2/transport/TransportManager.java new file mode 100644 index 0000000..2e88126 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/transport/TransportManager.java @@ -0,0 +1,802 @@ + +package com.trilead.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.Vector; + +import com.trilead.ssh2.ConnectionInfo; +import com.trilead.ssh2.ConnectionMonitor; +import com.trilead.ssh2.DHGexParameters; +import com.trilead.ssh2.HTTPProxyData; +import com.trilead.ssh2.HTTPProxyException; +import com.trilead.ssh2.ProxyData; +import com.trilead.ssh2.ServerHostKeyVerifier; +import com.trilead.ssh2.compression.ICompressor; +import com.trilead.ssh2.crypto.Base64; +import com.trilead.ssh2.crypto.CryptoWishList; +import com.trilead.ssh2.crypto.cipher.BlockCipher; +import com.trilead.ssh2.crypto.digest.MAC; +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.PacketDisconnect; +import com.trilead.ssh2.packets.Packets; +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.util.Tokenizer; + + +/* + * Yes, the "standard" is a big mess. On one side, the say that arbitary channel + * packets are allowed during kex exchange, on the other side we need to blindly + * ignore the next _packet_ if the KEX guess was wrong. Where do we know from that + * the next packet is not a channel data packet? Yes, we could check if it is in + * the KEX range. But the standard says nothing about this. The OpenSSH guys + * block local "normal" traffic during KEX. That's fine - however, they assume + * that the other side is doing the same. During re-key, if they receive traffic + * other than KEX, they become horribly irritated and kill the connection. Since + * we are very likely going to communicate with OpenSSH servers, we have to play + * the same game - even though we could do better. + * + * btw: having stdout and stderr on the same channel, with a shared window, is + * also a VERY good idea... =( + */ + +/** + * TransportManager. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: TransportManager.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class TransportManager +{ + private static final Logger log = Logger.getLogger(TransportManager.class); + + class HandlerEntry + { + MessageHandler mh; + int low; + int high; + } + + private final Vector<byte[]> asynchronousQueue = new Vector<byte[]>(); + private Thread asynchronousThread = null; + + class AsynchronousWorker extends Thread + { + public void run() + { + while (true) + { + byte[] msg = null; + + synchronized (asynchronousQueue) + { + if (asynchronousQueue.size() == 0) + { + /* After the queue is empty for about 2 seconds, stop this thread */ + + try + { + asynchronousQueue.wait(2000); + } + catch (InterruptedException e) + { + /* OKOK, if somebody interrupts us, then we may die earlier. */ + } + + if (asynchronousQueue.size() == 0) + { + asynchronousThread = null; + return; + } + } + + msg = asynchronousQueue.remove(0); + } + + /* The following invocation may throw an IOException. + * There is no point in handling it - it simply means + * that the connection has a problem and we should stop + * sending asynchronously messages. We do not need to signal that + * we have exited (asynchronousThread = null): further + * messages in the queue cannot be sent by this or any + * other thread. + * Other threads will sooner or later (when receiving or + * sending the next message) get the same IOException and + * get to the same conclusion. + */ + + try + { + sendMessage(msg); + } + catch (IOException e) + { + return; + } + } + } + } + + String hostname; + int port; + final Socket sock = new Socket(); + + Object connectionSemaphore = new Object(); + + boolean flagKexOngoing = false; + boolean connectionClosed = false; + + Throwable reasonClosedCause = null; + + TransportConnection tc; + KexManager km; + + Vector<HandlerEntry> messageHandlers = new Vector<HandlerEntry>(); + + Thread receiveThread; + + Vector connectionMonitors = new Vector(); + boolean monitorsWereInformed = false; + + /** + * There were reports that there are JDKs which use + * the resolver even though one supplies a dotted IP + * address in the Socket constructor. That is why we + * try to generate the InetAdress "by hand". + * + * @param host + * @return the InetAddress + * @throws UnknownHostException + */ + private InetAddress createInetAddress(String host) throws UnknownHostException + { + /* Check if it is a dotted IP4 address */ + + InetAddress addr = parseIPv4Address(host); + + if (addr != null) + return addr; + + return InetAddress.getByName(host); + } + + private InetAddress parseIPv4Address(String host) throws UnknownHostException + { + if (host == null) + return null; + + String[] quad = Tokenizer.parseTokens(host, '.'); + + if ((quad == null) || (quad.length != 4)) + return null; + + byte[] addr = new byte[4]; + + for (int i = 0; i < 4; i++) + { + int part = 0; + + if ((quad[i].length() == 0) || (quad[i].length() > 3)) + return null; + + for (int k = 0; k < quad[i].length(); k++) + { + char c = quad[i].charAt(k); + + /* No, Character.isDigit is not the same */ + if ((c < '0') || (c > '9')) + return null; + + part = part * 10 + (c - '0'); + } + + if (part > 255) /* 300.1.2.3 is invalid =) */ + return null; + + addr[i] = (byte) part; + } + + return InetAddress.getByAddress(host, addr); + } + + public TransportManager(String host, int port) throws IOException + { + this.hostname = host; + this.port = port; + } + + public int getPacketOverheadEstimate() + { + return tc.getPacketOverheadEstimate(); + } + + public void setTcpNoDelay(boolean state) throws IOException + { + sock.setTcpNoDelay(state); + } + + public void setSoTimeout(int timeout) throws IOException + { + sock.setSoTimeout(timeout); + } + + public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException + { + return km.getOrWaitForConnectionInfo(kexNumber); + } + + public Throwable getReasonClosedCause() + { + synchronized (connectionSemaphore) + { + return reasonClosedCause; + } + } + + public byte[] getSessionIdentifier() + { + return km.sessionId; + } + + public void close(Throwable cause, boolean useDisconnectPacket) + { + if (useDisconnectPacket == false) + { + /* OK, hard shutdown - do not aquire the semaphore, + * perhaps somebody is inside (and waits until the remote + * side is ready to accept new data). */ + + try + { + sock.close(); + } + catch (IOException ignore) + { + } + + /* OK, whoever tried to send data, should now agree that + * there is no point in further waiting =) + * It is safe now to aquire the semaphore. + */ + } + + synchronized (connectionSemaphore) + { + if (connectionClosed == false) + { + if (useDisconnectPacket == true) + { + try + { + byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "") + .getPayload(); + if (tc != null) + tc.sendMessage(msg); + } + catch (IOException ignore) + { + } + + try + { + sock.close(); + } + catch (IOException ignore) + { + } + } + + connectionClosed = true; + reasonClosedCause = cause; /* may be null */ + } + connectionSemaphore.notifyAll(); + } + + /* No check if we need to inform the monitors */ + + Vector monitors = null; + + synchronized (this) + { + /* Short term lock to protect "connectionMonitors" + * and "monitorsWereInformed" + * (they may be modified concurrently) + */ + + if (monitorsWereInformed == false) + { + monitorsWereInformed = true; + monitors = (Vector) connectionMonitors.clone(); + } + } + + if (monitors != null) + { + for (int i = 0; i < monitors.size(); i++) + { + try + { + ConnectionMonitor cmon = (ConnectionMonitor) monitors.elementAt(i); + cmon.connectionLost(reasonClosedCause); + } + catch (Exception ignore) + { + } + } + } + } + + private void establishConnection(ProxyData proxyData, int connectTimeout) throws IOException + { + /* See the comment for createInetAddress() */ + + if (proxyData == null) + { + InetAddress addr = createInetAddress(hostname); + sock.connect(new InetSocketAddress(addr, port), connectTimeout); + sock.setSoTimeout(0); + return; + } + + if (proxyData instanceof HTTPProxyData) + { + HTTPProxyData pd = (HTTPProxyData) proxyData; + + /* At the moment, we only support HTTP proxies */ + + InetAddress addr = createInetAddress(pd.proxyHost); + sock.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout); + sock.setSoTimeout(0); + + /* OK, now tell the proxy where we actually want to connect to */ + + StringBuffer sb = new StringBuffer(); + + sb.append("CONNECT "); + sb.append(hostname); + sb.append(':'); + sb.append(port); + sb.append(" HTTP/1.0\r\n"); + + if ((pd.proxyUser != null) && (pd.proxyPass != null)) + { + String credentials = pd.proxyUser + ":" + pd.proxyPass; + char[] encoded = Base64.encode(credentials.getBytes("ISO-8859-1")); + sb.append("Proxy-Authorization: Basic "); + sb.append(encoded); + sb.append("\r\n"); + } + + if (pd.requestHeaderLines != null) + { + for (int i = 0; i < pd.requestHeaderLines.length; i++) + { + if (pd.requestHeaderLines[i] != null) + { + sb.append(pd.requestHeaderLines[i]); + sb.append("\r\n"); + } + } + } + + sb.append("\r\n"); + + OutputStream out = sock.getOutputStream(); + + out.write(sb.toString().getBytes("ISO-8859-1")); + out.flush(); + + /* Now parse the HTTP response */ + + byte[] buffer = new byte[1024]; + InputStream in = sock.getInputStream(); + + int len = ClientServerHello.readLineRN(in, buffer); + + String httpReponse = new String(buffer, 0, len, "ISO-8859-1"); + + if (httpReponse.startsWith("HTTP/") == false) + throw new IOException("The proxy did not send back a valid HTTP response."); + + /* "HTTP/1.X XYZ X" => 14 characters minimum */ + + if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' ')) + throw new IOException("The proxy did not send back a valid HTTP response."); + + int errorCode = 0; + + try + { + errorCode = Integer.parseInt(httpReponse.substring(9, 12)); + } + catch (NumberFormatException ignore) + { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + if ((errorCode < 0) || (errorCode > 999)) + throw new IOException("The proxy did not send back a valid HTTP response."); + + if (errorCode != 200) + { + throw new HTTPProxyException(httpReponse.substring(13), errorCode); + } + + /* OK, read until empty line */ + + while (true) + { + len = ClientServerHello.readLineRN(in, buffer); + if (len == 0) + break; + } + return; + } + + throw new IOException("Unsupported ProxyData"); + } + + public void initialize(CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex, + int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException + { + /* First, establish the TCP connection to the SSH-2 server */ + + establishConnection(proxyData, connectTimeout); + + /* Parse the server line and say hello - important: this information is later needed for the + * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object + * for later use. + */ + + ClientServerHello csh = new ClientServerHello(sock.getInputStream(), sock.getOutputStream()); + + tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); + + km = new KexManager(this, csh, cwl, hostname, port, verifier, rnd); + km.initiateKEX(cwl, dhgex); + + receiveThread = new Thread(new Runnable() + { + public void run() + { + try + { + receiveLoop(); + } + catch (IOException e) + { + close(e, false); + + if (log.isEnabled()) + log.log(10, "Receive thread: error in receiveLoop: " + e.getMessage()); + } + + if (log.isEnabled()) + log.log(50, "Receive thread: back from receiveLoop"); + + /* Tell all handlers that it is time to say goodbye */ + + if (km != null) + { + try + { + km.handleMessage(null, 0); + } + catch (IOException e) + { + } + } + + for (int i = 0; i < messageHandlers.size(); i++) + { + HandlerEntry he = messageHandlers.elementAt(i); + try + { + he.mh.handleMessage(null, 0); + } + catch (Exception ignore) + { + } + } + } + }); + + receiveThread.setDaemon(true); + receiveThread.start(); + } + + public void registerMessageHandler(MessageHandler mh, int low, int high) + { + HandlerEntry he = new HandlerEntry(); + he.mh = mh; + he.low = low; + he.high = high; + + synchronized (messageHandlers) + { + messageHandlers.addElement(he); + } + } + + public void removeMessageHandler(MessageHandler mh, int low, int high) + { + synchronized (messageHandlers) + { + for (int i = 0; i < messageHandlers.size(); i++) + { + HandlerEntry he = messageHandlers.elementAt(i); + if ((he.mh == mh) && (he.low == low) && (he.high == high)) + { + messageHandlers.removeElementAt(i); + break; + } + } + } + } + + public void sendKexMessage(byte[] msg) throws IOException + { + synchronized (connectionSemaphore) + { + if (connectionClosed) + { + throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); + } + + flagKexOngoing = true; + + try + { + tc.sendMessage(msg); + } + catch (IOException e) + { + close(e, false); + throw e; + } + } + } + + public void kexFinished() throws IOException + { + synchronized (connectionSemaphore) + { + flagKexOngoing = false; + connectionSemaphore.notifyAll(); + } + } + + public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws IOException + { + km.initiateKEX(cwl, dhgex); + } + + public void changeRecvCipher(BlockCipher bc, MAC mac) + { + tc.changeRecvCipher(bc, mac); + } + + public void changeSendCipher(BlockCipher bc, MAC mac) + { + tc.changeSendCipher(bc, mac); + } + + /** + * @param comp + */ + public void changeRecvCompression(ICompressor comp) { + tc.changeRecvCompression(comp); + } + + /** + * @param comp + */ + public void changeSendCompression(ICompressor comp) { + tc.changeSendCompression(comp); + } + + /** + * + */ + public void startCompression() { + tc.startCompression(); + } + + public void sendAsynchronousMessage(byte[] msg) throws IOException + { + synchronized (asynchronousQueue) + { + asynchronousQueue.addElement(msg); + + /* This limit should be flexible enough. We need this, otherwise the peer + * can flood us with global requests (and other stuff where we have to reply + * with an asynchronous message) and (if the server just sends data and does not + * read what we send) this will probably put us in a low memory situation + * (our send queue would grow and grow and...) */ + + if (asynchronousQueue.size() > 100) + throw new IOException("Error: the peer is not consuming our asynchronous replies."); + + /* Check if we have an asynchronous sending thread */ + + if (asynchronousThread == null) + { + asynchronousThread = new AsynchronousWorker(); + asynchronousThread.setDaemon(true); + asynchronousThread.start(); + + /* The thread will stop after 2 seconds of inactivity (i.e., empty queue) */ + } + } + } + + public void setConnectionMonitors(Vector monitors) + { + synchronized (this) + { + connectionMonitors = (Vector) monitors.clone(); + } + } + + public void sendMessage(byte[] msg) throws IOException + { + if (Thread.currentThread() == receiveThread) + throw new IOException("Assertion error: sendMessage may never be invoked by the receiver thread!"); + + synchronized (connectionSemaphore) + { + while (true) + { + if (connectionClosed) + { + throw (IOException) new IOException("Sorry, this connection is closed.") + .initCause(reasonClosedCause); + } + + if (flagKexOngoing == false) + break; + + try + { + connectionSemaphore.wait(); + } + catch (InterruptedException e) + { + } + } + + try + { + tc.sendMessage(msg); + } + catch (IOException e) + { + close(e, false); + throw e; + } + } + } + + public void receiveLoop() throws IOException + { + byte[] msg = new byte[35000]; + + while (true) + { + int msglen = tc.receiveMessage(msg, 0, msg.length); + + int type = msg[0] & 0xff; + + if (type == Packets.SSH_MSG_IGNORE) + continue; + + if (type == Packets.SSH_MSG_DEBUG) + { + if (log.isEnabled()) + { + TypesReader tr = new TypesReader(msg, 0, msglen); + tr.readByte(); + tr.readBoolean(); + StringBuffer debugMessageBuffer = new StringBuffer(); + debugMessageBuffer.append(tr.readString("UTF-8")); + + for (int i = 0; i < debugMessageBuffer.length(); i++) + { + char c = debugMessageBuffer.charAt(i); + + if ((c >= 32) && (c <= 126)) + continue; + debugMessageBuffer.setCharAt(i, '\uFFFD'); + } + + log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'"); + } + continue; + } + + if (type == Packets.SSH_MSG_UNIMPLEMENTED) + { + throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen."); + } + + if (type == Packets.SSH_MSG_DISCONNECT) + { + TypesReader tr = new TypesReader(msg, 0, msglen); + tr.readByte(); + int reason_code = tr.readUINT32(); + StringBuffer reasonBuffer = new StringBuffer(); + reasonBuffer.append(tr.readString("UTF-8")); + + /* + * Do not get fooled by servers that send abnormal long error + * messages + */ + + if (reasonBuffer.length() > 255) + { + reasonBuffer.setLength(255); + reasonBuffer.setCharAt(254, '.'); + reasonBuffer.setCharAt(253, '.'); + reasonBuffer.setCharAt(252, '.'); + } + + /* + * Also, check that the server did not send charcaters that may + * screw up the receiver -> restrict to reasonable US-ASCII + * subset -> "printable characters" (ASCII 32 - 126). Replace + * all others with 0xFFFD (UNICODE replacement character). + */ + + for (int i = 0; i < reasonBuffer.length(); i++) + { + char c = reasonBuffer.charAt(i); + + if ((c >= 32) && (c <= 126)) + continue; + reasonBuffer.setCharAt(i, '\uFFFD'); + } + + throw new IOException("Peer sent DISCONNECT message (reason code " + reason_code + "): " + + reasonBuffer.toString()); + } + + /* + * Is it a KEX Packet? + */ + + if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS) + || ((type >= 30) && (type <= 49))) + { + km.handleMessage(msg, msglen); + continue; + } + + if (type == Packets.SSH_MSG_USERAUTH_SUCCESS) { + tc.startCompression(); + } + + MessageHandler mh = null; + + for (int i = 0; i < messageHandlers.size(); i++) + { + HandlerEntry he = messageHandlers.elementAt(i); + if ((he.low <= type) && (type <= he.high)) + { + mh = he.mh; + break; + } + } + + if (mh == null) + throw new IOException("Unexpected SSH message (type " + type + ")"); + + mh.handleMessage(msg, msglen); + } + } +} diff --git a/app/src/main/java/com/trilead/ssh2/util/TimeoutService.java b/app/src/main/java/com/trilead/ssh2/util/TimeoutService.java new file mode 100644 index 0000000..3d52161 --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/util/TimeoutService.java @@ -0,0 +1,149 @@ + +package com.trilead.ssh2.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Collections; +import java.util.LinkedList; + +import com.trilead.ssh2.log.Logger; + + +/** + * TimeoutService (beta). Here you can register a timeout. + * <p> + * Implemented having large scale programs in mind: if you open many concurrent SSH connections + * that rely on timeouts, then there will be only one timeout thread. Once all timeouts + * have expired/are cancelled, the thread will (sooner or later) exit. + * Only after new timeouts arrive a new thread (singleton) will be instantiated. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: TimeoutService.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class TimeoutService +{ + private static final Logger log = Logger.getLogger(TimeoutService.class); + + public static class TimeoutToken implements Comparable + { + private long runTime; + private Runnable handler; + + private TimeoutToken(long runTime, Runnable handler) + { + this.runTime = runTime; + this.handler = handler; + } + + public int compareTo(Object o) + { + TimeoutToken t = (TimeoutToken) o; + if (runTime > t.runTime) + return 1; + if (runTime == t.runTime) + return 0; + return -1; + } + } + + private static class TimeoutThread extends Thread + { + public void run() + { + synchronized (todolist) + { + while (true) + { + if (todolist.size() == 0) + { + timeoutThread = null; + return; + } + + long now = System.currentTimeMillis(); + + TimeoutToken tt = (TimeoutToken) todolist.getFirst(); + + if (tt.runTime > now) + { + /* Not ready yet, sleep a little bit */ + + try + { + todolist.wait(tt.runTime - now); + } + catch (InterruptedException e) + { + } + + /* We cannot simply go on, since it could be that the token + * was removed (cancelled) or another one has been inserted in + * the meantime. + */ + + continue; + } + + todolist.removeFirst(); + + try + { + tt.handler.run(); + } + catch (Exception e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + log.log(20, "Exeception in Timeout handler:" + e.getMessage() + "(" + sw.toString() + ")"); + } + } + } + } + } + + /* The list object is also used for locking purposes */ + private static final LinkedList todolist = new LinkedList(); + + private static Thread timeoutThread = null; + + /** + * It is assumed that the passed handler will not execute for a long time. + * + * @param runTime + * @param handler + * @return a TimeoutToken that can be used to cancel the timeout. + */ + public static final TimeoutToken addTimeoutHandler(long runTime, Runnable handler) + { + TimeoutToken token = new TimeoutToken(runTime, handler); + + synchronized (todolist) + { + todolist.add(token); + Collections.sort(todolist); + + if (timeoutThread != null) + timeoutThread.interrupt(); + else + { + timeoutThread = new TimeoutThread(); + timeoutThread.setDaemon(true); + timeoutThread.start(); + } + } + + return token; + } + + public static final void cancelTimeoutHandler(TimeoutToken token) + { + synchronized (todolist) + { + todolist.remove(token); + + if (timeoutThread != null) + timeoutThread.interrupt(); + } + } + +} diff --git a/app/src/main/java/com/trilead/ssh2/util/Tokenizer.java b/app/src/main/java/com/trilead/ssh2/util/Tokenizer.java new file mode 100644 index 0000000..dfd480b --- /dev/null +++ b/app/src/main/java/com/trilead/ssh2/util/Tokenizer.java @@ -0,0 +1,51 @@ + +package com.trilead.ssh2.util; + +/** + * Tokenizer. Why? Because StringTokenizer is not available in J2ME. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Tokenizer.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class Tokenizer +{ + /** + * Exists because StringTokenizer is not available in J2ME. + * Returns an array with at least 1 entry. + * + * @param source must be non-null + * @param delimiter + * @return an array of Strings + */ + public static String[] parseTokens(String source, char delimiter) + { + int numtoken = 1; + + for (int i = 0; i < source.length(); i++) + { + if (source.charAt(i) == delimiter) + numtoken++; + } + + String list[] = new String[numtoken]; + int nextfield = 0; + + for (int i = 0; i < numtoken; i++) + { + if (nextfield >= source.length()) + { + list[i] = ""; + } + else + { + int idx = source.indexOf(delimiter, nextfield); + if (idx == -1) + idx = source.length(); + list[i] = source.substring(nextfield, idx); + nextfield = idx + 1; + } + } + + return list; + } +} diff --git a/app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java b/app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java new file mode 100644 index 0000000..74f08bb --- /dev/null +++ b/app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java @@ -0,0 +1,678 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ + +package de.mud.telnet; + +import java.io.IOException; +/** + * This is a telnet protocol handler. The handler needs implementations + * for several methods to handle the telnet options and to be able to + * read and write the buffer. + * <P> + * <B>Maintainer:</B> Marcus Meissner + * + * @version $Id: TelnetProtocolHandler.java 503 2005-10-24 07:34:13Z marcus $ + * @author Matthias L. Jugel, Marcus Meissner + */ +public abstract class TelnetProtocolHandler { + /** contains the current revision id */ + public final static String ID = "$Id: TelnetProtocolHandler.java 503 2005-10-24 07:34:13Z marcus $"; + + /** debug level */ + private final static int debug = 0; + + /** temporary buffer for data-telnetstuff-data transformation */ + private byte[] tempbuf = new byte[0]; + + /** the data sent on pressing <RETURN> \n */ + private byte[] crlf = new byte[2]; + /** the data sent on pressing <LineFeed> \r */ + private byte[] cr = new byte[2]; + + /** + * Create a new telnet protocol handler. + */ + public TelnetProtocolHandler() { + reset(); + + crlf[0] = 13; crlf[1] = 10; + cr[0] = 13; cr[1] = 0; + } + + /** + * Get the current terminal type for TTYPE telnet option. + * @return the string id of the terminal + */ + protected abstract String getTerminalType(); + + /** + * Get the current window size of the terminal for the + * NAWS telnet option. + * @return the size of the terminal as Dimension + */ + protected abstract int[] getWindowSize(); + + /** + * Set the local echo option of telnet. + * @param echo true for local echo, false for no local echo + */ + protected abstract void setLocalEcho(boolean echo); + + /** + * Generate an EOR (end of record) request. For use by prompt displaying. + */ + protected abstract void notifyEndOfRecord(); + + /** + * Send data to the remote host. + * @param b array of bytes to send + */ + protected abstract void write(byte[] b) throws IOException; + + /** + * Read the charset name from terminal. + */ + protected abstract String getCharsetName(); + + /** + * Send one byte to the remote host. + * @param b the byte to be sent + * @see #write(byte[] b) + */ + private static byte[] one = new byte[1]; + private void write(byte b) throws IOException { + one[0] = b; + write(one); + } + + /** + * Reset the protocol handler. This may be necessary after the + * connection was closed or some other problem occured. + */ + public void reset() { + neg_state = 0; + receivedDX = new byte[256]; + sentDX = new byte[256]; + receivedWX = new byte[256]; + sentWX = new byte[256]; + } + + // =================================================================== + // the actual negotiation handling for the telnet protocol follows: + // =================================================================== + + /** state variable for telnet negotiation reader */ + private byte neg_state = 0; + + /** constants for the negotiation state */ + private final static byte STATE_DATA = 0; + private final static byte STATE_IAC = 1; + private final static byte STATE_IACSB = 2; + private final static byte STATE_IACWILL = 3; + private final static byte STATE_IACDO = 4; + private final static byte STATE_IACWONT = 5; + private final static byte STATE_IACDONT = 6; + private final static byte STATE_IACSBIAC = 7; + private final static byte STATE_IACSBDATA = 8; + private final static byte STATE_IACSBDATAIAC = 9; + + /** What IAC SB <xx> we are handling right now */ + private byte current_sb; + + /** current SB negotiation buffer */ + private byte[] sbbuf; + + /** IAC - init sequence for telnet negotiation. */ + private final static byte IAC = (byte)255; + /** [IAC] End Of Record */ + private final static byte EOR = (byte)239; + /** [IAC] WILL */ + private final static byte WILL = (byte)251; + /** [IAC] WONT */ + private final static byte WONT = (byte)252; + /** [IAC] DO */ + private final static byte DO = (byte)253; + /** [IAC] DONT */ + private final static byte DONT = (byte)254; + /** [IAC] Sub Begin */ + private final static byte SB = (byte)250; + /** [IAC] Sub End */ + private final static byte SE = (byte)240; + /** Telnet option: binary mode */ + private final static byte TELOPT_BINARY= (byte)0; /* binary mode */ + /** Telnet option: echo text */ + private final static byte TELOPT_ECHO = (byte)1; /* echo on/off */ + /** Telnet option: sga */ + private final static byte TELOPT_SGA = (byte)3; /* supress go ahead */ + /** Telnet option: End Of Record */ + private final static byte TELOPT_EOR = (byte)25; /* end of record */ + /** Telnet option: Negotiate About Window Size */ + private final static byte TELOPT_NAWS = (byte)31; /* NA-WindowSize*/ + /** Telnet option: Terminal Type */ + private final static byte TELOPT_TTYPE = (byte)24; /* terminal type */ + /** Telnet option: CHARSET */ + private final static byte TELOPT_CHARSET= (byte)42; /* charset */ + + private final static byte[] IACWILL = { IAC, WILL }; + private final static byte[] IACWONT = { IAC, WONT }; + private final static byte[] IACDO = { IAC, DO }; + private final static byte[] IACDONT = { IAC, DONT }; + private final static byte[] IACSB = { IAC, SB }; + private final static byte[] IACSE = { IAC, SE }; + + private final static byte CHARSET_ACCEPTED = (byte)2; + private final static byte CHARSET_REJECTED = (byte)3; + + /** Telnet option qualifier 'IS' */ + private final static byte TELQUAL_IS = (byte)0; + /** Telnet option qualifier 'SEND' */ + private final static byte TELQUAL_SEND = (byte)1; + + /** What IAC DO(NT) request do we have received already ? */ + private byte[] receivedDX; + /** What IAC WILL/WONT request do we have received already ? */ + private byte[] receivedWX; + /** What IAC DO/DONT request do we have sent already ? */ + private byte[] sentDX; + /** What IAC WILL/WONT request do we have sent already ? */ + private byte[] sentWX; + + /** + * Send a Telnet Escape character (IAC <code>) + */ + public void sendTelnetControl(byte code) + throws IOException { + byte[] b = new byte[2]; + + b[0] = IAC; + b[1] = code; + write(b); + } + + /** + * Send the new Window Size (via NAWS) + */ + public void setWindowSize(int columns,int rows) + throws IOException { + if(debug > 2) System.err.println("sending NAWS"); + + if (receivedDX[TELOPT_NAWS] != DO) { + System.err.println("not allowed to send NAWS? (DONT NAWS)"); + return; + } + write(IAC);write(SB);write(TELOPT_NAWS); + write((byte) (columns >> 8)); + write((byte) (columns & 0xff)); + write((byte) (rows >> 8)); + write((byte) (rows & 0xff)); + write(IAC);write(SE); + } + + + /** + * Handle an incoming IAC SB <type> <bytes> IAC SE + * @param type type of SB + * @param sbata byte array as <bytes> + */ + private void handle_sb(byte type, byte[] sbdata) + throws IOException { + if(debug > 1) + System.err.println("TelnetIO.handle_sb("+type+")"); + switch (type) { + case TELOPT_TTYPE: + if (sbdata.length>0 && sbdata[0]==TELQUAL_SEND) { + write(IACSB);write(TELOPT_TTYPE);write(TELQUAL_IS); + /* FIXME: need more logic here if we use + * more than one terminal type + */ + String ttype = getTerminalType(); + if(ttype == null) ttype = "dumb"; + write(ttype.getBytes()); + write(IACSE); + } + break; + case TELOPT_CHARSET: + System.out.println("Got SB CHARSET"); + + String charsetStr = new String(sbdata, "US-ASCII"); + if (charsetStr.startsWith("TTABLE ")) { + charsetStr = charsetStr.substring(7); + } + String[] charsets = charsetStr.split(charsetStr.substring(0,0)); + String myCharset = getCharsetName(); + for (String charset : charsets) { + if (charset.equals(myCharset)) { + write(IACSB);write(TELOPT_CHARSET);write(CHARSET_ACCEPTED); + write(charset.getBytes()); + write(IACSE); + System.out.println("Sent our charset!"); + return; + } + } + write(IACSB);write(TELOPT_CHARSET);write(CHARSET_REJECTED); + write(IACSE); + break; + } + } + + /** + * Do not send any notifications at startup. We do not know, + * whether the remote client understands telnet protocol handling, + * so we are silent. + * (This used to send IAC WILL SGA, but this is false for a compliant + * client.) + */ + public void startup() throws IOException { + } + /** + * Transpose special telnet codes like 0xff or newlines to values + * that are compliant to the protocol. This method will also send + * the buffer immediately after transposing the data. + * @param buf the data buffer to be sent + */ + public void transpose(byte[] buf) throws IOException { + int i; + + byte[] nbuf,xbuf; + int nbufptr=0; + nbuf = new byte[buf.length*2]; // FIXME: buffer overflows possible + + for (i = 0; i < buf.length ; i++) { + switch (buf[i]) { + // Escape IAC twice in stream ... to be telnet protocol compliant + // this is there in binary and non-binary mode. + case IAC: + nbuf[nbufptr++]=IAC; + nbuf[nbufptr++]=IAC; + break; + // We need to heed RFC 854. LF (\n) is 10, CR (\r) is 13 + // we assume that the Terminal sends \n for lf+cr and \r for just cr + // linefeed+carriage return is CR LF */ + case 10: // \n + if (receivedDX[TELOPT_BINARY + 128 ] != DO) { + while (nbuf.length - nbufptr < crlf.length) { + xbuf = new byte[nbuf.length*2]; + System.arraycopy(nbuf,0,xbuf,0,nbufptr); + nbuf = xbuf; + } + for (int j=0;j<crlf.length;j++) + nbuf[nbufptr++]=crlf[j]; + break; + } else { + // copy verbatim in binary mode. + nbuf[nbufptr++]=buf[i]; + } + break; + // carriage return is CR NUL */ + case 13: // \r + if (receivedDX[TELOPT_BINARY + 128 ] != DO) { + while (nbuf.length - nbufptr < cr.length) { + xbuf = new byte[nbuf.length*2]; + System.arraycopy(nbuf,0,xbuf,0,nbufptr); + nbuf = xbuf; + } + for (int j=0;j<cr.length;j++) + nbuf[nbufptr++]=cr[j]; + } else { + // copy verbatim in binary mode. + nbuf[nbufptr++]=buf[i]; + } + break; + // all other characters are just copied + default: + nbuf[nbufptr++]=buf[i]; + break; + } + } + xbuf = new byte[nbufptr]; + System.arraycopy(nbuf,0,xbuf,0,nbufptr); + write(xbuf); + } + + public void setCRLF(String xcrlf) { crlf = xcrlf.getBytes(); } + public void setCR(String xcr) { cr = xcr.getBytes(); } + + /** + * Handle telnet protocol negotiation. The buffer will be parsed + * and necessary actions are taken according to the telnet protocol. + * See <A HREF="RFC-Telnet-URL">RFC-Telnet</A> + * @param nbuf the byte buffer put out after negotiation + * @return number of bytes processed, 0 for none, and -1 for end of buffer. + */ + public int negotiate(byte nbuf[], int offset) + throws IOException + { + int count = tempbuf.length; + byte[] buf = tempbuf; + byte sendbuf[] = new byte[3]; + byte b,reply; + int boffset = 0, noffset = offset; + boolean dobreak = false; + + if (count == 0) // buffer is empty. + return -1; + + while(!dobreak && (boffset < count) && (noffset < nbuf.length)) { + b=buf[boffset++]; + // of course, byte is a signed entity (-128 -> 127) + // but apparently the SGI Netscape 3.0 doesn't seem + // to care and provides happily values up to 255 + if (b>=128) + b=(byte)(b-256); + if(debug > 2) { + Byte B = new Byte(b); + System.err.print("byte: " + B.intValue()+ " "); + } + switch (neg_state) { + case STATE_DATA: + if (b==IAC) { + neg_state = STATE_IAC; + dobreak = true; // leave the loop so we can sync. + } else + nbuf[noffset++]=b; + break; + case STATE_IAC: + switch (b) { + case IAC: + if(debug > 2) System.err.print("IAC "); + neg_state = STATE_DATA; + nbuf[noffset++]=IAC; + break; + case WILL: + if(debug > 2) System.err.print("WILL "); + neg_state = STATE_IACWILL; + break; + case WONT: + if(debug > 2) System.err.print("WONT "); + neg_state = STATE_IACWONT; + break; + case DONT: + if(debug > 2) System.err.print("DONT "); + neg_state = STATE_IACDONT; + break; + case DO: + if(debug > 2) System.err.print("DO "); + neg_state = STATE_IACDO; + break; + case EOR: + if(debug > 1) System.err.print("EOR "); + notifyEndOfRecord(); + dobreak = true; // leave the loop so we can sync. + neg_state = STATE_DATA; + break; + case SB: + if(debug > 2) System.err.print("SB "); + neg_state = STATE_IACSB; + break; + default: + if(debug > 2) System.err.print("<UNKNOWN "+b+" > "); + neg_state = STATE_DATA; + break; + } + break; + case STATE_IACWILL: + switch(b) { + case TELOPT_ECHO: + if(debug > 2) System.err.println("ECHO"); + reply = DO; + setLocalEcho(false); + break; + case TELOPT_SGA: + if(debug > 2) System.err.println("SGA"); + reply = DO; + break; + case TELOPT_EOR: + if(debug > 2) System.err.println("EOR"); + reply = DO; + break; + case TELOPT_BINARY: + if(debug > 2) System.err.println("BINARY"); + reply = DO; + break; + default: + if(debug > 2) System.err.println("<UNKNOWN,"+b+">"); + reply = DONT; + break; + } + if(debug > 1) System.err.println("<"+b+", WILL ="+WILL+">"); + if (reply != sentDX[b+128] || WILL != receivedWX[b+128]) { + sendbuf[0]=IAC; + sendbuf[1]=reply; + sendbuf[2]=b; + write(sendbuf); + sentDX[b+128] = reply; + receivedWX[b+128] = WILL; + } + neg_state = STATE_DATA; + break; + case STATE_IACWONT: + switch(b) { + case TELOPT_ECHO: + if(debug > 2) System.err.println("ECHO"); + setLocalEcho(true); + reply = DONT; + break; + case TELOPT_SGA: + if(debug > 2) System.err.println("SGA"); + reply = DONT; + break; + case TELOPT_EOR: + if(debug > 2) System.err.println("EOR"); + reply = DONT; + break; + case TELOPT_BINARY: + if(debug > 2) System.err.println("BINARY"); + reply = DONT; + break; + default: + if(debug > 2) System.err.println("<UNKNOWN,"+b+">"); + reply = DONT; + break; + } + if(reply != sentDX[b+128] || WONT != receivedWX[b+128]) { + sendbuf[0]=IAC; + sendbuf[1]=reply; + sendbuf[2]=b; + write(sendbuf); + sentDX[b+128] = reply; + receivedWX[b+128] = WILL; + } + neg_state = STATE_DATA; + break; + case STATE_IACDO: + switch (b) { + case TELOPT_ECHO: + if(debug > 2) System.err.println("ECHO"); + reply = WILL; + setLocalEcho(true); + break; + case TELOPT_SGA: + if(debug > 2) System.err.println("SGA"); + reply = WILL; + break; + case TELOPT_TTYPE: + if(debug > 2) System.err.println("TTYPE"); + reply = WILL; + break; + case TELOPT_BINARY: + if(debug > 2) System.err.println("BINARY"); + reply = WILL; + break; + case TELOPT_NAWS: + if(debug > 2) System.err.println("NAWS"); + int[] size = getWindowSize(); + receivedDX[b] = DO; + if(size == null) { + // this shouldn't happen + write(IAC); + write(WONT); + write(TELOPT_NAWS); + reply = WONT; + sentWX[b] = WONT; + break; + } + reply = WILL; + sentWX[b] = WILL; + sendbuf[0]=IAC; + sendbuf[1]=WILL; + sendbuf[2]=TELOPT_NAWS; + write(sendbuf); + write(IAC);write(SB);write(TELOPT_NAWS); + write((byte) (size[0] >> 8)); + write((byte) (size[0] & 0xff)); + write((byte) (size[1] >> 8)); + write((byte) (size[1] & 0xff)); + write(IAC);write(SE); + break; + default: + if(debug > 2) System.err.println("<UNKNOWN,"+b+">"); + reply = WONT; + break; + } + if(reply != sentWX[128+b] || DO != receivedDX[128+b]) { + sendbuf[0]=IAC; + sendbuf[1]=reply; + sendbuf[2]=b; + write(sendbuf); + sentWX[b+128] = reply; + receivedDX[b+128] = DO; + } + neg_state = STATE_DATA; + break; + case STATE_IACDONT: + switch (b) { + case TELOPT_ECHO: + if(debug > 2) System.err.println("ECHO"); + reply = WONT; + setLocalEcho(false); + break; + case TELOPT_SGA: + if(debug > 2) System.err.println("SGA"); + reply = WONT; + break; + case TELOPT_NAWS: + if(debug > 2) System.err.println("NAWS"); + reply = WONT; + break; + case TELOPT_BINARY: + if(debug > 2) System.err.println("BINARY"); + reply = WONT; + break; + default: + if(debug > 2) System.err.println("<UNKNOWN,"+b+">"); + reply = WONT; + break; + } + if(reply != sentWX[b+128] || DONT != receivedDX[b+128]) { + write(IAC);write(reply);write(b); + sentWX[b+128] = reply; + receivedDX[b+128] = DONT; + } + neg_state = STATE_DATA; + break; + case STATE_IACSBIAC: + if(debug > 2) System.err.println(""+b+" "); + if (b == IAC) { + sbbuf = new byte[0]; + current_sb = b; + neg_state = STATE_IACSBDATA; + } else { + System.err.println("(bad) "+b+" "); + neg_state = STATE_DATA; + } + break; + case STATE_IACSB: + if(debug > 2) System.err.println(""+b+" "); + switch (b) { + case IAC: + neg_state = STATE_IACSBIAC; + break; + default: + current_sb = b; + sbbuf = new byte[0]; + neg_state = STATE_IACSBDATA; + break; + } + break; + case STATE_IACSBDATA: + if (debug > 2) System.err.println(""+b+" "); + switch (b) { + case IAC: + neg_state = STATE_IACSBDATAIAC; + break; + default: + byte[] xsb = new byte[sbbuf.length+1]; + System.arraycopy(sbbuf,0,xsb,0,sbbuf.length); + sbbuf = xsb; + sbbuf[sbbuf.length-1] = b; + break; + } + break; + case STATE_IACSBDATAIAC: + if (debug > 2) System.err.println(""+b+" "); + switch (b) { + case IAC: + neg_state = STATE_IACSBDATA; + byte[] xsb = new byte[sbbuf.length+1]; + System.arraycopy(sbbuf,0,xsb,0,sbbuf.length); + sbbuf = xsb; + sbbuf[sbbuf.length-1] = IAC; + break; + case SE: + handle_sb(current_sb,sbbuf); + current_sb = 0; + neg_state = STATE_DATA; + break; + case SB: + handle_sb(current_sb,sbbuf); + neg_state = STATE_IACSB; + break; + default: + neg_state = STATE_DATA; + break; + } + break; + default: + if (debug > 1) + System.err.println("This should not happen: "+neg_state+" "); + neg_state = STATE_DATA; + break; + } + } + // shrink tempbuf to new processed size. + byte[] xb = new byte[count-boffset]; + System.arraycopy(tempbuf,boffset,xb,0,count-boffset); + tempbuf = xb; + return noffset - offset; + } + + public void inputfeed(byte[] b, int offset, int len) { + byte[] xb = new byte[tempbuf.length+len]; + + System.arraycopy(tempbuf,0,xb,0,tempbuf.length); + System.arraycopy(b,offset,xb,tempbuf.length,len); + tempbuf = xb; + } +} diff --git a/app/src/main/java/de/mud/terminal/Precomposer.java b/app/src/main/java/de/mud/terminal/Precomposer.java new file mode 100644 index 0000000..edad64c --- /dev/null +++ b/app/src/main/java/de/mud/terminal/Precomposer.java @@ -0,0 +1,1052 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.mud.terminal; + +/** + * @author Kenny Root + * This data was taken from xterm's precompose.c + */ +public class Precomposer { + public final static char precompositions[][] = { + { 0x226E, 0x003C, 0x0338}, + { 0x2260, 0x003D, 0x0338}, + { 0x226F, 0x003E, 0x0338}, + { 0x00C0, 0x0041, 0x0300}, + { 0x00C1, 0x0041, 0x0301}, + { 0x00C2, 0x0041, 0x0302}, + { 0x00C3, 0x0041, 0x0303}, + { 0x0100, 0x0041, 0x0304}, + { 0x0102, 0x0041, 0x0306}, + { 0x0226, 0x0041, 0x0307}, + { 0x00C4, 0x0041, 0x0308}, + { 0x1EA2, 0x0041, 0x0309}, + { 0x00C5, 0x0041, 0x030A}, + { 0x01CD, 0x0041, 0x030C}, + { 0x0200, 0x0041, 0x030F}, + { 0x0202, 0x0041, 0x0311}, + { 0x1EA0, 0x0041, 0x0323}, + { 0x1E00, 0x0041, 0x0325}, + { 0x0104, 0x0041, 0x0328}, + { 0x1E02, 0x0042, 0x0307}, + { 0x1E04, 0x0042, 0x0323}, + { 0x1E06, 0x0042, 0x0331}, + { 0x0106, 0x0043, 0x0301}, + { 0x0108, 0x0043, 0x0302}, + { 0x010A, 0x0043, 0x0307}, + { 0x010C, 0x0043, 0x030C}, + { 0x00C7, 0x0043, 0x0327}, + { 0x1E0A, 0x0044, 0x0307}, + { 0x010E, 0x0044, 0x030C}, + { 0x1E0C, 0x0044, 0x0323}, + { 0x1E10, 0x0044, 0x0327}, + { 0x1E12, 0x0044, 0x032D}, + { 0x1E0E, 0x0044, 0x0331}, + { 0x00C8, 0x0045, 0x0300}, + { 0x00C9, 0x0045, 0x0301}, + { 0x00CA, 0x0045, 0x0302}, + { 0x1EBC, 0x0045, 0x0303}, + { 0x0112, 0x0045, 0x0304}, + { 0x0114, 0x0045, 0x0306}, + { 0x0116, 0x0045, 0x0307}, + { 0x00CB, 0x0045, 0x0308}, + { 0x1EBA, 0x0045, 0x0309}, + { 0x011A, 0x0045, 0x030C}, + { 0x0204, 0x0045, 0x030F}, + { 0x0206, 0x0045, 0x0311}, + { 0x1EB8, 0x0045, 0x0323}, + { 0x0228, 0x0045, 0x0327}, + { 0x0118, 0x0045, 0x0328}, + { 0x1E18, 0x0045, 0x032D}, + { 0x1E1A, 0x0045, 0x0330}, + { 0x1E1E, 0x0046, 0x0307}, + { 0x01F4, 0x0047, 0x0301}, + { 0x011C, 0x0047, 0x0302}, + { 0x1E20, 0x0047, 0x0304}, + { 0x011E, 0x0047, 0x0306}, + { 0x0120, 0x0047, 0x0307}, + { 0x01E6, 0x0047, 0x030C}, + { 0x0122, 0x0047, 0x0327}, + { 0x0124, 0x0048, 0x0302}, + { 0x1E22, 0x0048, 0x0307}, + { 0x1E26, 0x0048, 0x0308}, + { 0x021E, 0x0048, 0x030C}, + { 0x1E24, 0x0048, 0x0323}, + { 0x1E28, 0x0048, 0x0327}, + { 0x1E2A, 0x0048, 0x032E}, + { 0x00CC, 0x0049, 0x0300}, + { 0x00CD, 0x0049, 0x0301}, + { 0x00CE, 0x0049, 0x0302}, + { 0x0128, 0x0049, 0x0303}, + { 0x012A, 0x0049, 0x0304}, + { 0x012C, 0x0049, 0x0306}, + { 0x0130, 0x0049, 0x0307}, + { 0x00CF, 0x0049, 0x0308}, + { 0x1EC8, 0x0049, 0x0309}, + { 0x01CF, 0x0049, 0x030C}, + { 0x0208, 0x0049, 0x030F}, + { 0x020A, 0x0049, 0x0311}, + { 0x1ECA, 0x0049, 0x0323}, + { 0x012E, 0x0049, 0x0328}, + { 0x1E2C, 0x0049, 0x0330}, + { 0x0134, 0x004A, 0x0302}, + { 0x1E30, 0x004B, 0x0301}, + { 0x01E8, 0x004B, 0x030C}, + { 0x1E32, 0x004B, 0x0323}, + { 0x0136, 0x004B, 0x0327}, + { 0x1E34, 0x004B, 0x0331}, + { 0x0139, 0x004C, 0x0301}, + { 0x013D, 0x004C, 0x030C}, + { 0x1E36, 0x004C, 0x0323}, + { 0x013B, 0x004C, 0x0327}, + { 0x1E3C, 0x004C, 0x032D}, + { 0x1E3A, 0x004C, 0x0331}, + { 0x1E3E, 0x004D, 0x0301}, + { 0x1E40, 0x004D, 0x0307}, + { 0x1E42, 0x004D, 0x0323}, + { 0x01F8, 0x004E, 0x0300}, + { 0x0143, 0x004E, 0x0301}, + { 0x00D1, 0x004E, 0x0303}, + { 0x1E44, 0x004E, 0x0307}, + { 0x0147, 0x004E, 0x030C}, + { 0x1E46, 0x004E, 0x0323}, + { 0x0145, 0x004E, 0x0327}, + { 0x1E4A, 0x004E, 0x032D}, + { 0x1E48, 0x004E, 0x0331}, + { 0x00D2, 0x004F, 0x0300}, + { 0x00D3, 0x004F, 0x0301}, + { 0x00D4, 0x004F, 0x0302}, + { 0x00D5, 0x004F, 0x0303}, + { 0x014C, 0x004F, 0x0304}, + { 0x014E, 0x004F, 0x0306}, + { 0x022E, 0x004F, 0x0307}, + { 0x00D6, 0x004F, 0x0308}, + { 0x1ECE, 0x004F, 0x0309}, + { 0x0150, 0x004F, 0x030B}, + { 0x01D1, 0x004F, 0x030C}, + { 0x020C, 0x004F, 0x030F}, + { 0x020E, 0x004F, 0x0311}, + { 0x01A0, 0x004F, 0x031B}, + { 0x1ECC, 0x004F, 0x0323}, + { 0x01EA, 0x004F, 0x0328}, + { 0x1E54, 0x0050, 0x0301}, + { 0x1E56, 0x0050, 0x0307}, + { 0x0154, 0x0052, 0x0301}, + { 0x1E58, 0x0052, 0x0307}, + { 0x0158, 0x0052, 0x030C}, + { 0x0210, 0x0052, 0x030F}, + { 0x0212, 0x0052, 0x0311}, + { 0x1E5A, 0x0052, 0x0323}, + { 0x0156, 0x0052, 0x0327}, + { 0x1E5E, 0x0052, 0x0331}, + { 0x015A, 0x0053, 0x0301}, + { 0x015C, 0x0053, 0x0302}, + { 0x1E60, 0x0053, 0x0307}, + { 0x0160, 0x0053, 0x030C}, + { 0x1E62, 0x0053, 0x0323}, + { 0x0218, 0x0053, 0x0326}, + { 0x015E, 0x0053, 0x0327}, + { 0x1E6A, 0x0054, 0x0307}, + { 0x0164, 0x0054, 0x030C}, + { 0x1E6C, 0x0054, 0x0323}, + { 0x021A, 0x0054, 0x0326}, + { 0x0162, 0x0054, 0x0327}, + { 0x1E70, 0x0054, 0x032D}, + { 0x1E6E, 0x0054, 0x0331}, + { 0x00D9, 0x0055, 0x0300}, + { 0x00DA, 0x0055, 0x0301}, + { 0x00DB, 0x0055, 0x0302}, + { 0x0168, 0x0055, 0x0303}, + { 0x016A, 0x0055, 0x0304}, + { 0x016C, 0x0055, 0x0306}, + { 0x00DC, 0x0055, 0x0308}, + { 0x1EE6, 0x0055, 0x0309}, + { 0x016E, 0x0055, 0x030A}, + { 0x0170, 0x0055, 0x030B}, + { 0x01D3, 0x0055, 0x030C}, + { 0x0214, 0x0055, 0x030F}, + { 0x0216, 0x0055, 0x0311}, + { 0x01AF, 0x0055, 0x031B}, + { 0x1EE4, 0x0055, 0x0323}, + { 0x1E72, 0x0055, 0x0324}, + { 0x0172, 0x0055, 0x0328}, + { 0x1E76, 0x0055, 0x032D}, + { 0x1E74, 0x0055, 0x0330}, + { 0x1E7C, 0x0056, 0x0303}, + { 0x1E7E, 0x0056, 0x0323}, + { 0x1E80, 0x0057, 0x0300}, + { 0x1E82, 0x0057, 0x0301}, + { 0x0174, 0x0057, 0x0302}, + { 0x1E86, 0x0057, 0x0307}, + { 0x1E84, 0x0057, 0x0308}, + { 0x1E88, 0x0057, 0x0323}, + { 0x1E8A, 0x0058, 0x0307}, + { 0x1E8C, 0x0058, 0x0308}, + { 0x1EF2, 0x0059, 0x0300}, + { 0x00DD, 0x0059, 0x0301}, + { 0x0176, 0x0059, 0x0302}, + { 0x1EF8, 0x0059, 0x0303}, + { 0x0232, 0x0059, 0x0304}, + { 0x1E8E, 0x0059, 0x0307}, + { 0x0178, 0x0059, 0x0308}, + { 0x1EF6, 0x0059, 0x0309}, + { 0x1EF4, 0x0059, 0x0323}, + { 0x0179, 0x005A, 0x0301}, + { 0x1E90, 0x005A, 0x0302}, + { 0x017B, 0x005A, 0x0307}, + { 0x017D, 0x005A, 0x030C}, + { 0x1E92, 0x005A, 0x0323}, + { 0x1E94, 0x005A, 0x0331}, + { 0x00E0, 0x0061, 0x0300}, + { 0x00E1, 0x0061, 0x0301}, + { 0x00E2, 0x0061, 0x0302}, + { 0x00E3, 0x0061, 0x0303}, + { 0x0101, 0x0061, 0x0304}, + { 0x0103, 0x0061, 0x0306}, + { 0x0227, 0x0061, 0x0307}, + { 0x00E4, 0x0061, 0x0308}, + { 0x1EA3, 0x0061, 0x0309}, + { 0x00E5, 0x0061, 0x030A}, + { 0x01CE, 0x0061, 0x030C}, + { 0x0201, 0x0061, 0x030F}, + { 0x0203, 0x0061, 0x0311}, + { 0x1EA1, 0x0061, 0x0323}, + { 0x1E01, 0x0061, 0x0325}, + { 0x0105, 0x0061, 0x0328}, + { 0x1E03, 0x0062, 0x0307}, + { 0x1E05, 0x0062, 0x0323}, + { 0x1E07, 0x0062, 0x0331}, + { 0x0107, 0x0063, 0x0301}, + { 0x0109, 0x0063, 0x0302}, + { 0x010B, 0x0063, 0x0307}, + { 0x010D, 0x0063, 0x030C}, + { 0x00E7, 0x0063, 0x0327}, + { 0x1E0B, 0x0064, 0x0307}, + { 0x010F, 0x0064, 0x030C}, + { 0x1E0D, 0x0064, 0x0323}, + { 0x1E11, 0x0064, 0x0327}, + { 0x1E13, 0x0064, 0x032D}, + { 0x1E0F, 0x0064, 0x0331}, + { 0x00E8, 0x0065, 0x0300}, + { 0x00E9, 0x0065, 0x0301}, + { 0x00EA, 0x0065, 0x0302}, + { 0x1EBD, 0x0065, 0x0303}, + { 0x0113, 0x0065, 0x0304}, + { 0x0115, 0x0065, 0x0306}, + { 0x0117, 0x0065, 0x0307}, + { 0x00EB, 0x0065, 0x0308}, + { 0x1EBB, 0x0065, 0x0309}, + { 0x011B, 0x0065, 0x030C}, + { 0x0205, 0x0065, 0x030F}, + { 0x0207, 0x0065, 0x0311}, + { 0x1EB9, 0x0065, 0x0323}, + { 0x0229, 0x0065, 0x0327}, + { 0x0119, 0x0065, 0x0328}, + { 0x1E19, 0x0065, 0x032D}, + { 0x1E1B, 0x0065, 0x0330}, + { 0x1E1F, 0x0066, 0x0307}, + { 0x01F5, 0x0067, 0x0301}, + { 0x011D, 0x0067, 0x0302}, + { 0x1E21, 0x0067, 0x0304}, + { 0x011F, 0x0067, 0x0306}, + { 0x0121, 0x0067, 0x0307}, + { 0x01E7, 0x0067, 0x030C}, + { 0x0123, 0x0067, 0x0327}, + { 0x0125, 0x0068, 0x0302}, + { 0x1E23, 0x0068, 0x0307}, + { 0x1E27, 0x0068, 0x0308}, + { 0x021F, 0x0068, 0x030C}, + { 0x1E25, 0x0068, 0x0323}, + { 0x1E29, 0x0068, 0x0327}, + { 0x1E2B, 0x0068, 0x032E}, + { 0x1E96, 0x0068, 0x0331}, + { 0x00EC, 0x0069, 0x0300}, + { 0x00ED, 0x0069, 0x0301}, + { 0x00EE, 0x0069, 0x0302}, + { 0x0129, 0x0069, 0x0303}, + { 0x012B, 0x0069, 0x0304}, + { 0x012D, 0x0069, 0x0306}, + { 0x00EF, 0x0069, 0x0308}, + { 0x1EC9, 0x0069, 0x0309}, + { 0x01D0, 0x0069, 0x030C}, + { 0x0209, 0x0069, 0x030F}, + { 0x020B, 0x0069, 0x0311}, + { 0x1ECB, 0x0069, 0x0323}, + { 0x012F, 0x0069, 0x0328}, + { 0x1E2D, 0x0069, 0x0330}, + { 0x0135, 0x006A, 0x0302}, + { 0x01F0, 0x006A, 0x030C}, + { 0x1E31, 0x006B, 0x0301}, + { 0x01E9, 0x006B, 0x030C}, + { 0x1E33, 0x006B, 0x0323}, + { 0x0137, 0x006B, 0x0327}, + { 0x1E35, 0x006B, 0x0331}, + { 0x013A, 0x006C, 0x0301}, + { 0x013E, 0x006C, 0x030C}, + { 0x1E37, 0x006C, 0x0323}, + { 0x013C, 0x006C, 0x0327}, + { 0x1E3D, 0x006C, 0x032D}, + { 0x1E3B, 0x006C, 0x0331}, + { 0x1E3F, 0x006D, 0x0301}, + { 0x1E41, 0x006D, 0x0307}, + { 0x1E43, 0x006D, 0x0323}, + { 0x01F9, 0x006E, 0x0300}, + { 0x0144, 0x006E, 0x0301}, + { 0x00F1, 0x006E, 0x0303}, + { 0x1E45, 0x006E, 0x0307}, + { 0x0148, 0x006E, 0x030C}, + { 0x1E47, 0x006E, 0x0323}, + { 0x0146, 0x006E, 0x0327}, + { 0x1E4B, 0x006E, 0x032D}, + { 0x1E49, 0x006E, 0x0331}, + { 0x00F2, 0x006F, 0x0300}, + { 0x00F3, 0x006F, 0x0301}, + { 0x00F4, 0x006F, 0x0302}, + { 0x00F5, 0x006F, 0x0303}, + { 0x014D, 0x006F, 0x0304}, + { 0x014F, 0x006F, 0x0306}, + { 0x022F, 0x006F, 0x0307}, + { 0x00F6, 0x006F, 0x0308}, + { 0x1ECF, 0x006F, 0x0309}, + { 0x0151, 0x006F, 0x030B}, + { 0x01D2, 0x006F, 0x030C}, + { 0x020D, 0x006F, 0x030F}, + { 0x020F, 0x006F, 0x0311}, + { 0x01A1, 0x006F, 0x031B}, + { 0x1ECD, 0x006F, 0x0323}, + { 0x01EB, 0x006F, 0x0328}, + { 0x1E55, 0x0070, 0x0301}, + { 0x1E57, 0x0070, 0x0307}, + { 0x0155, 0x0072, 0x0301}, + { 0x1E59, 0x0072, 0x0307}, + { 0x0159, 0x0072, 0x030C}, + { 0x0211, 0x0072, 0x030F}, + { 0x0213, 0x0072, 0x0311}, + { 0x1E5B, 0x0072, 0x0323}, + { 0x0157, 0x0072, 0x0327}, + { 0x1E5F, 0x0072, 0x0331}, + { 0x015B, 0x0073, 0x0301}, + { 0x015D, 0x0073, 0x0302}, + { 0x1E61, 0x0073, 0x0307}, + { 0x0161, 0x0073, 0x030C}, + { 0x1E63, 0x0073, 0x0323}, + { 0x0219, 0x0073, 0x0326}, + { 0x015F, 0x0073, 0x0327}, + { 0x1E6B, 0x0074, 0x0307}, + { 0x1E97, 0x0074, 0x0308}, + { 0x0165, 0x0074, 0x030C}, + { 0x1E6D, 0x0074, 0x0323}, + { 0x021B, 0x0074, 0x0326}, + { 0x0163, 0x0074, 0x0327}, + { 0x1E71, 0x0074, 0x032D}, + { 0x1E6F, 0x0074, 0x0331}, + { 0x00F9, 0x0075, 0x0300}, + { 0x00FA, 0x0075, 0x0301}, + { 0x00FB, 0x0075, 0x0302}, + { 0x0169, 0x0075, 0x0303}, + { 0x016B, 0x0075, 0x0304}, + { 0x016D, 0x0075, 0x0306}, + { 0x00FC, 0x0075, 0x0308}, + { 0x1EE7, 0x0075, 0x0309}, + { 0x016F, 0x0075, 0x030A}, + { 0x0171, 0x0075, 0x030B}, + { 0x01D4, 0x0075, 0x030C}, + { 0x0215, 0x0075, 0x030F}, + { 0x0217, 0x0075, 0x0311}, + { 0x01B0, 0x0075, 0x031B}, + { 0x1EE5, 0x0075, 0x0323}, + { 0x1E73, 0x0075, 0x0324}, + { 0x0173, 0x0075, 0x0328}, + { 0x1E77, 0x0075, 0x032D}, + { 0x1E75, 0x0075, 0x0330}, + { 0x1E7D, 0x0076, 0x0303}, + { 0x1E7F, 0x0076, 0x0323}, + { 0x1E81, 0x0077, 0x0300}, + { 0x1E83, 0x0077, 0x0301}, + { 0x0175, 0x0077, 0x0302}, + { 0x1E87, 0x0077, 0x0307}, + { 0x1E85, 0x0077, 0x0308}, + { 0x1E98, 0x0077, 0x030A}, + { 0x1E89, 0x0077, 0x0323}, + { 0x1E8B, 0x0078, 0x0307}, + { 0x1E8D, 0x0078, 0x0308}, + { 0x1EF3, 0x0079, 0x0300}, + { 0x00FD, 0x0079, 0x0301}, + { 0x0177, 0x0079, 0x0302}, + { 0x1EF9, 0x0079, 0x0303}, + { 0x0233, 0x0079, 0x0304}, + { 0x1E8F, 0x0079, 0x0307}, + { 0x00FF, 0x0079, 0x0308}, + { 0x1EF7, 0x0079, 0x0309}, + { 0x1E99, 0x0079, 0x030A}, + { 0x1EF5, 0x0079, 0x0323}, + { 0x017A, 0x007A, 0x0301}, + { 0x1E91, 0x007A, 0x0302}, + { 0x017C, 0x007A, 0x0307}, + { 0x017E, 0x007A, 0x030C}, + { 0x1E93, 0x007A, 0x0323}, + { 0x1E95, 0x007A, 0x0331}, + { 0x1FED, 0x00A8, 0x0300}, + { 0x0385, 0x00A8, 0x0301}, + { 0x1FC1, 0x00A8, 0x0342}, + { 0x1EA6, 0x00C2, 0x0300}, + { 0x1EA4, 0x00C2, 0x0301}, + { 0x1EAA, 0x00C2, 0x0303}, + { 0x1EA8, 0x00C2, 0x0309}, + { 0x01DE, 0x00C4, 0x0304}, + { 0x01FA, 0x00C5, 0x0301}, + { 0x01FC, 0x00C6, 0x0301}, + { 0x01E2, 0x00C6, 0x0304}, + { 0x1E08, 0x00C7, 0x0301}, + { 0x1EC0, 0x00CA, 0x0300}, + { 0x1EBE, 0x00CA, 0x0301}, + { 0x1EC4, 0x00CA, 0x0303}, + { 0x1EC2, 0x00CA, 0x0309}, + { 0x1E2E, 0x00CF, 0x0301}, + { 0x1ED2, 0x00D4, 0x0300}, + { 0x1ED0, 0x00D4, 0x0301}, + { 0x1ED6, 0x00D4, 0x0303}, + { 0x1ED4, 0x00D4, 0x0309}, + { 0x1E4C, 0x00D5, 0x0301}, + { 0x022C, 0x00D5, 0x0304}, + { 0x1E4E, 0x00D5, 0x0308}, + { 0x022A, 0x00D6, 0x0304}, + { 0x01FE, 0x00D8, 0x0301}, + { 0x01DB, 0x00DC, 0x0300}, + { 0x01D7, 0x00DC, 0x0301}, + { 0x01D5, 0x00DC, 0x0304}, + { 0x01D9, 0x00DC, 0x030C}, + { 0x1EA7, 0x00E2, 0x0300}, + { 0x1EA5, 0x00E2, 0x0301}, + { 0x1EAB, 0x00E2, 0x0303}, + { 0x1EA9, 0x00E2, 0x0309}, + { 0x01DF, 0x00E4, 0x0304}, + { 0x01FB, 0x00E5, 0x0301}, + { 0x01FD, 0x00E6, 0x0301}, + { 0x01E3, 0x00E6, 0x0304}, + { 0x1E09, 0x00E7, 0x0301}, + { 0x1EC1, 0x00EA, 0x0300}, + { 0x1EBF, 0x00EA, 0x0301}, + { 0x1EC5, 0x00EA, 0x0303}, + { 0x1EC3, 0x00EA, 0x0309}, + { 0x1E2F, 0x00EF, 0x0301}, + { 0x1ED3, 0x00F4, 0x0300}, + { 0x1ED1, 0x00F4, 0x0301}, + { 0x1ED7, 0x00F4, 0x0303}, + { 0x1ED5, 0x00F4, 0x0309}, + { 0x1E4D, 0x00F5, 0x0301}, + { 0x022D, 0x00F5, 0x0304}, + { 0x1E4F, 0x00F5, 0x0308}, + { 0x022B, 0x00F6, 0x0304}, + { 0x01FF, 0x00F8, 0x0301}, + { 0x01DC, 0x00FC, 0x0300}, + { 0x01D8, 0x00FC, 0x0301}, + { 0x01D6, 0x00FC, 0x0304}, + { 0x01DA, 0x00FC, 0x030C}, + { 0x1EB0, 0x0102, 0x0300}, + { 0x1EAE, 0x0102, 0x0301}, + { 0x1EB4, 0x0102, 0x0303}, + { 0x1EB2, 0x0102, 0x0309}, + { 0x1EB1, 0x0103, 0x0300}, + { 0x1EAF, 0x0103, 0x0301}, + { 0x1EB5, 0x0103, 0x0303}, + { 0x1EB3, 0x0103, 0x0309}, + { 0x1E14, 0x0112, 0x0300}, + { 0x1E16, 0x0112, 0x0301}, + { 0x1E15, 0x0113, 0x0300}, + { 0x1E17, 0x0113, 0x0301}, + { 0x1E50, 0x014C, 0x0300}, + { 0x1E52, 0x014C, 0x0301}, + { 0x1E51, 0x014D, 0x0300}, + { 0x1E53, 0x014D, 0x0301}, + { 0x1E64, 0x015A, 0x0307}, + { 0x1E65, 0x015B, 0x0307}, + { 0x1E66, 0x0160, 0x0307}, + { 0x1E67, 0x0161, 0x0307}, + { 0x1E78, 0x0168, 0x0301}, + { 0x1E79, 0x0169, 0x0301}, + { 0x1E7A, 0x016A, 0x0308}, + { 0x1E7B, 0x016B, 0x0308}, + { 0x1E9B, 0x017F, 0x0307}, + { 0x1EDC, 0x01A0, 0x0300}, + { 0x1EDA, 0x01A0, 0x0301}, + { 0x1EE0, 0x01A0, 0x0303}, + { 0x1EDE, 0x01A0, 0x0309}, + { 0x1EE2, 0x01A0, 0x0323}, + { 0x1EDD, 0x01A1, 0x0300}, + { 0x1EDB, 0x01A1, 0x0301}, + { 0x1EE1, 0x01A1, 0x0303}, + { 0x1EDF, 0x01A1, 0x0309}, + { 0x1EE3, 0x01A1, 0x0323}, + { 0x1EEA, 0x01AF, 0x0300}, + { 0x1EE8, 0x01AF, 0x0301}, + { 0x1EEE, 0x01AF, 0x0303}, + { 0x1EEC, 0x01AF, 0x0309}, + { 0x1EF0, 0x01AF, 0x0323}, + { 0x1EEB, 0x01B0, 0x0300}, + { 0x1EE9, 0x01B0, 0x0301}, + { 0x1EEF, 0x01B0, 0x0303}, + { 0x1EED, 0x01B0, 0x0309}, + { 0x1EF1, 0x01B0, 0x0323}, + { 0x01EE, 0x01B7, 0x030C}, + { 0x01EC, 0x01EA, 0x0304}, + { 0x01ED, 0x01EB, 0x0304}, + { 0x01E0, 0x0226, 0x0304}, + { 0x01E1, 0x0227, 0x0304}, + { 0x1E1C, 0x0228, 0x0306}, + { 0x1E1D, 0x0229, 0x0306}, + { 0x0230, 0x022E, 0x0304}, + { 0x0231, 0x022F, 0x0304}, + { 0x01EF, 0x0292, 0x030C}, + { 0x0344, 0x0308, 0x0301}, + { 0x1FBA, 0x0391, 0x0300}, + { 0x0386, 0x0391, 0x0301}, + { 0x1FB9, 0x0391, 0x0304}, + { 0x1FB8, 0x0391, 0x0306}, + { 0x1F08, 0x0391, 0x0313}, + { 0x1F09, 0x0391, 0x0314}, + { 0x1FBC, 0x0391, 0x0345}, + { 0x1FC8, 0x0395, 0x0300}, + { 0x0388, 0x0395, 0x0301}, + { 0x1F18, 0x0395, 0x0313}, + { 0x1F19, 0x0395, 0x0314}, + { 0x1FCA, 0x0397, 0x0300}, + { 0x0389, 0x0397, 0x0301}, + { 0x1F28, 0x0397, 0x0313}, + { 0x1F29, 0x0397, 0x0314}, + { 0x1FCC, 0x0397, 0x0345}, + { 0x1FDA, 0x0399, 0x0300}, + { 0x038A, 0x0399, 0x0301}, + { 0x1FD9, 0x0399, 0x0304}, + { 0x1FD8, 0x0399, 0x0306}, + { 0x03AA, 0x0399, 0x0308}, + { 0x1F38, 0x0399, 0x0313}, + { 0x1F39, 0x0399, 0x0314}, + { 0x1FF8, 0x039F, 0x0300}, + { 0x038C, 0x039F, 0x0301}, + { 0x1F48, 0x039F, 0x0313}, + { 0x1F49, 0x039F, 0x0314}, + { 0x1FEC, 0x03A1, 0x0314}, + { 0x1FEA, 0x03A5, 0x0300}, + { 0x038E, 0x03A5, 0x0301}, + { 0x1FE9, 0x03A5, 0x0304}, + { 0x1FE8, 0x03A5, 0x0306}, + { 0x03AB, 0x03A5, 0x0308}, + { 0x1F59, 0x03A5, 0x0314}, + { 0x1FFA, 0x03A9, 0x0300}, + { 0x038F, 0x03A9, 0x0301}, + { 0x1F68, 0x03A9, 0x0313}, + { 0x1F69, 0x03A9, 0x0314}, + { 0x1FFC, 0x03A9, 0x0345}, + { 0x1FB4, 0x03AC, 0x0345}, + { 0x1FC4, 0x03AE, 0x0345}, + { 0x1F70, 0x03B1, 0x0300}, + { 0x03AC, 0x03B1, 0x0301}, + { 0x1FB1, 0x03B1, 0x0304}, + { 0x1FB0, 0x03B1, 0x0306}, + { 0x1F00, 0x03B1, 0x0313}, + { 0x1F01, 0x03B1, 0x0314}, + { 0x1FB6, 0x03B1, 0x0342}, + { 0x1FB3, 0x03B1, 0x0345}, + { 0x1F72, 0x03B5, 0x0300}, + { 0x03AD, 0x03B5, 0x0301}, + { 0x1F10, 0x03B5, 0x0313}, + { 0x1F11, 0x03B5, 0x0314}, + { 0x1F74, 0x03B7, 0x0300}, + { 0x03AE, 0x03B7, 0x0301}, + { 0x1F20, 0x03B7, 0x0313}, + { 0x1F21, 0x03B7, 0x0314}, + { 0x1FC6, 0x03B7, 0x0342}, + { 0x1FC3, 0x03B7, 0x0345}, + { 0x1F76, 0x03B9, 0x0300}, + { 0x03AF, 0x03B9, 0x0301}, + { 0x1FD1, 0x03B9, 0x0304}, + { 0x1FD0, 0x03B9, 0x0306}, + { 0x03CA, 0x03B9, 0x0308}, + { 0x1F30, 0x03B9, 0x0313}, + { 0x1F31, 0x03B9, 0x0314}, + { 0x1FD6, 0x03B9, 0x0342}, + { 0x1F78, 0x03BF, 0x0300}, + { 0x03CC, 0x03BF, 0x0301}, + { 0x1F40, 0x03BF, 0x0313}, + { 0x1F41, 0x03BF, 0x0314}, + { 0x1FE4, 0x03C1, 0x0313}, + { 0x1FE5, 0x03C1, 0x0314}, + { 0x1F7A, 0x03C5, 0x0300}, + { 0x03CD, 0x03C5, 0x0301}, + { 0x1FE1, 0x03C5, 0x0304}, + { 0x1FE0, 0x03C5, 0x0306}, + { 0x03CB, 0x03C5, 0x0308}, + { 0x1F50, 0x03C5, 0x0313}, + { 0x1F51, 0x03C5, 0x0314}, + { 0x1FE6, 0x03C5, 0x0342}, + { 0x1F7C, 0x03C9, 0x0300}, + { 0x03CE, 0x03C9, 0x0301}, + { 0x1F60, 0x03C9, 0x0313}, + { 0x1F61, 0x03C9, 0x0314}, + { 0x1FF6, 0x03C9, 0x0342}, + { 0x1FF3, 0x03C9, 0x0345}, + { 0x1FD2, 0x03CA, 0x0300}, + { 0x0390, 0x03CA, 0x0301}, + { 0x1FD7, 0x03CA, 0x0342}, + { 0x1FE2, 0x03CB, 0x0300}, + { 0x03B0, 0x03CB, 0x0301}, + { 0x1FE7, 0x03CB, 0x0342}, + { 0x1FF4, 0x03CE, 0x0345}, + { 0x03D3, 0x03D2, 0x0301}, + { 0x03D4, 0x03D2, 0x0308}, + { 0x0407, 0x0406, 0x0308}, + { 0x04D0, 0x0410, 0x0306}, + { 0x04D2, 0x0410, 0x0308}, + { 0x0403, 0x0413, 0x0301}, + { 0x0400, 0x0415, 0x0300}, + { 0x04D6, 0x0415, 0x0306}, + { 0x0401, 0x0415, 0x0308}, + { 0x04C1, 0x0416, 0x0306}, + { 0x04DC, 0x0416, 0x0308}, + { 0x04DE, 0x0417, 0x0308}, + { 0x040D, 0x0418, 0x0300}, + { 0x04E2, 0x0418, 0x0304}, + { 0x0419, 0x0418, 0x0306}, + { 0x04E4, 0x0418, 0x0308}, + { 0x040C, 0x041A, 0x0301}, + { 0x04E6, 0x041E, 0x0308}, + { 0x04EE, 0x0423, 0x0304}, + { 0x040E, 0x0423, 0x0306}, + { 0x04F0, 0x0423, 0x0308}, + { 0x04F2, 0x0423, 0x030B}, + { 0x04F4, 0x0427, 0x0308}, + { 0x04F8, 0x042B, 0x0308}, + { 0x04EC, 0x042D, 0x0308}, + { 0x04D1, 0x0430, 0x0306}, + { 0x04D3, 0x0430, 0x0308}, + { 0x0453, 0x0433, 0x0301}, + { 0x0450, 0x0435, 0x0300}, + { 0x04D7, 0x0435, 0x0306}, + { 0x0451, 0x0435, 0x0308}, + { 0x04C2, 0x0436, 0x0306}, + { 0x04DD, 0x0436, 0x0308}, + { 0x04DF, 0x0437, 0x0308}, + { 0x045D, 0x0438, 0x0300}, + { 0x04E3, 0x0438, 0x0304}, + { 0x0439, 0x0438, 0x0306}, + { 0x04E5, 0x0438, 0x0308}, + { 0x045C, 0x043A, 0x0301}, + { 0x04E7, 0x043E, 0x0308}, + { 0x04EF, 0x0443, 0x0304}, + { 0x045E, 0x0443, 0x0306}, + { 0x04F1, 0x0443, 0x0308}, + { 0x04F3, 0x0443, 0x030B}, + { 0x04F5, 0x0447, 0x0308}, + { 0x04F9, 0x044B, 0x0308}, + { 0x04ED, 0x044D, 0x0308}, + { 0x0457, 0x0456, 0x0308}, + { 0x0476, 0x0474, 0x030F}, + { 0x0477, 0x0475, 0x030F}, + { 0x04DA, 0x04D8, 0x0308}, + { 0x04DB, 0x04D9, 0x0308}, + { 0x04EA, 0x04E8, 0x0308}, + { 0x04EB, 0x04E9, 0x0308}, + { 0xFB2E, 0x05D0, 0x05B7}, + { 0xFB2F, 0x05D0, 0x05B8}, + { 0xFB30, 0x05D0, 0x05BC}, + { 0xFB31, 0x05D1, 0x05BC}, + { 0xFB4C, 0x05D1, 0x05BF}, + { 0xFB32, 0x05D2, 0x05BC}, + { 0xFB33, 0x05D3, 0x05BC}, + { 0xFB34, 0x05D4, 0x05BC}, + { 0xFB4B, 0x05D5, 0x05B9}, + { 0xFB35, 0x05D5, 0x05BC}, + { 0xFB36, 0x05D6, 0x05BC}, + { 0xFB38, 0x05D8, 0x05BC}, + { 0xFB1D, 0x05D9, 0x05B4}, + { 0xFB39, 0x05D9, 0x05BC}, + { 0xFB3A, 0x05DA, 0x05BC}, + { 0xFB3B, 0x05DB, 0x05BC}, + { 0xFB4D, 0x05DB, 0x05BF}, + { 0xFB3C, 0x05DC, 0x05BC}, + { 0xFB3E, 0x05DE, 0x05BC}, + { 0xFB40, 0x05E0, 0x05BC}, + { 0xFB41, 0x05E1, 0x05BC}, + { 0xFB43, 0x05E3, 0x05BC}, + { 0xFB44, 0x05E4, 0x05BC}, + { 0xFB4E, 0x05E4, 0x05BF}, + { 0xFB46, 0x05E6, 0x05BC}, + { 0xFB47, 0x05E7, 0x05BC}, + { 0xFB48, 0x05E8, 0x05BC}, + { 0xFB49, 0x05E9, 0x05BC}, + { 0xFB2A, 0x05E9, 0x05C1}, + { 0xFB2B, 0x05E9, 0x05C2}, + { 0xFB4A, 0x05EA, 0x05BC}, + { 0xFB1F, 0x05F2, 0x05B7}, + { 0x0622, 0x0627, 0x0653}, + { 0x0623, 0x0627, 0x0654}, + { 0x0625, 0x0627, 0x0655}, + { 0x0624, 0x0648, 0x0654}, + { 0x0626, 0x064A, 0x0654}, + { 0x06C2, 0x06C1, 0x0654}, + { 0x06D3, 0x06D2, 0x0654}, + { 0x06C0, 0x06D5, 0x0654}, + { 0x0958, 0x0915, 0x093C}, + { 0x0959, 0x0916, 0x093C}, + { 0x095A, 0x0917, 0x093C}, + { 0x095B, 0x091C, 0x093C}, + { 0x095C, 0x0921, 0x093C}, + { 0x095D, 0x0922, 0x093C}, + { 0x0929, 0x0928, 0x093C}, + { 0x095E, 0x092B, 0x093C}, + { 0x095F, 0x092F, 0x093C}, + { 0x0931, 0x0930, 0x093C}, + { 0x0934, 0x0933, 0x093C}, + { 0x09DC, 0x09A1, 0x09BC}, + { 0x09DD, 0x09A2, 0x09BC}, + { 0x09DF, 0x09AF, 0x09BC}, + { 0x09CB, 0x09C7, 0x09BE}, + { 0x09CC, 0x09C7, 0x09D7}, + { 0x0A59, 0x0A16, 0x0A3C}, + { 0x0A5A, 0x0A17, 0x0A3C}, + { 0x0A5B, 0x0A1C, 0x0A3C}, + { 0x0A5E, 0x0A2B, 0x0A3C}, + { 0x0A33, 0x0A32, 0x0A3C}, + { 0x0A36, 0x0A38, 0x0A3C}, + { 0x0B5C, 0x0B21, 0x0B3C}, + { 0x0B5D, 0x0B22, 0x0B3C}, + { 0x0B4B, 0x0B47, 0x0B3E}, + { 0x0B48, 0x0B47, 0x0B56}, + { 0x0B4C, 0x0B47, 0x0B57}, + { 0x0B94, 0x0B92, 0x0BD7}, + { 0x0BCA, 0x0BC6, 0x0BBE}, + { 0x0BCC, 0x0BC6, 0x0BD7}, + { 0x0BCB, 0x0BC7, 0x0BBE}, + { 0x0C48, 0x0C46, 0x0C56}, + { 0x0CC0, 0x0CBF, 0x0CD5}, + { 0x0CCA, 0x0CC6, 0x0CC2}, + { 0x0CC7, 0x0CC6, 0x0CD5}, + { 0x0CC8, 0x0CC6, 0x0CD6}, + { 0x0CCB, 0x0CCA, 0x0CD5}, + { 0x0D4A, 0x0D46, 0x0D3E}, + { 0x0D4C, 0x0D46, 0x0D57}, + { 0x0D4B, 0x0D47, 0x0D3E}, + { 0x0DDA, 0x0DD9, 0x0DCA}, + { 0x0DDC, 0x0DD9, 0x0DCF}, + { 0x0DDE, 0x0DD9, 0x0DDF}, + { 0x0DDD, 0x0DDC, 0x0DCA}, + { 0x0F69, 0x0F40, 0x0FB5}, + { 0x0F43, 0x0F42, 0x0FB7}, + { 0x0F4D, 0x0F4C, 0x0FB7}, + { 0x0F52, 0x0F51, 0x0FB7}, + { 0x0F57, 0x0F56, 0x0FB7}, + { 0x0F5C, 0x0F5B, 0x0FB7}, + { 0x0F73, 0x0F71, 0x0F72}, + { 0x0F75, 0x0F71, 0x0F74}, + { 0x0F81, 0x0F71, 0x0F80}, + { 0x0FB9, 0x0F90, 0x0FB5}, + { 0x0F93, 0x0F92, 0x0FB7}, + { 0x0F9D, 0x0F9C, 0x0FB7}, + { 0x0FA2, 0x0FA1, 0x0FB7}, + { 0x0FA7, 0x0FA6, 0x0FB7}, + { 0x0FAC, 0x0FAB, 0x0FB7}, + { 0x0F76, 0x0FB2, 0x0F80}, + { 0x0F78, 0x0FB3, 0x0F80}, + { 0x1026, 0x1025, 0x102E}, + { 0x1B06, 0x1B05, 0x1B35}, + { 0x1B08, 0x1B07, 0x1B35}, + { 0x1B0A, 0x1B09, 0x1B35}, + { 0x1B0C, 0x1B0B, 0x1B35}, + { 0x1B0E, 0x1B0D, 0x1B35}, + { 0x1B12, 0x1B11, 0x1B35}, + { 0x1B3B, 0x1B3A, 0x1B35}, + { 0x1B3D, 0x1B3C, 0x1B35}, + { 0x1B40, 0x1B3E, 0x1B35}, + { 0x1B41, 0x1B3F, 0x1B35}, + { 0x1B43, 0x1B42, 0x1B35}, + { 0x1E38, 0x1E36, 0x0304}, + { 0x1E39, 0x1E37, 0x0304}, + { 0x1E5C, 0x1E5A, 0x0304}, + { 0x1E5D, 0x1E5B, 0x0304}, + { 0x1E68, 0x1E62, 0x0307}, + { 0x1E69, 0x1E63, 0x0307}, + { 0x1EAC, 0x1EA0, 0x0302}, + { 0x1EB6, 0x1EA0, 0x0306}, + { 0x1EAD, 0x1EA1, 0x0302}, + { 0x1EB7, 0x1EA1, 0x0306}, + { 0x1EC6, 0x1EB8, 0x0302}, + { 0x1EC7, 0x1EB9, 0x0302}, + { 0x1ED8, 0x1ECC, 0x0302}, + { 0x1ED9, 0x1ECD, 0x0302}, + { 0x1F02, 0x1F00, 0x0300}, + { 0x1F04, 0x1F00, 0x0301}, + { 0x1F06, 0x1F00, 0x0342}, + { 0x1F80, 0x1F00, 0x0345}, + { 0x1F03, 0x1F01, 0x0300}, + { 0x1F05, 0x1F01, 0x0301}, + { 0x1F07, 0x1F01, 0x0342}, + { 0x1F81, 0x1F01, 0x0345}, + { 0x1F82, 0x1F02, 0x0345}, + { 0x1F83, 0x1F03, 0x0345}, + { 0x1F84, 0x1F04, 0x0345}, + { 0x1F85, 0x1F05, 0x0345}, + { 0x1F86, 0x1F06, 0x0345}, + { 0x1F87, 0x1F07, 0x0345}, + { 0x1F0A, 0x1F08, 0x0300}, + { 0x1F0C, 0x1F08, 0x0301}, + { 0x1F0E, 0x1F08, 0x0342}, + { 0x1F88, 0x1F08, 0x0345}, + { 0x1F0B, 0x1F09, 0x0300}, + { 0x1F0D, 0x1F09, 0x0301}, + { 0x1F0F, 0x1F09, 0x0342}, + { 0x1F89, 0x1F09, 0x0345}, + { 0x1F8A, 0x1F0A, 0x0345}, + { 0x1F8B, 0x1F0B, 0x0345}, + { 0x1F8C, 0x1F0C, 0x0345}, + { 0x1F8D, 0x1F0D, 0x0345}, + { 0x1F8E, 0x1F0E, 0x0345}, + { 0x1F8F, 0x1F0F, 0x0345}, + { 0x1F12, 0x1F10, 0x0300}, + { 0x1F14, 0x1F10, 0x0301}, + { 0x1F13, 0x1F11, 0x0300}, + { 0x1F15, 0x1F11, 0x0301}, + { 0x1F1A, 0x1F18, 0x0300}, + { 0x1F1C, 0x1F18, 0x0301}, + { 0x1F1B, 0x1F19, 0x0300}, + { 0x1F1D, 0x1F19, 0x0301}, + { 0x1F22, 0x1F20, 0x0300}, + { 0x1F24, 0x1F20, 0x0301}, + { 0x1F26, 0x1F20, 0x0342}, + { 0x1F90, 0x1F20, 0x0345}, + { 0x1F23, 0x1F21, 0x0300}, + { 0x1F25, 0x1F21, 0x0301}, + { 0x1F27, 0x1F21, 0x0342}, + { 0x1F91, 0x1F21, 0x0345}, + { 0x1F92, 0x1F22, 0x0345}, + { 0x1F93, 0x1F23, 0x0345}, + { 0x1F94, 0x1F24, 0x0345}, + { 0x1F95, 0x1F25, 0x0345}, + { 0x1F96, 0x1F26, 0x0345}, + { 0x1F97, 0x1F27, 0x0345}, + { 0x1F2A, 0x1F28, 0x0300}, + { 0x1F2C, 0x1F28, 0x0301}, + { 0x1F2E, 0x1F28, 0x0342}, + { 0x1F98, 0x1F28, 0x0345}, + { 0x1F2B, 0x1F29, 0x0300}, + { 0x1F2D, 0x1F29, 0x0301}, + { 0x1F2F, 0x1F29, 0x0342}, + { 0x1F99, 0x1F29, 0x0345}, + { 0x1F9A, 0x1F2A, 0x0345}, + { 0x1F9B, 0x1F2B, 0x0345}, + { 0x1F9C, 0x1F2C, 0x0345}, + { 0x1F9D, 0x1F2D, 0x0345}, + { 0x1F9E, 0x1F2E, 0x0345}, + { 0x1F9F, 0x1F2F, 0x0345}, + { 0x1F32, 0x1F30, 0x0300}, + { 0x1F34, 0x1F30, 0x0301}, + { 0x1F36, 0x1F30, 0x0342}, + { 0x1F33, 0x1F31, 0x0300}, + { 0x1F35, 0x1F31, 0x0301}, + { 0x1F37, 0x1F31, 0x0342}, + { 0x1F3A, 0x1F38, 0x0300}, + { 0x1F3C, 0x1F38, 0x0301}, + { 0x1F3E, 0x1F38, 0x0342}, + { 0x1F3B, 0x1F39, 0x0300}, + { 0x1F3D, 0x1F39, 0x0301}, + { 0x1F3F, 0x1F39, 0x0342}, + { 0x1F42, 0x1F40, 0x0300}, + { 0x1F44, 0x1F40, 0x0301}, + { 0x1F43, 0x1F41, 0x0300}, + { 0x1F45, 0x1F41, 0x0301}, + { 0x1F4A, 0x1F48, 0x0300}, + { 0x1F4C, 0x1F48, 0x0301}, + { 0x1F4B, 0x1F49, 0x0300}, + { 0x1F4D, 0x1F49, 0x0301}, + { 0x1F52, 0x1F50, 0x0300}, + { 0x1F54, 0x1F50, 0x0301}, + { 0x1F56, 0x1F50, 0x0342}, + { 0x1F53, 0x1F51, 0x0300}, + { 0x1F55, 0x1F51, 0x0301}, + { 0x1F57, 0x1F51, 0x0342}, + { 0x1F5B, 0x1F59, 0x0300}, + { 0x1F5D, 0x1F59, 0x0301}, + { 0x1F5F, 0x1F59, 0x0342}, + { 0x1F62, 0x1F60, 0x0300}, + { 0x1F64, 0x1F60, 0x0301}, + { 0x1F66, 0x1F60, 0x0342}, + { 0x1FA0, 0x1F60, 0x0345}, + { 0x1F63, 0x1F61, 0x0300}, + { 0x1F65, 0x1F61, 0x0301}, + { 0x1F67, 0x1F61, 0x0342}, + { 0x1FA1, 0x1F61, 0x0345}, + { 0x1FA2, 0x1F62, 0x0345}, + { 0x1FA3, 0x1F63, 0x0345}, + { 0x1FA4, 0x1F64, 0x0345}, + { 0x1FA5, 0x1F65, 0x0345}, + { 0x1FA6, 0x1F66, 0x0345}, + { 0x1FA7, 0x1F67, 0x0345}, + { 0x1F6A, 0x1F68, 0x0300}, + { 0x1F6C, 0x1F68, 0x0301}, + { 0x1F6E, 0x1F68, 0x0342}, + { 0x1FA8, 0x1F68, 0x0345}, + { 0x1F6B, 0x1F69, 0x0300}, + { 0x1F6D, 0x1F69, 0x0301}, + { 0x1F6F, 0x1F69, 0x0342}, + { 0x1FA9, 0x1F69, 0x0345}, + { 0x1FAA, 0x1F6A, 0x0345}, + { 0x1FAB, 0x1F6B, 0x0345}, + { 0x1FAC, 0x1F6C, 0x0345}, + { 0x1FAD, 0x1F6D, 0x0345}, + { 0x1FAE, 0x1F6E, 0x0345}, + { 0x1FAF, 0x1F6F, 0x0345}, + { 0x1FB2, 0x1F70, 0x0345}, + { 0x1FC2, 0x1F74, 0x0345}, + { 0x1FF2, 0x1F7C, 0x0345}, + { 0x1FB7, 0x1FB6, 0x0345}, + { 0x1FCD, 0x1FBF, 0x0300}, + { 0x1FCE, 0x1FBF, 0x0301}, + { 0x1FCF, 0x1FBF, 0x0342}, + { 0x1FC7, 0x1FC6, 0x0345}, + { 0x1FF7, 0x1FF6, 0x0345}, + { 0x1FDD, 0x1FFE, 0x0300}, + { 0x1FDE, 0x1FFE, 0x0301}, + { 0x1FDF, 0x1FFE, 0x0342}, + { 0x219A, 0x2190, 0x0338}, + { 0x219B, 0x2192, 0x0338}, + { 0x21AE, 0x2194, 0x0338}, + { 0x21CD, 0x21D0, 0x0338}, + { 0x21CF, 0x21D2, 0x0338}, + { 0x21CE, 0x21D4, 0x0338}, + { 0x2204, 0x2203, 0x0338}, + { 0x2209, 0x2208, 0x0338}, + { 0x220C, 0x220B, 0x0338}, + { 0x2224, 0x2223, 0x0338}, + { 0x2226, 0x2225, 0x0338}, + { 0x2241, 0x223C, 0x0338}, + { 0x2244, 0x2243, 0x0338}, + { 0x2247, 0x2245, 0x0338}, + { 0x2249, 0x2248, 0x0338}, + { 0x226D, 0x224D, 0x0338}, + { 0x2262, 0x2261, 0x0338}, + { 0x2270, 0x2264, 0x0338}, + { 0x2271, 0x2265, 0x0338}, + { 0x2274, 0x2272, 0x0338}, + { 0x2275, 0x2273, 0x0338}, + { 0x2278, 0x2276, 0x0338}, + { 0x2279, 0x2277, 0x0338}, + { 0x2280, 0x227A, 0x0338}, + { 0x2281, 0x227B, 0x0338}, + { 0x22E0, 0x227C, 0x0338}, + { 0x22E1, 0x227D, 0x0338}, + { 0x2284, 0x2282, 0x0338}, + { 0x2285, 0x2283, 0x0338}, + { 0x2288, 0x2286, 0x0338}, + { 0x2289, 0x2287, 0x0338}, + { 0x22E2, 0x2291, 0x0338}, + { 0x22E3, 0x2292, 0x0338}, + { 0x22AC, 0x22A2, 0x0338}, + { 0x22AD, 0x22A8, 0x0338}, + { 0x22AE, 0x22A9, 0x0338}, + { 0x22AF, 0x22AB, 0x0338}, + { 0x22EA, 0x22B2, 0x0338}, + { 0x22EB, 0x22B3, 0x0338}, + { 0x22EC, 0x22B4, 0x0338}, + { 0x22ED, 0x22B5, 0x0338}, + { 0x2ADC, 0x2ADD, 0x0338}, + { 0x3094, 0x3046, 0x3099}, + { 0x304C, 0x304B, 0x3099}, + { 0x304E, 0x304D, 0x3099}, + { 0x3050, 0x304F, 0x3099}, + { 0x3052, 0x3051, 0x3099}, + { 0x3054, 0x3053, 0x3099}, + { 0x3056, 0x3055, 0x3099}, + { 0x3058, 0x3057, 0x3099}, + { 0x305A, 0x3059, 0x3099}, + { 0x305C, 0x305B, 0x3099}, + { 0x305E, 0x305D, 0x3099}, + { 0x3060, 0x305F, 0x3099}, + { 0x3062, 0x3061, 0x3099}, + { 0x3065, 0x3064, 0x3099}, + { 0x3067, 0x3066, 0x3099}, + { 0x3069, 0x3068, 0x3099}, + { 0x3070, 0x306F, 0x3099}, + { 0x3071, 0x306F, 0x309A}, + { 0x3073, 0x3072, 0x3099}, + { 0x3074, 0x3072, 0x309A}, + { 0x3076, 0x3075, 0x3099}, + { 0x3077, 0x3075, 0x309A}, + { 0x3079, 0x3078, 0x3099}, + { 0x307A, 0x3078, 0x309A}, + { 0x307C, 0x307B, 0x3099}, + { 0x307D, 0x307B, 0x309A}, + { 0x309E, 0x309D, 0x3099}, + { 0x30F4, 0x30A6, 0x3099}, + { 0x30AC, 0x30AB, 0x3099}, + { 0x30AE, 0x30AD, 0x3099}, + { 0x30B0, 0x30AF, 0x3099}, + { 0x30B2, 0x30B1, 0x3099}, + { 0x30B4, 0x30B3, 0x3099}, + { 0x30B6, 0x30B5, 0x3099}, + { 0x30B8, 0x30B7, 0x3099}, + { 0x30BA, 0x30B9, 0x3099}, + { 0x30BC, 0x30BB, 0x3099}, + { 0x30BE, 0x30BD, 0x3099}, + { 0x30C0, 0x30BF, 0x3099}, + { 0x30C2, 0x30C1, 0x3099}, + { 0x30C5, 0x30C4, 0x3099}, + { 0x30C7, 0x30C6, 0x3099}, + { 0x30C9, 0x30C8, 0x3099}, + { 0x30D0, 0x30CF, 0x3099}, + { 0x30D1, 0x30CF, 0x309A}, + { 0x30D3, 0x30D2, 0x3099}, + { 0x30D4, 0x30D2, 0x309A}, + { 0x30D6, 0x30D5, 0x3099}, + { 0x30D7, 0x30D5, 0x309A}, + { 0x30D9, 0x30D8, 0x3099}, + { 0x30DA, 0x30D8, 0x309A}, + { 0x30DC, 0x30DB, 0x3099}, + { 0x30DD, 0x30DB, 0x309A}, + { 0x30F7, 0x30EF, 0x3099}, + { 0x30F8, 0x30F0, 0x3099}, + { 0x30F9, 0x30F1, 0x3099}, + { 0x30FA, 0x30F2, 0x3099}, + { 0x30FE, 0x30FD, 0x3099}, + { 0xFB2C, 0xFB49, 0x05C1}, + { 0xFB2D, 0xFB49, 0x05C2}, + }; + + private static final int UNICODE_SHIFT = 21; + + public static char precompose(char base, char comb) { + int min = 0; + int max = precompositions.length - 1; + int mid; + + long sought = base << UNICODE_SHIFT | comb; + long that; + + while (max >= min) { + mid = (min + max) / 2; + that = precompositions[mid][1] << UNICODE_SHIFT | precompositions[mid][2]; + if (that < sought) + min = mid + 1; + else if (that > sought) + max = mid - 1; + else + return precompositions[mid][0]; + } + + // No match; return character without combiner + return base; + } +} diff --git a/app/src/main/java/de/mud/terminal/VDUBuffer.java b/app/src/main/java/de/mud/terminal/VDUBuffer.java new file mode 100644 index 0000000..93e3ccf --- /dev/null +++ b/app/src/main/java/de/mud/terminal/VDUBuffer.java @@ -0,0 +1,854 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Mei�ner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ + +package de.mud.terminal; + +import java.util.Arrays; + +/** + * Implementation of a Video Display Unit (VDU) buffer. This class contains + * all methods to manipulate the buffer that stores characters and their + * attributes as well as the regions displayed. + * + * @author Matthias L. Jugel, Marcus Meißner + * @version $Id: VDUBuffer.java 503 2005-10-24 07:34:13Z marcus $ + */ +public class VDUBuffer { + + /** The current version id tag */ + public final static String ID = "$Id: VDUBuffer.java 503 2005-10-24 07:34:13Z marcus $"; + + /** Enable debug messages. */ + public final static int debug = 0; + + public int height, width; /* rows and columns */ + public boolean[] update; /* contains the lines that need update */ + public char[][] charArray; /* contains the characters */ + public int[][] charAttributes; /* contains character attrs */ + public int bufSize; + public int maxBufSize; /* buffer sizes */ + public int screenBase; /* the actual screen start */ + public int windowBase; /* where the start displaying */ + public int scrollMarker; /* marks the last line inserted */ + + private int topMargin; /* top scroll margin */ + private int bottomMargin; /* bottom scroll margin */ + + // cursor variables + protected boolean showcursor = true; + protected int cursorX, cursorY; + + /** Scroll up when inserting a line. */ + public final static boolean SCROLL_UP = false; + /** Scroll down when inserting a line. */ + public final static boolean SCROLL_DOWN = true; + + /* Attributes bit-field usage: + * + * 8421 8421 8421 8421 8421 8421 8421 8421 + * |||| |||| |||| |||| |||| |||| |||| |||`- Bold + * |||| |||| |||| |||| |||| |||| |||| ||`-- Underline + * |||| |||| |||| |||| |||| |||| |||| |`--- Invert + * |||| |||| |||| |||| |||| |||| |||| `---- Low + * |||| |||| |||| |||| |||| |||| |||`------ Invisible + * |||| |||| |||| |||| ||`+-++++-+++------- Foreground Color + * |||| |||| |`++-++++-++------------------ Background Color + * |||| |||| `----------------------------- Fullwidth character + * `+++-++++------------------------------- Reserved for future use + */ + + /** Make character normal. */ + public final static int NORMAL = 0x00; + /** Make character bold. */ + public final static int BOLD = 0x01; + /** Underline character. */ + public final static int UNDERLINE = 0x02; + /** Invert character. */ + public final static int INVERT = 0x04; + /** Lower intensity character. */ + public final static int LOW = 0x08; + /** Invisible character. */ + public final static int INVISIBLE = 0x10; + /** Unicode full-width character (CJK, et al.) */ + public final static int FULLWIDTH = 0x8000000; + + /** how much to left shift the foreground color */ + public final static int COLOR_FG_SHIFT = 5; + /** how much to left shift the background color */ + public final static int COLOR_BG_SHIFT = 14; + /** color mask */ + public final static int COLOR = 0x7fffe0; /* 0000 0000 0111 1111 1111 1111 1110 0000 */ + /** foreground color mask */ + public final static int COLOR_FG = 0x3fe0; /* 0000 0000 0000 0000 0011 1111 1110 0000 */ + /** background color mask */ + public final static int COLOR_BG = 0x7fc000; /* 0000 0000 0111 1111 1100 0000 0000 0000 */ + + /** + * Create a new video display buffer with the passed width and height in + * characters. + * @param width the length of the character lines + * @param height the amount of lines on the screen + */ + public VDUBuffer(int width, int height) { + // set the display screen size + setScreenSize(width, height, false); + } + + /** + * Create a standard video display buffer with 80 columns and 24 lines. + */ + public VDUBuffer() { + this(80, 24); + } + + /** + * Put a character on the screen with normal font and outline. + * The character previously on that position will be overwritten. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param ch the character to show on the screen + * @see #insertChar + * @see #deleteChar + * @see #redraw + */ + public void putChar(int c, int l, char ch) { + putChar(c, l, ch, NORMAL); + } + + /** + * Put a character on the screen with specific font and outline. + * The character previously on that position will be overwritten. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param ch the character to show on the screen + * @param attributes the character attributes + * @see #BOLD + * @see #UNDERLINE + * @see #INVERT + * @see #INVISIBLE + * @see #NORMAL + * @see #LOW + * @see #insertChar + * @see #deleteChar + * @see #redraw + */ + + public void putChar(int c, int l, char ch, int attributes) { + charArray[screenBase + l][c] = ch; + charAttributes[screenBase + l][c] = attributes; + if (l < height) + update[l + 1] = true; + } + + /** + * Get the character at the specified position. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @see #putChar + */ + public char getChar(int c, int l) { + return charArray[screenBase + l][c]; + } + + /** + * Get the attributes for the specified position. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @see #putChar + */ + public int getAttributes(int c, int l) { + return charAttributes[screenBase + l][c]; + } + + /** + * Insert a character at a specific position on the screen. + * All character right to from this position will be moved one to the right. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param ch the character to insert + * @param attributes the character attributes + * @see #BOLD + * @see #UNDERLINE + * @see #INVERT + * @see #INVISIBLE + * @see #NORMAL + * @see #LOW + * @see #putChar + * @see #deleteChar + * @see #redraw + */ + public void insertChar(int c, int l, char ch, int attributes) { + System.arraycopy(charArray[screenBase + l], c, + charArray[screenBase + l], c + 1, width - c - 1); + System.arraycopy(charAttributes[screenBase + l], c, + charAttributes[screenBase + l], c + 1, width - c - 1); + putChar(c, l, ch, attributes); + } + + /** + * Delete a character at a given position on the screen. + * All characters right to the position will be moved one to the left. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @see #putChar + * @see #insertChar + * @see #redraw + */ + public void deleteChar(int c, int l) { + if (c < width - 1) { + System.arraycopy(charArray[screenBase + l], c + 1, + charArray[screenBase + l], c, width - c - 1); + System.arraycopy(charAttributes[screenBase + l], c + 1, + charAttributes[screenBase + l], c, width - c - 1); + } + putChar(width - 1, l, (char) 0); + } + + /** + * Put a String at a specific position. Any characters previously on that + * position will be overwritten. You need to call redraw() for screen update. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param s the string to be shown on the screen + * @see #BOLD + * @see #UNDERLINE + * @see #INVERT + * @see #INVISIBLE + * @see #NORMAL + * @see #LOW + * @see #putChar + * @see #insertLine + * @see #deleteLine + * @see #redraw + */ + public void putString(int c, int l, String s) { + putString(c, l, s, NORMAL); + } + + /** + * Put a String at a specific position giving all characters the same + * attributes. Any characters previously on that position will be + * overwritten. You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param s the string to be shown on the screen + * @param attributes character attributes + * @see #BOLD + * @see #UNDERLINE + * @see #INVERT + * @see #INVISIBLE + * @see #NORMAL + * @see #LOW + * @see #putChar + * @see #insertLine + * @see #deleteLine + * @see #redraw + */ + public void putString(int c, int l, String s, int attributes) { + for (int i = 0; i < s.length() && c + i < width; i++) + putChar(c + i, l, s.charAt(i), attributes); + } + + /** + * Insert a blank line at a specific position. + * The current line and all previous lines are scrolled one line up. The + * top line is lost. You need to call redraw() to update the screen. + * @param l the y-coordinate to insert the line + * @see #deleteLine + * @see #redraw + */ + public void insertLine(int l) { + insertLine(l, 1, SCROLL_UP); + } + + /** + * Insert blank lines at a specific position. + * You need to call redraw() to update the screen + * @param l the y-coordinate to insert the line + * @param n amount of lines to be inserted + * @see #deleteLine + * @see #redraw + */ + public void insertLine(int l, int n) { + insertLine(l, n, SCROLL_UP); + } + + /** + * Insert a blank line at a specific position. Scroll text according to + * the argument. + * You need to call redraw() to update the screen + * @param l the y-coordinate to insert the line + * @param scrollDown scroll down + * @see #deleteLine + * @see #SCROLL_UP + * @see #SCROLL_DOWN + * @see #redraw + */ + public void insertLine(int l, boolean scrollDown) { + insertLine(l, 1, scrollDown); + } + + /** + * Insert blank lines at a specific position. + * The current line and all previous lines are scrolled one line up. The + * top line is lost. You need to call redraw() to update the screen. + * @param l the y-coordinate to insert the line + * @param n number of lines to be inserted + * @param scrollDown scroll down + * @see #deleteLine + * @see #SCROLL_UP + * @see #SCROLL_DOWN + * @see #redraw + */ + public synchronized void insertLine(int l, int n, boolean scrollDown) { + char cbuf[][] = null; + int abuf[][] = null; + int offset = 0; + int oldBase = screenBase; + + int newScreenBase = screenBase; + int newWindowBase = windowBase; + int newBufSize = bufSize; + + if (l > bottomMargin) /* We do not scroll below bottom margin (below the scrolling region). */ + return; + int top = (l < topMargin ? + 0 : (l > bottomMargin ? + (bottomMargin + 1 < height ? + bottomMargin + 1 : height - 1) : topMargin)); + int bottom = (l > bottomMargin ? + height - 1 : (l < topMargin ? + (topMargin > 0 ? + topMargin - 1 : 0) : bottomMargin)); + + // System.out.println("l is "+l+", top is "+top+", bottom is "+bottom+", bottomargin is "+bottomMargin+", topMargin is "+topMargin); + + if (scrollDown) { + if (n > (bottom - top)) n = (bottom - top); + int size = bottom - l - (n - 1); + if(size < 0) size = 0; + cbuf = new char[size][]; + abuf = new int[size][]; + + System.arraycopy(charArray, oldBase + l, cbuf, 0, bottom - l - (n - 1)); + System.arraycopy(charAttributes, oldBase + l, + abuf, 0, bottom - l - (n - 1)); + System.arraycopy(cbuf, 0, charArray, oldBase + l + n, + bottom - l - (n - 1)); + System.arraycopy(abuf, 0, charAttributes, oldBase + l + n, + bottom - l - (n - 1)); + cbuf = charArray; + abuf = charAttributes; + } else { + try { + if (n > (bottom - top) + 1) n = (bottom - top) + 1; + if (bufSize < maxBufSize) { + if (bufSize + n > maxBufSize) { + offset = n - (maxBufSize - bufSize); + scrollMarker += offset; + newBufSize = maxBufSize; + newScreenBase = maxBufSize - height - 1; + newWindowBase = screenBase; + } else { + scrollMarker += n; + newScreenBase += n; + newWindowBase += n; + newBufSize += n; + } + + cbuf = new char[newBufSize][]; + abuf = new int[newBufSize][]; + } else { + offset = n; + cbuf = charArray; + abuf = charAttributes; + } + // copy anything from the top of the buffer (+offset) to the new top + // up to the screenBase. + if (oldBase > 0) { + System.arraycopy(charArray, offset, + cbuf, 0, + oldBase - offset); + System.arraycopy(charAttributes, offset, + abuf, 0, + oldBase - offset); + } + // copy anything from the top of the screen (screenBase) up to the + // topMargin to the new screen + if (top > 0) { + System.arraycopy(charArray, oldBase, + cbuf, newScreenBase, + top); + System.arraycopy(charAttributes, oldBase, + abuf, newScreenBase, + top); + } + // copy anything from the topMargin up to the amount of lines inserted + // to the gap left over between scrollback buffer and screenBase + if (oldBase >= 0) { + System.arraycopy(charArray, oldBase + top, + cbuf, oldBase - offset, + n); + System.arraycopy(charAttributes, oldBase + top, + abuf, oldBase - offset, + n); + } + // copy anything from topMargin + n up to the line linserted to the + // topMargin + System.arraycopy(charArray, oldBase + top + n, + cbuf, newScreenBase + top, + l - top - (n - 1)); + System.arraycopy(charAttributes, oldBase + top + n, + abuf, newScreenBase + top, + l - top - (n - 1)); + // + // copy the all lines next to the inserted to the new buffer + if (l < height - 1) { + System.arraycopy(charArray, oldBase + l + 1, + cbuf, newScreenBase + l + 1, + (height - 1) - l); + System.arraycopy(charAttributes, oldBase + l + 1, + abuf, newScreenBase + l + 1, + (height - 1) - l); + } + } catch (ArrayIndexOutOfBoundsException e) { + // this should not happen anymore, but I will leave the code + // here in case something happens anyway. That code above is + // so complex I always have a hard time understanding what + // I did, even though there are comments + System.err.println("*** Error while scrolling up:"); + System.err.println("--- BEGIN STACK TRACE ---"); + e.printStackTrace(); + System.err.println("--- END STACK TRACE ---"); + System.err.println("bufSize=" + bufSize + ", maxBufSize=" + maxBufSize); + System.err.println("top=" + top + ", bottom=" + bottom); + System.err.println("n=" + n + ", l=" + l); + System.err.println("screenBase=" + screenBase + ", windowBase=" + windowBase); + System.err.println("newScreenBase=" + newScreenBase + ", newWindowBase=" + newWindowBase); + System.err.println("oldBase=" + oldBase); + System.err.println("size.width=" + width + ", size.height=" + height); + System.err.println("abuf.length=" + abuf.length + ", cbuf.length=" + cbuf.length); + System.err.println("*** done dumping debug information"); + } + } + + // this is a little helper to mark the scrolling + scrollMarker -= n; + + + for (int i = 0; i < n; i++) { + cbuf[(newScreenBase + l) + (scrollDown ? i : -i)] = new char[width]; + Arrays.fill(cbuf[(newScreenBase + l) + (scrollDown ? i : -i)], ' '); + abuf[(newScreenBase + l) + (scrollDown ? i : -i)] = new int[width]; + } + + charArray = cbuf; + charAttributes = abuf; + screenBase = newScreenBase; + windowBase = newWindowBase; + bufSize = newBufSize; + + if (scrollDown) + markLine(l, bottom - l + 1); + else + markLine(top, l - top + 1); + + display.updateScrollBar(); + } + + /** + * Delete a line at a specific position. Subsequent lines will be scrolled + * up to fill the space and a blank line is inserted at the end of the + * screen. + * @param l the y-coordinate to insert the line + * @see #deleteLine + */ + public void deleteLine(int l) { + int bottom = (l > bottomMargin ? height - 1: + (l < topMargin?topMargin:bottomMargin + 1)); + int numRows = bottom - l - 1; + + char[] discardedChars = charArray[screenBase + l]; + int[] discardedAttributes = charAttributes[screenBase + l]; + + if (numRows > 0) { + System.arraycopy(charArray, screenBase + l + 1, + charArray, screenBase + l, numRows); + System.arraycopy(charAttributes, screenBase + l + 1, + charAttributes, screenBase + l, numRows); + } + + int newBottomRow = screenBase + bottom - 1; + charArray[newBottomRow] = discardedChars; + charAttributes[newBottomRow] = discardedAttributes; + Arrays.fill(charArray[newBottomRow], ' '); + Arrays.fill(charAttributes[newBottomRow], 0); + + markLine(l, bottom - l); + } + + /** + * Delete a rectangular portion of the screen. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (row) + * @param w with of the area in characters + * @param h height of the area in characters + * @param curAttr attribute to fill + * @see #deleteChar + * @see #deleteLine + * @see #redraw + */ + public void deleteArea(int c, int l, int w, int h, int curAttr) { + int endColumn = c + w; + int targetRow = screenBase + l; + for (int i = 0; i < h && l + i < height; i++) { + Arrays.fill(charAttributes[targetRow], c, endColumn, curAttr); + Arrays.fill(charArray[targetRow], c, endColumn, ' '); + targetRow++; + } + markLine(l, h); + } + + /** + * Delete a rectangular portion of the screen. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (row) + * @param w with of the area in characters + * @param h height of the area in characters + * @see #deleteChar + * @see #deleteLine + * @see #redraw + */ + public void deleteArea(int c, int l, int w, int h) { + deleteArea(c, l, w, h, 0); + } + + /** + * Sets whether the cursor is visible or not. + * @param doshow + */ + public void showCursor(boolean doshow) { + showcursor = doshow; + } + + /** + * Check whether the cursor is currently visible. + * @return visibility + */ + public boolean isCursorVisible() { + return showcursor; + } + + /** + * Puts the cursor at the specified position. + * @param c column + * @param l line + */ + public void setCursorPosition(int c, int l) { + cursorX = c; + cursorY = l; + } + + /** + * Get the current column of the cursor position. + */ + public int getCursorColumn() { + return cursorX; + } + + /** + * Get the current line of the cursor position. + */ + public int getCursorRow() { + return cursorY; + } + + /** + * Set the current window base. This allows to view the scrollback buffer. + * @param line the line where the screen window starts + * @see #setBufferSize + * @see #getBufferSize + */ + public void setWindowBase(int line) { + if (line > screenBase) + line = screenBase; + else if (line < 0) line = 0; + windowBase = line; + update[0] = true; + redraw(); + } + + /** + * Get the current window base. + * @see #setWindowBase + */ + public int getWindowBase() { + return windowBase; + } + + /** + * Set the scroll margins simultaneously. If they're out of bounds, trim them. + * @param l1 line that is the top + * @param l2 line that is the bottom + */ + public void setMargins(int l1, int l2) { + if (l1 > l2) + return; + + if (l1 < 0) + l1 = 0; + if (l2 >= height) + l2 = height - 1; + + topMargin = l1; + bottomMargin = l2; + } + + /** + * Set the top scroll margin for the screen. If the current bottom margin + * is smaller it will become the top margin and the line will become the + * bottom margin. + * @param l line that is the margin + */ + public void setTopMargin(int l) { + if (l > bottomMargin) { + topMargin = bottomMargin; + bottomMargin = l; + } else + topMargin = l; + if (topMargin < 0) topMargin = 0; + if (bottomMargin >= height) bottomMargin = height - 1; + } + + /** + * Get the top scroll margin. + */ + public int getTopMargin() { + return topMargin; + } + + /** + * Set the bottom scroll margin for the screen. If the current top margin + * is bigger it will become the bottom margin and the line will become the + * top margin. + * @param l line that is the margin + */ + public void setBottomMargin(int l) { + if (l < topMargin) { + bottomMargin = topMargin; + topMargin = l; + } else + bottomMargin = l; + if (topMargin < 0) topMargin = 0; + if (bottomMargin >= height) bottomMargin = height - 1; + } + + /** + * Get the bottom scroll margin. + */ + public int getBottomMargin() { + return bottomMargin; + } + + /** + * Set scrollback buffer size. + * @param amount new size of the buffer + */ + public void setBufferSize(int amount) { + if (amount < height) amount = height; + if (amount < maxBufSize) { + char cbuf[][] = new char[amount][width]; + int abuf[][] = new int[amount][width]; + int copyStart = bufSize - amount < 0 ? 0 : bufSize - amount; + int copyCount = bufSize - amount < 0 ? bufSize : amount; + if (charArray != null) + System.arraycopy(charArray, copyStart, cbuf, 0, copyCount); + if (charAttributes != null) + System.arraycopy(charAttributes, copyStart, abuf, 0, copyCount); + charArray = cbuf; + charAttributes = abuf; + bufSize = copyCount; + screenBase = bufSize - height; + windowBase = screenBase; + } + maxBufSize = amount; + + update[0] = true; + redraw(); + } + + /** + * Retrieve current scrollback buffer size. + * @see #setBufferSize + */ + public int getBufferSize() { + return bufSize; + } + + /** + * Retrieve maximum buffer Size. + * @see #getBufferSize + */ + public int getMaxBufferSize() { + return maxBufSize; + } + + /** + * Change the size of the screen. This will include adjustment of the + * scrollback buffer. + * @param w of the screen + * @param h of the screen + */ + public void setScreenSize(int w, int h, boolean broadcast) { + char cbuf[][]; + int abuf[][]; + int maxSize = bufSize; + + if (w < 1 || h < 1) return; + + if (debug > 0) + System.err.println("VDU: screen size [" + w + "," + h + "]"); + + if (h > maxBufSize) + maxBufSize = h; + + if (h > bufSize) { + bufSize = h; + screenBase = 0; + windowBase = 0; + } + + if (windowBase + h >= bufSize) + windowBase = bufSize - h; + + if (screenBase + h >= bufSize) + screenBase = bufSize - h; + + + cbuf = new char[bufSize][w]; + abuf = new int[bufSize][w]; + + + for (int i = 0; i < bufSize; i++) { + Arrays.fill(cbuf[i], ' '); + } + + if (bufSize < maxSize) + maxSize = bufSize; + + int rowLength; + if (charArray != null && charAttributes != null) { + for (int i = 0; i < maxSize && charArray[i] != null; i++) { + rowLength = charArray[i].length; + System.arraycopy(charArray[i], 0, cbuf[i], 0, + w < rowLength ? w : rowLength); + System.arraycopy(charAttributes[i], 0, abuf[i], 0, + w < rowLength ? w : rowLength); + } + } + + int C = getCursorColumn(); + if (C < 0) + C = 0; + else if (C >= width) + C = width - 1; + + int R = getCursorRow(); + if (R < 0) + R = 0; + else if (R >= height) + R = height - 1; + + setCursorPosition(C, R); + + charArray = cbuf; + charAttributes = abuf; + width = w; + height = h; + topMargin = 0; + bottomMargin = h - 1; + update = new boolean[h + 1]; + update[0] = true; + /* FIXME: ??? + if(resizeStrategy == RESIZE_FONT) + setBounds(getBounds()); + */ + } + + /** + * Get amount of rows on the screen. + */ + public int getRows() { + return height; + } + + /** + * Get amount of columns on the screen. + */ + public int getColumns() { + return width; + } + + /** + * Mark lines to be updated with redraw(). + * @param l starting line + * @param n amount of lines to be updated + * @see #redraw + */ + public void markLine(int l, int n) { + for (int i = 0; (i < n) && (l + i < height); i++) + update[l + i + 1] = true; + } + +// private static int checkBounds(int value, int lower, int upper) { +// if (value < lower) +// return lower; +// else if (value > upper) +// return upper; +// else +// return value; +// } + + /** a generic display that should redraw on demand */ + protected VDUDisplay display; + + public void setDisplay(VDUDisplay display) { + this.display = display; + } + + /** + * Trigger a redraw on the display. + */ + protected void redraw() { + if (display != null) + display.redraw(); + } +} diff --git a/app/src/main/java/de/mud/terminal/VDUDisplay.java b/app/src/main/java/de/mud/terminal/VDUDisplay.java new file mode 100644 index 0000000..bb4a7b8 --- /dev/null +++ b/app/src/main/java/de/mud/terminal/VDUDisplay.java @@ -0,0 +1,40 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ + +package de.mud.terminal; + +/** + * Generic display + */ +public interface VDUDisplay { + public void redraw(); + public void updateScrollBar(); + + public void setVDUBuffer(VDUBuffer buffer); + public VDUBuffer getVDUBuffer(); + + public void setColor(int index, int red, int green, int blue); + public void resetColors(); +} diff --git a/app/src/main/java/de/mud/terminal/VDUInput.java b/app/src/main/java/de/mud/terminal/VDUInput.java new file mode 100644 index 0000000..43c88de --- /dev/null +++ b/app/src/main/java/de/mud/terminal/VDUInput.java @@ -0,0 +1,90 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ +package de.mud.terminal; + +import java.util.Properties; + +/** + * An interface for a terminal that accepts input from keyboard and mouse. + * + * @author Matthias L. Jugel, Marcus Meißner + * @version $Id: VDUInput.java 499 2005-09-29 08:24:54Z leo $ + */ +public interface VDUInput { + + public final static int KEY_CONTROL = 0x01; + public final static int KEY_SHIFT = 0x02; + public final static int KEY_ALT = 0x04; + public final static int KEY_ACTION = 0x08; + + + + /** + * Direct access to writing data ... + * @param b + */ + void write(byte b[]); + + /** + * Terminal is mouse-aware and requires (x,y) coordinates of + * on the terminal (character coordinates) and the button clicked. + * @param x + * @param y + * @param modifiers + */ + void mousePressed(int x, int y, int modifiers); + + /** + * Terminal is mouse-aware and requires the coordinates and button + * of the release. + * @param x + * @param y + * @param modifiers + */ + void mouseReleased(int x, int y, int modifiers); + + /** + * Override the standard key codes used by the terminal emulation. + * @param codes a properties object containing key code definitions + */ + void setKeyCodes(Properties codes); + + /** + * main keytyping event handler... + * @param keyCode the key code + * @param keyChar the character represented by the key + * @param modifiers shift/alt/control modifiers + */ + void keyPressed(int keyCode, char keyChar, int modifiers); + + /** + * Handle key Typed events for the terminal, this will get + * all normal key types, but no shift/alt/control/numlock. + * @param keyCode the key code + * @param keyChar the character represented by the key + * @param modifiers shift/alt/control modifiers + */ + void keyTyped(int keyCode, char keyChar, int modifiers); +} diff --git a/app/src/main/java/de/mud/terminal/vt320.java b/app/src/main/java/de/mud/terminal/vt320.java new file mode 100644 index 0000000..73369af --- /dev/null +++ b/app/src/main/java/de/mud/terminal/vt320.java @@ -0,0 +1,3032 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meiner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ + +package de.mud.terminal; + +import android.text.AndroidCharacter; + +import java.util.Properties; + +/** + * Implementation of a VT terminal emulation plus ANSI compatible. + * <P> + * <B>Maintainer:</B> Marcus Meißner + * + * @version $Id: vt320.java 507 2005-10-25 10:14:52Z marcus $ + * @author Matthias L. Jugel, Marcus Meißner + */ +public abstract class vt320 extends VDUBuffer implements VDUInput { + + /** The current version id tag.<P> + * $Id: vt320.java 507 2005-10-25 10:14:52Z marcus $ + * + */ + public final static String ID = "$Id: vt320.java 507 2005-10-25 10:14:52Z marcus $"; + + /** the debug level */ + private final static int debug = 0; + private StringBuilder debugStr; + public abstract void debug(String notice); + + /** + * Write an answer back to the remote host. This is needed to be able to + * send terminal answers requests like status and type information. + * @param b the array of bytes to be sent + */ + public abstract void write(byte[] b); + + /** + * Write an answer back to the remote host. This is needed to be able to + * send terminal answers requests like status and type information. + * @param b the array of bytes to be sent + */ + public abstract void write(int b); + + /** + * Play the beep sound ... + */ + public void beep() { /* do nothing by default */ + } + + /** + * Convenience function for putString(char[], int, int) + */ + public void putString(String s) { + int len = s.length(); + char[] tmp = new char[len]; + s.getChars(0, len, tmp, 0); + putString(tmp, null, 0, len); + } + + /** + * Put string at current cursor position. Moves cursor + * according to the String. Does NOT wrap. + * @param s character array + * @param start place to start in array + * @param len number of characters to process + */ + public void putString(char[] s, byte[] fullwidths, int start, int len) { + if (len > 0) { + //markLine(R, 1); + int lastChar = -1; + char c; + boolean isWide = false; + + for (int i = 0; i < len; i++) { + c = s[start + i]; + // Shortcut for my favorite ASCII + if (c <= 0x7F) { + if (lastChar != -1) + putChar((char) lastChar, isWide, false); + lastChar = c; + isWide = false; + } else if (!Character.isLowSurrogate(c) && !Character.isHighSurrogate(c)) { + if (Character.getType(c) == Character.NON_SPACING_MARK) { + if (lastChar != -1) { + char nc = Precomposer.precompose((char) lastChar, c); + putChar(nc, isWide, false); + lastChar = -1; + } + } else { + if (lastChar != -1) + putChar((char) lastChar, isWide, false); + lastChar = c; + if (fullwidths != null) { + final byte width = fullwidths[i]; + isWide = (width == AndroidCharacter.EAST_ASIAN_WIDTH_WIDE) + || (width == AndroidCharacter.EAST_ASIAN_WIDTH_FULL_WIDTH); + } + } + } + } + + if (lastChar != -1) + putChar((char) lastChar, isWide, false); + + setCursorPosition(C, R); + redraw(); + } + } + + protected void sendTelnetCommand(byte cmd) { + + } + + /** + * Sent the changed window size from the terminal to all listeners. + */ + protected void setWindowSize(int c, int r) { + /* To be overridden by Terminal.java */ + } + + @Override +public void setScreenSize(int c, int r, boolean broadcast) { + int oldrows = height; + + if (debug>2) { + if (debugStr == null) + debugStr = new StringBuilder(); + + debugStr.append("setscreensize (") + .append(c) + .append(',') + .append(r) + .append(',') + .append(broadcast) + .append(')'); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + super.setScreenSize(c,r,false); + + boolean cursorChanged = false; + + // Don't let the cursor go off the screen. + if (C >= c) { + C = c - 1; + cursorChanged = true; + } + + if (R >= r) { + R = r - 1; + cursorChanged = true; + } + + if (cursorChanged) { + setCursorPosition(C, R); + redraw(); + } + + if (broadcast) { + setWindowSize(c, r); /* broadcast up */ + } + } + + + /** + * Create a new vt320 terminal and intialize it with useful settings. + */ + public vt320(int width, int height) { + super(width, height); + + debugStr = new StringBuilder(); + + setVMS(false); + setIBMCharset(false); + setTerminalID("vt320"); + setBufferSize(100); + //setBorder(2, false); + + gx = new char[4]; + reset(); + + /* top row of numpad */ + PF1 = "\u001bOP"; + PF2 = "\u001bOQ"; + PF3 = "\u001bOR"; + PF4 = "\u001bOS"; + + /* the 3x2 keyblock on PC keyboards */ + Insert = new String[4]; + Remove = new String[4]; + KeyHome = new String[4]; + KeyEnd = new String[4]; + NextScn = new String[4]; + PrevScn = new String[4]; + Escape = new String[4]; + BackSpace = new String[4]; + TabKey = new String[4]; + Insert[0] = Insert[1] = Insert[2] = Insert[3] = "\u001b[2~"; + Remove[0] = Remove[1] = Remove[2] = Remove[3] = "\u001b[3~"; + PrevScn[0] = PrevScn[1] = PrevScn[2] = PrevScn[3] = "\u001b[5~"; + NextScn[0] = NextScn[1] = NextScn[2] = NextScn[3] = "\u001b[6~"; + KeyHome[0] = KeyHome[1] = KeyHome[2] = KeyHome[3] = "\u001b[H"; + KeyEnd[0] = KeyEnd[1] = KeyEnd[2] = KeyEnd[3] = "\u001b[F"; + Escape[0] = Escape[1] = Escape[2] = Escape[3] = "\u001b"; + if (vms) { + BackSpace[1] = "" + (char) 10; // VMS shift deletes word back + BackSpace[2] = "\u0018"; // VMS control deletes line back + BackSpace[0] = BackSpace[3] = "\u007f"; // VMS other is delete + } else { + //BackSpace[0] = BackSpace[1] = BackSpace[2] = BackSpace[3] = "\b"; + // ConnectBot modifications. + BackSpace[0] = "\b"; + BackSpace[1] = "\u007f"; + BackSpace[2] = "\u001b[3~"; + BackSpace[3] = "\u001b[2~"; + } + + /* some more VT100 keys */ + Find = "\u001b[1~"; + Select = "\u001b[4~"; + Help = "\u001b[28~"; + Do = "\u001b[29~"; + + FunctionKey = new String[21]; + FunctionKey[0] = ""; + FunctionKey[1] = PF1; + FunctionKey[2] = PF2; + FunctionKey[3] = PF3; + FunctionKey[4] = PF4; + /* following are defined differently for vt220 / vt132 ... */ + FunctionKey[5] = "\u001b[15~"; + FunctionKey[6] = "\u001b[17~"; + FunctionKey[7] = "\u001b[18~"; + FunctionKey[8] = "\u001b[19~"; + FunctionKey[9] = "\u001b[20~"; + FunctionKey[10] = "\u001b[21~"; + FunctionKey[11] = "\u001b[23~"; + FunctionKey[12] = "\u001b[24~"; + FunctionKey[13] = "\u001b[25~"; + FunctionKey[14] = "\u001b[26~"; + FunctionKey[15] = Help; + FunctionKey[16] = Do; + FunctionKey[17] = "\u001b[31~"; + FunctionKey[18] = "\u001b[32~"; + FunctionKey[19] = "\u001b[33~"; + FunctionKey[20] = "\u001b[34~"; + + FunctionKeyShift = new String[21]; + FunctionKeyAlt = new String[21]; + FunctionKeyCtrl = new String[21]; + + for (int i = 0; i < 20; i++) { + FunctionKeyShift[i] = ""; + FunctionKeyAlt[i] = ""; + FunctionKeyCtrl[i] = ""; + } + FunctionKeyShift[15] = Find; + FunctionKeyShift[16] = Select; + + + TabKey[0] = "\u0009"; + TabKey[1] = "\u001bOP\u0009"; + TabKey[2] = TabKey[3] = ""; + + KeyUp = new String[4]; + KeyUp[0] = "\u001b[A"; + KeyDown = new String[4]; + KeyDown[0] = "\u001b[B"; + KeyRight = new String[4]; + KeyRight[0] = "\u001b[C"; + KeyLeft = new String[4]; + KeyLeft[0] = "\u001b[D"; + Numpad = new String[10]; + Numpad[0] = "\u001bOp"; + Numpad[1] = "\u001bOq"; + Numpad[2] = "\u001bOr"; + Numpad[3] = "\u001bOs"; + Numpad[4] = "\u001bOt"; + Numpad[5] = "\u001bOu"; + Numpad[6] = "\u001bOv"; + Numpad[7] = "\u001bOw"; + Numpad[8] = "\u001bOx"; + Numpad[9] = "\u001bOy"; + KPMinus = PF4; + KPComma = "\u001bOl"; + KPPeriod = "\u001bOn"; + KPEnter = "\u001bOM"; + + NUMPlus = new String[4]; + NUMPlus[0] = "+"; + NUMDot = new String[4]; + NUMDot[0] = "."; + } + + public void setBackspace(int type) { + switch (type) { + case DELETE_IS_DEL: + BackSpace[0] = "\u007f"; + BackSpace[1] = "\b"; + break; + case DELETE_IS_BACKSPACE: + BackSpace[0] = "\b"; + BackSpace[1] = "\u007f"; + break; + } + } + + /** + * Create a default vt320 terminal with 80 columns and 24 lines. + */ + public vt320() { + this(80, 24); + } + + /** + * Terminal is mouse-aware and requires (x,y) coordinates of + * on the terminal (character coordinates) and the button clicked. + * @param x + * @param y + * @param modifiers + */ + public void mousePressed(int x, int y, int modifiers) { + if (mouserpt == 0) + return; + + int mods = modifiers; + mousebut = 3; + if ((mods & 16) == 16) mousebut = 0; + if ((mods & 8) == 8) mousebut = 1; + if ((mods & 4) == 4) mousebut = 2; + + int mousecode; + if (mouserpt == 9) /* X10 Mouse */ + mousecode = 0x20 | mousebut; + else /* normal xterm mouse reporting */ + mousecode = mousebut | 0x20 | ((mods & 7) << 2); + + byte b[] = new byte[6]; + + b[0] = 27; + b[1] = (byte) '['; + b[2] = (byte) 'M'; + b[3] = (byte) mousecode; + b[4] = (byte) (0x20 + x + 1); + b[5] = (byte) (0x20 + y + 1); + + write(b); // FIXME: writeSpecial here + } + + /** + * Terminal is mouse-aware and requires the coordinates and button + * of the release. + * @param x + * @param y + * @param modifiers + */ + public void mouseReleased(int x, int y, int modifiers) { + if (mouserpt == 0) + return; + + /* problem is tht modifiers still have the released button set in them. + int mods = modifiers; + mousebut = 3; + if ((mods & 16)==16) mousebut=0; + if ((mods & 8)==8 ) mousebut=1; + if ((mods & 4)==4 ) mousebut=2; + */ + + int mousecode; + if (mouserpt == 9) + mousecode = 0x20 + mousebut; /* same as press? appears so. */ + else + mousecode = '#'; + + byte b[] = new byte[6]; + b[0] = 27; + b[1] = (byte) '['; + b[2] = (byte) 'M'; + b[3] = (byte) mousecode; + b[4] = (byte) (0x20 + x + 1); + b[5] = (byte) (0x20 + y + 1); + write(b); // FIXME: writeSpecial here + mousebut = 0; + } + + + /** we should do localecho (passed from other modules). false is default */ + private boolean localecho = false; + + /** + * Enable or disable the local echo property of the terminal. + * @param echo true if the terminal should echo locally + */ + public void setLocalEcho(boolean echo) { + localecho = echo; + } + + /** + * Enable the VMS mode of the terminal to handle some things differently + * for VMS hosts. + * @param vms true for vms mode, false for normal mode + */ + public void setVMS(boolean vms) { + this.vms = vms; + } + + /** + * Enable the usage of the IBM character set used by some BBS's. Special + * graphical character are available in this mode. + * @param ibm true to use the ibm character set + */ + public void setIBMCharset(boolean ibm) { + useibmcharset = ibm; + } + + /** + * Override the standard key codes used by the terminal emulation. + * @param codes a properties object containing key code definitions + */ + public void setKeyCodes(Properties codes) { + String res, prefixes[] = {"", "S", "C", "A"}; + int i; + + for (i = 0; i < 10; i++) { + res = codes.getProperty("NUMPAD" + i); + if (res != null) Numpad[i] = unEscape(res); + } + for (i = 1; i < 20; i++) { + res = codes.getProperty("F" + i); + if (res != null) FunctionKey[i] = unEscape(res); + res = codes.getProperty("SF" + i); + if (res != null) FunctionKeyShift[i] = unEscape(res); + res = codes.getProperty("CF" + i); + if (res != null) FunctionKeyCtrl[i] = unEscape(res); + res = codes.getProperty("AF" + i); + if (res != null) FunctionKeyAlt[i] = unEscape(res); + } + for (i = 0; i < 4; i++) { + res = codes.getProperty(prefixes[i] + "PGUP"); + if (res != null) PrevScn[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "PGDOWN"); + if (res != null) NextScn[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "END"); + if (res != null) KeyEnd[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "HOME"); + if (res != null) KeyHome[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "INSERT"); + if (res != null) Insert[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "REMOVE"); + if (res != null) Remove[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "UP"); + if (res != null) KeyUp[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "DOWN"); + if (res != null) KeyDown[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "LEFT"); + if (res != null) KeyLeft[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "RIGHT"); + if (res != null) KeyRight[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "ESCAPE"); + if (res != null) Escape[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "BACKSPACE"); + if (res != null) BackSpace[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "TAB"); + if (res != null) TabKey[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "NUMPLUS"); + if (res != null) NUMPlus[i] = unEscape(res); + res = codes.getProperty(prefixes[i] + "NUMDECIMAL"); + if (res != null) NUMDot[i] = unEscape(res); + } + } + + /** + * Set the terminal id used to identify this terminal. + * @param terminalID the id string + */ + public void setTerminalID(String terminalID) { + this.terminalID = terminalID; + + if (terminalID.equals("scoansi")) { + FunctionKey[1] = "\u001b[M"; FunctionKey[2] = "\u001b[N"; + FunctionKey[3] = "\u001b[O"; FunctionKey[4] = "\u001b[P"; + FunctionKey[5] = "\u001b[Q"; FunctionKey[6] = "\u001b[R"; + FunctionKey[7] = "\u001b[S"; FunctionKey[8] = "\u001b[T"; + FunctionKey[9] = "\u001b[U"; FunctionKey[10] = "\u001b[V"; + FunctionKey[11] = "\u001b[W"; FunctionKey[12] = "\u001b[X"; + FunctionKey[13] = "\u001b[Y"; FunctionKey[14] = "?"; + FunctionKey[15] = "\u001b[a"; FunctionKey[16] = "\u001b[b"; + FunctionKey[17] = "\u001b[c"; FunctionKey[18] = "\u001b[d"; + FunctionKey[19] = "\u001b[e"; FunctionKey[20] = "\u001b[f"; + PrevScn[0] = PrevScn[1] = PrevScn[2] = PrevScn[3] = "\u001b[I"; + NextScn[0] = NextScn[1] = NextScn[2] = NextScn[3] = "\u001b[G"; + // more theoretically. + } + } + + public void setAnswerBack(String ab) { + this.answerBack = unEscape(ab); + } + + /** + * Get the terminal id used to identify this terminal. + */ + public String getTerminalID() { + return terminalID; + } + + /** + * A small conveniance method thar converts the string to a byte array + * for sending. + * @param s the string to be sent + */ + private boolean write(String s, boolean doecho) { + if (debug > 2) { + debugStr.append("write(|") + .append(s) + .append("|,") + .append(doecho); + debug(debugStr.toString()); + debugStr.setLength(0); + } + if (s == null) // aka the empty string. + return true; + /* NOTE: getBytes() honours some locale, it *CONVERTS* the string. + * However, we output only 7bit stuff towards the target, and *some* + * 8 bit control codes. We must not mess up the latter, so we do hand + * by hand copy. + */ + + byte arr[] = new byte[s.length()]; + for (int i = 0; i < s.length(); i++) { + arr[i] = (byte) s.charAt(i); + } + write(arr); + + if (doecho) + putString(s); + return true; + } + + private boolean write(int s, boolean doecho) { + if (debug > 2) { + debugStr.append("write(|") + .append(s) + .append("|,") + .append(doecho); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + write(s); + + // TODO check if character is wide + if (doecho) + putChar((char)s, false, false); + return true; + } + + private boolean write(String s) { + return write(s, localecho); + } + + // =================================================================== + // the actual terminal emulation code comes here: + // =================================================================== + + private String terminalID = "vt320"; + private String answerBack = "Use Terminal.answerback to set ...\n"; + + // X - COLUMNS, Y - ROWS + int R,C; + int attributes = 0; + + int Sc,Sr,Sa,Stm,Sbm; + char Sgr,Sgl; + char Sgx[]; + + int insertmode = 0; + int statusmode = 0; + boolean vt52mode = false; + boolean keypadmode = false; /* false - numeric, true - application */ + boolean output8bit = false; + int normalcursor = 0; + boolean moveoutsidemargins = true; + boolean wraparound = true; + boolean sendcrlf = true; + boolean capslock = false; + boolean numlock = false; + int mouserpt = 0; + byte mousebut = 0; + + boolean useibmcharset = false; + + int lastwaslf = 0; + boolean usedcharsets = false; + + private final static char ESC = 27; + private final static char IND = 132; + private final static char NEL = 133; + private final static char RI = 141; + private final static char SS2 = 142; + private final static char SS3 = 143; + private final static char DCS = 144; + private final static char HTS = 136; + private final static char CSI = 155; + private final static char OSC = 157; + private final static int TSTATE_DATA = 0; + private final static int TSTATE_ESC = 1; /* ESC */ + private final static int TSTATE_CSI = 2; /* ESC [ */ + private final static int TSTATE_DCS = 3; /* ESC P */ + private final static int TSTATE_DCEQ = 4; /* ESC [? */ + private final static int TSTATE_ESCSQUARE = 5; /* ESC # */ + private final static int TSTATE_OSC = 6; /* ESC ] */ + private final static int TSTATE_SETG0 = 7; /* ESC (? */ + private final static int TSTATE_SETG1 = 8; /* ESC )? */ + private final static int TSTATE_SETG2 = 9; /* ESC *? */ + private final static int TSTATE_SETG3 = 10; /* ESC +? */ + private final static int TSTATE_CSI_DOLLAR = 11; /* ESC [ Pn $ */ + private final static int TSTATE_CSI_EX = 12; /* ESC [ ! */ + private final static int TSTATE_ESCSPACE = 13; /* ESC <space> */ + private final static int TSTATE_VT52X = 14; + private final static int TSTATE_VT52Y = 15; + private final static int TSTATE_CSI_TICKS = 16; + private final static int TSTATE_CSI_EQUAL = 17; /* ESC [ = */ + private final static int TSTATE_TITLE = 18; /* xterm title */ + + /* Keys we support */ + public final static int KEY_PAUSE = 1; + public final static int KEY_F1 = 2; + public final static int KEY_F2 = 3; + public final static int KEY_F3 = 4; + public final static int KEY_F4 = 5; + public final static int KEY_F5 = 6; + public final static int KEY_F6 = 7; + public final static int KEY_F7 = 8; + public final static int KEY_F8 = 9; + public final static int KEY_F9 = 10; + public final static int KEY_F10 = 11; + public final static int KEY_F11 = 12; + public final static int KEY_F12 = 13; + public final static int KEY_UP = 14; + public final static int KEY_DOWN =15 ; + public final static int KEY_LEFT = 16; + public final static int KEY_RIGHT = 17; + public final static int KEY_PAGE_DOWN = 18; + public final static int KEY_PAGE_UP = 19; + public final static int KEY_INSERT = 20; + public final static int KEY_DELETE = 21; + public final static int KEY_BACK_SPACE = 22; + public final static int KEY_HOME = 23; + public final static int KEY_END = 24; + public final static int KEY_NUM_LOCK = 25; + public final static int KEY_CAPS_LOCK = 26; + public final static int KEY_SHIFT = 27; + public final static int KEY_CONTROL = 28; + public final static int KEY_ALT = 29; + public final static int KEY_ENTER = 30; + public final static int KEY_NUMPAD0 = 31; + public final static int KEY_NUMPAD1 = 32; + public final static int KEY_NUMPAD2 = 33; + public final static int KEY_NUMPAD3 = 34; + public final static int KEY_NUMPAD4 = 35; + public final static int KEY_NUMPAD5 = 36; + public final static int KEY_NUMPAD6 = 37; + public final static int KEY_NUMPAD7 = 38; + public final static int KEY_NUMPAD8 = 39; + public final static int KEY_NUMPAD9 = 40; + public final static int KEY_DECIMAL = 41; + public final static int KEY_ADD = 42; + public final static int KEY_ESCAPE = 43; + + public final static int DELETE_IS_DEL = 0; + public final static int DELETE_IS_BACKSPACE = 1; + + /* The graphics charsets + * B - default ASCII + * A - ISO Latin 1 + * 0 - DEC SPECIAL + * < - User defined + * .... + */ + char gx[]; + char gl; // GL (left charset) + char gr; // GR (right charset) + int onegl; // single shift override for GL. + + // Map from scoansi linedrawing to DEC _and_ unicode (for the stuff which + // is not in linedrawing). Got from experimenting with scoadmin. + private final static String scoansi_acs = "Tm7k3x4u?kZl@mYjEnB\u2566DqCtAvM\u2550:\u2551N\u2557I\u2554;\u2557H\u255a0a<\u255d"; + // array to store DEC Special -> Unicode mapping + // Unicode DEC Unicode name (DEC name) + private static char DECSPECIAL[] = { + '\u0040', //5f blank + '\u2666', //60 black diamond + '\u2592', //61 grey square + '\u2409', //62 Horizontal tab (ht) pict. for control + '\u240c', //63 Form Feed (ff) pict. for control + '\u240d', //64 Carriage Return (cr) pict. for control + '\u240a', //65 Line Feed (lf) pict. for control + '\u00ba', //66 Masculine ordinal indicator + '\u00b1', //67 Plus or minus sign + '\u2424', //68 New Line (nl) pict. for control + '\u240b', //69 Vertical Tab (vt) pict. for control + '\u2518', //6a Forms light up and left + '\u2510', //6b Forms light down and left + '\u250c', //6c Forms light down and right + '\u2514', //6d Forms light up and right + '\u253c', //6e Forms light vertical and horizontal + '\u2594', //6f Upper 1/8 block (Scan 1) + '\u2580', //70 Upper 1/2 block (Scan 3) + '\u2500', //71 Forms light horizontal or ?em dash? (Scan 5) + '\u25ac', //72 \u25ac black rect. or \u2582 lower 1/4 (Scan 7) + '\u005f', //73 \u005f underscore or \u2581 lower 1/8 (Scan 9) + '\u251c', //74 Forms light vertical and right + '\u2524', //75 Forms light vertical and left + '\u2534', //76 Forms light up and horizontal + '\u252c', //77 Forms light down and horizontal + '\u2502', //78 vertical bar + '\u2264', //79 less than or equal + '\u2265', //7a greater than or equal + '\u00b6', //7b paragraph + '\u2260', //7c not equal + '\u00a3', //7d Pound Sign (british) + '\u00b7' //7e Middle Dot + }; + + /** Strings to send on function key pressing */ + private String Numpad[]; + private String FunctionKey[]; + private String FunctionKeyShift[]; + private String FunctionKeyCtrl[]; + private String FunctionKeyAlt[]; + private String TabKey[]; + private String KeyUp[],KeyDown[],KeyLeft[],KeyRight[]; + private String KPMinus, KPComma, KPPeriod, KPEnter; + private String PF1, PF2, PF3, PF4; + private String Help, Do, Find, Select; + + private String KeyHome[], KeyEnd[], Insert[], Remove[], PrevScn[], NextScn[]; + private String Escape[], BackSpace[], NUMDot[], NUMPlus[]; + + private String osc,dcs; /* to memorize OSC & DCS control sequence */ + + /** vt320 state variable (internal) */ + private int term_state = TSTATE_DATA; + /** in vms mode, set by Terminal.VMS property */ + private boolean vms = false; + /** Tabulators */ + private byte[] Tabs; + /** The list of integers as used by CSI */ + private int[] DCEvars = new int[30]; + private int DCEvar; + + /** + * Replace escape code characters (backslash + identifier) with their + * respective codes. + * @param tmp the string to be parsed + * @return a unescaped string + */ + static String unEscape(String tmp) { + int idx = 0, oldidx = 0; + String cmd; + // f.println("unescape("+tmp+")"); + cmd = ""; + while ((idx = tmp.indexOf('\\', oldidx)) >= 0 && + ++idx <= tmp.length()) { + cmd += tmp.substring(oldidx, idx - 1); + if (idx == tmp.length()) return cmd; + switch (tmp.charAt(idx)) { + case 'b': + cmd += "\b"; + break; + case 'e': + cmd += "\u001b"; + break; + case 'n': + cmd += "\n"; + break; + case 'r': + cmd += "\r"; + break; + case 't': + cmd += "\t"; + break; + case 'v': + cmd += "\u000b"; + break; + case 'a': + cmd += "\u0012"; + break; + default : + if ((tmp.charAt(idx) >= '0') && (tmp.charAt(idx) <= '9')) { + int i; + for (i = idx; i < tmp.length(); i++) + if ((tmp.charAt(i) < '0') || (tmp.charAt(i) > '9')) + break; + cmd += (char) Integer.parseInt(tmp.substring(idx, i)); + idx = i - 1; + } else + cmd += tmp.substring(idx, ++idx); + break; + } + oldidx = ++idx; + } + if (oldidx <= tmp.length()) cmd += tmp.substring(oldidx); + return cmd; + } + + /** + * A small conveniance method thar converts a 7bit string to the 8bit + * version depending on VT52/Output8Bit mode. + * + * @param s the string to be sent + */ + private boolean writeSpecial(String s) { + if (s == null) + return true; + if (((s.length() >= 3) && (s.charAt(0) == 27) && (s.charAt(1) == 'O'))) { + if (vt52mode) { + if ((s.charAt(2) >= 'P') && (s.charAt(2) <= 'S')) { + s = "\u001b" + s.substring(2); /* ESC x */ + } else { + s = "\u001b?" + s.substring(2); /* ESC ? x */ + } + } else { + if (output8bit) { + s = "\u008f" + s.substring(2); /* SS3 x */ + } /* else keep string as it is */ + } + } + if (((s.length() >= 3) && (s.charAt(0) == 27) && (s.charAt(1) == '['))) { + if (output8bit) { + s = "\u009b" + s.substring(2); /* CSI ... */ + } /* else keep */ + } + return write(s, false); + } + + /** + * main keytyping event handler... + */ + public void keyPressed(int keyCode, char keyChar, int modifiers) { + boolean control = (modifiers & VDUInput.KEY_CONTROL) != 0; + boolean shift = (modifiers & VDUInput.KEY_SHIFT) != 0; + boolean alt = (modifiers & VDUInput.KEY_ALT) != 0; + + if (debug > 1) { + debugStr.append("keyPressed(") + .append(keyCode) + .append(", ") + .append((int)keyChar) + .append(", ") + .append(modifiers) + .append(')'); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + int xind; + String fmap[]; + xind = 0; + fmap = FunctionKey; + if (shift) { + fmap = FunctionKeyShift; + xind = 1; + } + if (control) { + fmap = FunctionKeyCtrl; + xind = 2; + } + if (alt) { + fmap = FunctionKeyAlt; + xind = 3; + } + + switch (keyCode) { + case KEY_PAUSE: + if (shift || control) + sendTelnetCommand((byte) 243); // BREAK + break; + case KEY_F1: + writeSpecial(fmap[1]); + break; + case KEY_F2: + writeSpecial(fmap[2]); + break; + case KEY_F3: + writeSpecial(fmap[3]); + break; + case KEY_F4: + writeSpecial(fmap[4]); + break; + case KEY_F5: + writeSpecial(fmap[5]); + break; + case KEY_F6: + writeSpecial(fmap[6]); + break; + case KEY_F7: + writeSpecial(fmap[7]); + break; + case KEY_F8: + writeSpecial(fmap[8]); + break; + case KEY_F9: + writeSpecial(fmap[9]); + break; + case KEY_F10: + writeSpecial(fmap[10]); + break; + case KEY_F11: + writeSpecial(fmap[11]); + break; + case KEY_F12: + writeSpecial(fmap[12]); + break; + case KEY_UP: + writeSpecial(KeyUp[xind]); + break; + case KEY_DOWN: + writeSpecial(KeyDown[xind]); + break; + case KEY_LEFT: + writeSpecial(KeyLeft[xind]); + break; + case KEY_RIGHT: + writeSpecial(KeyRight[xind]); + break; + case KEY_PAGE_DOWN: + writeSpecial(NextScn[xind]); + break; + case KEY_PAGE_UP: + writeSpecial(PrevScn[xind]); + break; + case KEY_INSERT: + writeSpecial(Insert[xind]); + break; + case KEY_DELETE: + writeSpecial(Remove[xind]); + break; + case KEY_BACK_SPACE: + writeSpecial(BackSpace[xind]); + if (localecho) { + if (BackSpace[xind] == "\b") { + putString("\b \b"); // make the last char 'deleted' + } else { + putString(BackSpace[xind]); // echo it + } + } + break; + case KEY_HOME: + writeSpecial(KeyHome[xind]); + break; + case KEY_END: + writeSpecial(KeyEnd[xind]); + break; + case KEY_NUM_LOCK: + if (vms && control) { + writeSpecial(PF1); + } + if (!control) + numlock = !numlock; + break; + case KEY_CAPS_LOCK: + capslock = !capslock; + return; + case KEY_SHIFT: + case KEY_CONTROL: + case KEY_ALT: + return; + default: + break; + } + } +/* + public void keyReleased(KeyEvent evt) { + if (debug > 1) debug("keyReleased("+evt+")"); + // ignore + } +*/ + /** + * Handle key Typed events for the terminal, this will get + * all normal key types, but no shift/alt/control/numlock. + */ + public void keyTyped(int keyCode, char keyChar, int modifiers) { + boolean control = (modifiers & VDUInput.KEY_CONTROL) != 0; + boolean shift = (modifiers & VDUInput.KEY_SHIFT) != 0; + boolean alt = (modifiers & VDUInput.KEY_ALT) != 0; + + if (debug > 1) debug("keyTyped("+keyCode+", "+(int)keyChar+", "+modifiers+")"); + + if (keyChar == '\t') { + if (shift) { + write(TabKey[1], false); + } else { + if (control) { + write(TabKey[2], false); + } else { + if (alt) { + write(TabKey[3], false); + } else { + write(TabKey[0], false); + } + } + } + return; + } + if (alt) { + write(((char) (keyChar | 0x80))); + return; + } + + if (((keyCode == KEY_ENTER) || (keyChar == 10)) + && !control) { + write('\r'); + if (localecho) putString("\r\n"); // bad hack + return; + } + + if ((keyCode == 10) && !control) { + debug("Sending \\r"); + write('\r'); + return; + } + + // FIXME: on german PC keyboards you have to use Alt-Ctrl-q to get an @, + // so we can't just use it here... will probably break some other VMS + // codes. -Marcus + // if(((!vms && keyChar == '2') || keyChar == '@' || keyChar == ' ') + // && control) + if (((!vms && keyChar == '2') || keyChar == ' ') && control) + write(0); + + if (vms) { + if (keyChar == 127 && !control) { + if (shift) + writeSpecial(Insert[0]); // VMS shift delete = insert + else + writeSpecial(Remove[0]); // VMS delete = remove + return; + } else if (control) + switch (keyChar) { + case '0': + writeSpecial(Numpad[0]); + return; + case '1': + writeSpecial(Numpad[1]); + return; + case '2': + writeSpecial(Numpad[2]); + return; + case '3': + writeSpecial(Numpad[3]); + return; + case '4': + writeSpecial(Numpad[4]); + return; + case '5': + writeSpecial(Numpad[5]); + return; + case '6': + writeSpecial(Numpad[6]); + return; + case '7': + writeSpecial(Numpad[7]); + return; + case '8': + writeSpecial(Numpad[8]); + return; + case '9': + writeSpecial(Numpad[9]); + return; + case '.': + writeSpecial(KPPeriod); + return; + case '-': + case 31: + writeSpecial(KPMinus); + return; + case '+': + writeSpecial(KPComma); + return; + case 10: + writeSpecial(KPEnter); + return; + case '/': + writeSpecial(PF2); + return; + case '*': + writeSpecial(PF3); + return; + /* NUMLOCK handled in keyPressed */ + default: + break; + } + /* Now what does this do and how did it get here. -Marcus + if (shift && keyChar < 32) { + write(PF1+(char)(keyChar + 64)); + return; + } + */ + } + + // FIXME: not used? + //String fmap[]; + int xind; + xind = 0; + //fmap = FunctionKey; + if (shift) { + //fmap = FunctionKeyShift; + xind = 1; + } + if (control) { + //fmap = FunctionKeyCtrl; + xind = 2; + } + if (alt) { + //fmap = FunctionKeyAlt; + xind = 3; + } + + if (keyCode == KEY_ESCAPE) { + writeSpecial(Escape[xind]); + return; + } + + if ((modifiers & VDUInput.KEY_ACTION) != 0) + switch (keyCode) { + case KEY_NUMPAD0: + writeSpecial(Numpad[0]); + return; + case KEY_NUMPAD1: + writeSpecial(Numpad[1]); + return; + case KEY_NUMPAD2: + writeSpecial(Numpad[2]); + return; + case KEY_NUMPAD3: + writeSpecial(Numpad[3]); + return; + case KEY_NUMPAD4: + writeSpecial(Numpad[4]); + return; + case KEY_NUMPAD5: + writeSpecial(Numpad[5]); + return; + case KEY_NUMPAD6: + writeSpecial(Numpad[6]); + return; + case KEY_NUMPAD7: + writeSpecial(Numpad[7]); + return; + case KEY_NUMPAD8: + writeSpecial(Numpad[8]); + return; + case KEY_NUMPAD9: + writeSpecial(Numpad[9]); + return; + case KEY_DECIMAL: + writeSpecial(NUMDot[xind]); + return; + case KEY_ADD: + writeSpecial(NUMPlus[xind]); + return; + } + + if (!((keyChar == 8) || (keyChar == 127) || (keyChar == '\r') || (keyChar == '\n'))) { + write(keyChar); + return; + } + } + + private void handle_dcs(String dcs) { + debugStr.append("DCS: ") + .append(dcs); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + private void handle_osc(String osc) { + if (osc.length() > 2 && osc.substring(0, 2).equals("4;")) { + // Define color palette + String[] colorData = osc.split(";"); + + try { + int colorIndex = Integer.parseInt(colorData[1]); + + if ("rgb:".equals(colorData[2].substring(0, 4))) { + String[] rgb = colorData[2].substring(4).split("/"); + + int red = Integer.parseInt(rgb[0].substring(0, 2), 16) & 0xFF; + int green = Integer.parseInt(rgb[1].substring(0, 2), 16) & 0xFF; + int blue = Integer.parseInt(rgb[2].substring(0, 2), 16) & 0xFF; + display.setColor(colorIndex, red, green, blue); + } + } catch (Exception e) { + debugStr.append("OSC: invalid color sequence encountered: ") + .append(osc); + debug(debugStr.toString()); + debugStr.setLength(0); + } + } else + debug("OSC: " + osc); + } + + private final static char unimap[] = { + //# + //# Name: cp437_DOSLatinUS to Unicode table + //# Unicode version: 1.1 + //# Table version: 1.1 + //# Table format: Format A + //# Date: 03/31/95 + //# Authors: Michel Suignard <michelsu@microsoft.com> + //# Lori Hoerth <lorih@microsoft.com> + //# General notes: none + //# + //# Format: Three tab-separated columns + //# Column #1 is the cp1255_WinHebrew code (in hex) + //# Column #2 is the Unicode (in hex as 0xXXXX) + //# Column #3 is the Unicode name (follows a comment sign, '#') + //# + //# The entries are in cp437_DOSLatinUS order + //# + + 0x0000, // #NULL + 0x0001, // #START OF HEADING + 0x0002, // #START OF TEXT + 0x0003, // #END OF TEXT + 0x0004, // #END OF TRANSMISSION + 0x0005, // #ENQUIRY + 0x0006, // #ACKNOWLEDGE + 0x0007, // #BELL + 0x0008, // #BACKSPACE + 0x0009, // #HORIZONTAL TABULATION + 0x000a, // #LINE FEED + 0x000b, // #VERTICAL TABULATION + 0x000c, // #FORM FEED + 0x000d, // #CARRIAGE RETURN + 0x000e, // #SHIFT OUT + 0x000f, // #SHIFT IN + 0x0010, // #DATA LINK ESCAPE + 0x0011, // #DEVICE CONTROL ONE + 0x0012, // #DEVICE CONTROL TWO + 0x0013, // #DEVICE CONTROL THREE + 0x0014, // #DEVICE CONTROL FOUR + 0x0015, // #NEGATIVE ACKNOWLEDGE + 0x0016, // #SYNCHRONOUS IDLE + 0x0017, // #END OF TRANSMISSION BLOCK + 0x0018, // #CANCEL + 0x0019, // #END OF MEDIUM + 0x001a, // #SUBSTITUTE + 0x001b, // #ESCAPE + 0x001c, // #FILE SEPARATOR + 0x001d, // #GROUP SEPARATOR + 0x001e, // #RECORD SEPARATOR + 0x001f, // #UNIT SEPARATOR + 0x0020, // #SPACE + 0x0021, // #EXCLAMATION MARK + 0x0022, // #QUOTATION MARK + 0x0023, // #NUMBER SIGN + 0x0024, // #DOLLAR SIGN + 0x0025, // #PERCENT SIGN + 0x0026, // #AMPERSAND + 0x0027, // #APOSTROPHE + 0x0028, // #LEFT PARENTHESIS + 0x0029, // #RIGHT PARENTHESIS + 0x002a, // #ASTERISK + 0x002b, // #PLUS SIGN + 0x002c, // #COMMA + 0x002d, // #HYPHEN-MINUS + 0x002e, // #FULL STOP + 0x002f, // #SOLIDUS + 0x0030, // #DIGIT ZERO + 0x0031, // #DIGIT ONE + 0x0032, // #DIGIT TWO + 0x0033, // #DIGIT THREE + 0x0034, // #DIGIT FOUR + 0x0035, // #DIGIT FIVE + 0x0036, // #DIGIT SIX + 0x0037, // #DIGIT SEVEN + 0x0038, // #DIGIT EIGHT + 0x0039, // #DIGIT NINE + 0x003a, // #COLON + 0x003b, // #SEMICOLON + 0x003c, // #LESS-THAN SIGN + 0x003d, // #EQUALS SIGN + 0x003e, // #GREATER-THAN SIGN + 0x003f, // #QUESTION MARK + 0x0040, // #COMMERCIAL AT + 0x0041, // #LATIN CAPITAL LETTER A + 0x0042, // #LATIN CAPITAL LETTER B + 0x0043, // #LATIN CAPITAL LETTER C + 0x0044, // #LATIN CAPITAL LETTER D + 0x0045, // #LATIN CAPITAL LETTER E + 0x0046, // #LATIN CAPITAL LETTER F + 0x0047, // #LATIN CAPITAL LETTER G + 0x0048, // #LATIN CAPITAL LETTER H + 0x0049, // #LATIN CAPITAL LETTER I + 0x004a, // #LATIN CAPITAL LETTER J + 0x004b, // #LATIN CAPITAL LETTER K + 0x004c, // #LATIN CAPITAL LETTER L + 0x004d, // #LATIN CAPITAL LETTER M + 0x004e, // #LATIN CAPITAL LETTER N + 0x004f, // #LATIN CAPITAL LETTER O + 0x0050, // #LATIN CAPITAL LETTER P + 0x0051, // #LATIN CAPITAL LETTER Q + 0x0052, // #LATIN CAPITAL LETTER R + 0x0053, // #LATIN CAPITAL LETTER S + 0x0054, // #LATIN CAPITAL LETTER T + 0x0055, // #LATIN CAPITAL LETTER U + 0x0056, // #LATIN CAPITAL LETTER V + 0x0057, // #LATIN CAPITAL LETTER W + 0x0058, // #LATIN CAPITAL LETTER X + 0x0059, // #LATIN CAPITAL LETTER Y + 0x005a, // #LATIN CAPITAL LETTER Z + 0x005b, // #LEFT SQUARE BRACKET + 0x005c, // #REVERSE SOLIDUS + 0x005d, // #RIGHT SQUARE BRACKET + 0x005e, // #CIRCUMFLEX ACCENT + 0x005f, // #LOW LINE + 0x0060, // #GRAVE ACCENT + 0x0061, // #LATIN SMALL LETTER A + 0x0062, // #LATIN SMALL LETTER B + 0x0063, // #LATIN SMALL LETTER C + 0x0064, // #LATIN SMALL LETTER D + 0x0065, // #LATIN SMALL LETTER E + 0x0066, // #LATIN SMALL LETTER F + 0x0067, // #LATIN SMALL LETTER G + 0x0068, // #LATIN SMALL LETTER H + 0x0069, // #LATIN SMALL LETTER I + 0x006a, // #LATIN SMALL LETTER J + 0x006b, // #LATIN SMALL LETTER K + 0x006c, // #LATIN SMALL LETTER L + 0x006d, // #LATIN SMALL LETTER M + 0x006e, // #LATIN SMALL LETTER N + 0x006f, // #LATIN SMALL LETTER O + 0x0070, // #LATIN SMALL LETTER P + 0x0071, // #LATIN SMALL LETTER Q + 0x0072, // #LATIN SMALL LETTER R + 0x0073, // #LATIN SMALL LETTER S + 0x0074, // #LATIN SMALL LETTER T + 0x0075, // #LATIN SMALL LETTER U + 0x0076, // #LATIN SMALL LETTER V + 0x0077, // #LATIN SMALL LETTER W + 0x0078, // #LATIN SMALL LETTER X + 0x0079, // #LATIN SMALL LETTER Y + 0x007a, // #LATIN SMALL LETTER Z + 0x007b, // #LEFT CURLY BRACKET + 0x007c, // #VERTICAL LINE + 0x007d, // #RIGHT CURLY BRACKET + 0x007e, // #TILDE + 0x007f, // #DELETE + 0x00c7, // #LATIN CAPITAL LETTER C WITH CEDILLA + 0x00fc, // #LATIN SMALL LETTER U WITH DIAERESIS + 0x00e9, // #LATIN SMALL LETTER E WITH ACUTE + 0x00e2, // #LATIN SMALL LETTER A WITH CIRCUMFLEX + 0x00e4, // #LATIN SMALL LETTER A WITH DIAERESIS + 0x00e0, // #LATIN SMALL LETTER A WITH GRAVE + 0x00e5, // #LATIN SMALL LETTER A WITH RING ABOVE + 0x00e7, // #LATIN SMALL LETTER C WITH CEDILLA + 0x00ea, // #LATIN SMALL LETTER E WITH CIRCUMFLEX + 0x00eb, // #LATIN SMALL LETTER E WITH DIAERESIS + 0x00e8, // #LATIN SMALL LETTER E WITH GRAVE + 0x00ef, // #LATIN SMALL LETTER I WITH DIAERESIS + 0x00ee, // #LATIN SMALL LETTER I WITH CIRCUMFLEX + 0x00ec, // #LATIN SMALL LETTER I WITH GRAVE + 0x00c4, // #LATIN CAPITAL LETTER A WITH DIAERESIS + 0x00c5, // #LATIN CAPITAL LETTER A WITH RING ABOVE + 0x00c9, // #LATIN CAPITAL LETTER E WITH ACUTE + 0x00e6, // #LATIN SMALL LIGATURE AE + 0x00c6, // #LATIN CAPITAL LIGATURE AE + 0x00f4, // #LATIN SMALL LETTER O WITH CIRCUMFLEX + 0x00f6, // #LATIN SMALL LETTER O WITH DIAERESIS + 0x00f2, // #LATIN SMALL LETTER O WITH GRAVE + 0x00fb, // #LATIN SMALL LETTER U WITH CIRCUMFLEX + 0x00f9, // #LATIN SMALL LETTER U WITH GRAVE + 0x00ff, // #LATIN SMALL LETTER Y WITH DIAERESIS + 0x00d6, // #LATIN CAPITAL LETTER O WITH DIAERESIS + 0x00dc, // #LATIN CAPITAL LETTER U WITH DIAERESIS + 0x00a2, // #CENT SIGN + 0x00a3, // #POUND SIGN + 0x00a5, // #YEN SIGN + 0x20a7, // #PESETA SIGN + 0x0192, // #LATIN SMALL LETTER F WITH HOOK + 0x00e1, // #LATIN SMALL LETTER A WITH ACUTE + 0x00ed, // #LATIN SMALL LETTER I WITH ACUTE + 0x00f3, // #LATIN SMALL LETTER O WITH ACUTE + 0x00fa, // #LATIN SMALL LETTER U WITH ACUTE + 0x00f1, // #LATIN SMALL LETTER N WITH TILDE + 0x00d1, // #LATIN CAPITAL LETTER N WITH TILDE + 0x00aa, // #FEMININE ORDINAL INDICATOR + 0x00ba, // #MASCULINE ORDINAL INDICATOR + 0x00bf, // #INVERTED QUESTION MARK + 0x2310, // #REVERSED NOT SIGN + 0x00ac, // #NOT SIGN + 0x00bd, // #VULGAR FRACTION ONE HALF + 0x00bc, // #VULGAR FRACTION ONE QUARTER + 0x00a1, // #INVERTED EXCLAMATION MARK + 0x00ab, // #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 0x00bb, // #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + 0x2591, // #LIGHT SHADE + 0x2592, // #MEDIUM SHADE + 0x2593, // #DARK SHADE + 0x2502, // #BOX DRAWINGS LIGHT VERTICAL + 0x2524, // #BOX DRAWINGS LIGHT VERTICAL AND LEFT + 0x2561, // #BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE + 0x2562, // #BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE + 0x2556, // #BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE + 0x2555, // #BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE + 0x2563, // #BOX DRAWINGS DOUBLE VERTICAL AND LEFT + 0x2551, // #BOX DRAWINGS DOUBLE VERTICAL + 0x2557, // #BOX DRAWINGS DOUBLE DOWN AND LEFT + 0x255d, // #BOX DRAWINGS DOUBLE UP AND LEFT + 0x255c, // #BOX DRAWINGS UP DOUBLE AND LEFT SINGLE + 0x255b, // #BOX DRAWINGS UP SINGLE AND LEFT DOUBLE + 0x2510, // #BOX DRAWINGS LIGHT DOWN AND LEFT + 0x2514, // #BOX DRAWINGS LIGHT UP AND RIGHT + 0x2534, // #BOX DRAWINGS LIGHT UP AND HORIZONTAL + 0x252c, // #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + 0x251c, // #BOX DRAWINGS LIGHT VERTICAL AND RIGHT + 0x2500, // #BOX DRAWINGS LIGHT HORIZONTAL + 0x253c, // #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + 0x255e, // #BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE + 0x255f, // #BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE + 0x255a, // #BOX DRAWINGS DOUBLE UP AND RIGHT + 0x2554, // #BOX DRAWINGS DOUBLE DOWN AND RIGHT + 0x2569, // #BOX DRAWINGS DOUBLE UP AND HORIZONTAL + 0x2566, // #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + 0x2560, // #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + 0x2550, // #BOX DRAWINGS DOUBLE HORIZONTAL + 0x256c, // #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + 0x2567, // #BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE + 0x2568, // #BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE + 0x2564, // #BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE + 0x2565, // #BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE + 0x2559, // #BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE + 0x2558, // #BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE + 0x2552, // #BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE + 0x2553, // #BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE + 0x256b, // #BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE + 0x256a, // #BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE + 0x2518, // #BOX DRAWINGS LIGHT UP AND LEFT + 0x250c, // #BOX DRAWINGS LIGHT DOWN AND RIGHT + 0x2588, // #FULL BLOCK + 0x2584, // #LOWER HALF BLOCK + 0x258c, // #LEFT HALF BLOCK + 0x2590, // #RIGHT HALF BLOCK + 0x2580, // #UPPER HALF BLOCK + 0x03b1, // #GREEK SMALL LETTER ALPHA + 0x00df, // #LATIN SMALL LETTER SHARP S + 0x0393, // #GREEK CAPITAL LETTER GAMMA + 0x03c0, // #GREEK SMALL LETTER PI + 0x03a3, // #GREEK CAPITAL LETTER SIGMA + 0x03c3, // #GREEK SMALL LETTER SIGMA + 0x00b5, // #MICRO SIGN + 0x03c4, // #GREEK SMALL LETTER TAU + 0x03a6, // #GREEK CAPITAL LETTER PHI + 0x0398, // #GREEK CAPITAL LETTER THETA + 0x03a9, // #GREEK CAPITAL LETTER OMEGA + 0x03b4, // #GREEK SMALL LETTER DELTA + 0x221e, // #INFINITY + 0x03c6, // #GREEK SMALL LETTER PHI + 0x03b5, // #GREEK SMALL LETTER EPSILON + 0x2229, // #INTERSECTION + 0x2261, // #IDENTICAL TO + 0x00b1, // #PLUS-MINUS SIGN + 0x2265, // #GREATER-THAN OR EQUAL TO + 0x2264, // #LESS-THAN OR EQUAL TO + 0x2320, // #TOP HALF INTEGRAL + 0x2321, // #BOTTOM HALF INTEGRAL + 0x00f7, // #DIVISION SIGN + 0x2248, // #ALMOST EQUAL TO + 0x00b0, // #DEGREE SIGN + 0x2219, // #BULLET OPERATOR + 0x00b7, // #MIDDLE DOT + 0x221a, // #SQUARE ROOT + 0x207f, // #SUPERSCRIPT LATIN SMALL LETTER N + 0x00b2, // #SUPERSCRIPT TWO + 0x25a0, // #BLACK SQUARE + 0x00a0, // #NO-BREAK SPACE + }; + + public char map_cp850_unicode(char x) { + if (x >= 0x100) + return x; + return unimap[x]; + } + + private void _SetCursor(int row, int col) { + int maxr = height - 1; + int tm = getTopMargin(); + + R = (row < 0)?0: row; + C = (col < 0)?0: (col >= width) ? width - 1 : col; + + if (!moveoutsidemargins) { + R += tm; + maxr = getBottomMargin(); + } + if (R > maxr) R = maxr; + } + + private void putChar(char c, boolean isWide, boolean doshowcursor) { + int rows = this.height; //statusline + int columns = this.width; + // byte msg[]; + +// if (debug > 4) { +// debugStr.append("putChar(") +// .append(c) +// .append(" [") +// .append((int) c) +// .append("]) at R=") +// .append(R) +// .append(" , C=") +// .append(C) +// .append(", columns=") +// .append(columns) +// .append(", rows=") +// .append(rows); +// debug(debugStr.toString()); +// debugStr.setLength(0); +// } +// markLine(R, 1); +// if (c > 255) { +// if (debug > 0) +// debug("char > 255:" + (int) c); +// //return; +// } + + switch (term_state) { + case TSTATE_DATA: + /* FIXME: we shouldn't use chars with bit 8 set if ibmcharset. + * probably... but some BBS do anyway... + */ + if (!useibmcharset) { + boolean doneflag = true; + switch (c) { + case OSC: + osc = ""; + term_state = TSTATE_OSC; + break; + case RI: + if (R > getTopMargin()) + R--; + else + insertLine(R, 1, SCROLL_DOWN); + if (debug > 1) + debug("RI"); + break; + case IND: + if (debug > 2) { + debugStr.append("IND at ") + .append(R) + .append(", tm is ") + .append(getTopMargin()) + .append(", bm is ") + .append(getBottomMargin()); + debug(debugStr.toString()); + debugStr.setLength(0); + } + if (R == getBottomMargin() || R == rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + if (debug > 1) + debug("IND (at " + R + " )"); + break; + case NEL: + if (R == getBottomMargin() || R == rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + C = 0; + if (debug > 1) + debug("NEL (at " + R + " )"); + break; + case HTS: + Tabs[C] = 1; + if (debug > 1) + debug("HTS"); + break; + case DCS: + dcs = ""; + term_state = TSTATE_DCS; + break; + default: + doneflag = false; + break; + } + if (doneflag) break; + } + switch (c) { + case SS3: + onegl = 3; + break; + case SS2: + onegl = 2; + break; + case CSI: // should be in the 8bit section, but some BBS use this + DCEvar = 0; + DCEvars[0] = 0; + DCEvars[1] = 0; + DCEvars[2] = 0; + DCEvars[3] = 0; + term_state = TSTATE_CSI; + break; + case ESC: + term_state = TSTATE_ESC; + lastwaslf = 0; + break; + case 5: /* ENQ */ + write(answerBack, false); + break; + case 12: + /* FormFeed, Home for the BBS world */ + deleteArea(0, 0, columns, rows, attributes); + C = R = 0; + break; + case '\b': /* 8 */ + C--; + if (C < 0) + C = 0; + lastwaslf = 0; + break; + case '\t': + do { + // Don't overwrite or insert! TABS are not destructive, but movement! + C++; + } while (C < columns && (Tabs[C] == 0)); + lastwaslf = 0; + break; + case '\r': // 13 CR + C = 0; + break; + case '\n': // 10 LF + if (debug > 3) + debug("R= " + R + ", bm " + getBottomMargin() + ", tm=" + getTopMargin() + ", rows=" + rows); + if (!vms) { + if (lastwaslf != 0 && lastwaslf != c) // Ray: I do not understand this logic. + break; + lastwaslf = c; + /*C = 0;*/ + } + if (R == getBottomMargin() || R >= rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + break; + case 7: + beep(); + break; + case '\016': /* SMACS , as */ + /* ^N, Shift out - Put G1 into GL */ + gl = 1; + usedcharsets = true; + break; + case '\017': /* RMACS , ae */ + /* ^O, Shift in - Put G0 into GL */ + gl = 0; + usedcharsets = true; + break; + default: + { + int thisgl = gl; + + if (onegl >= 0) { + thisgl = onegl; + onegl = -1; + } + lastwaslf = 0; + if (c < 32) { + if (c != 0) + if (debug > 0) + debug("TSTATE_DATA char: " + ((int) c)); + /*break; some BBS really want those characters, like hearst etc. */ + if (c == 0) /* print 0 ... you bet */ + break; + } + if (C >= columns) { + if (wraparound) { + int bot = rows; + + // If we're in the scroll region, check against the bottom margin + if (R <= getBottomMargin() && R >= getTopMargin()) + bot = getBottomMargin() + 1; + + if (R < bot - 1) + R++; + else { + if (debug > 3) debug("scrolling due to wrap at " + R); + insertLine(R, 1, SCROLL_UP); + } + C = 0; + } else { + // cursor stays on last character. + C = columns - 1; + } + } + + boolean mapped = false; + + // Mapping if DEC Special is chosen charset + if (usedcharsets) { + if (c >= '\u0020' && c <= '\u007f') { + switch (gx[thisgl]) { + case '0': + // Remap SCOANSI line drawing to VT100 line drawing chars + // for our SCO using customers. + if (terminalID.equals("scoansi") || terminalID.equals("ansi")) { + for (int i = 0; i < scoansi_acs.length(); i += 2) { + if (c == scoansi_acs.charAt(i)) { + c = scoansi_acs.charAt(i + 1); + break; + } + } + } + if (c >= '\u005f' && c <= '\u007e') { + c = DECSPECIAL[(short) c - 0x5f]; + mapped = true; + } + break; + case '<': // 'user preferred' is currently 'ISO Latin-1 suppl + c = (char) ((c & 0x7f) | 0x80); + mapped = true; + break; + case 'A': + case 'B': // Latin-1 , ASCII -> fall through + mapped = true; + break; + default: + debug("Unsupported GL mapping: " + gx[thisgl]); + break; + } + } + if (!mapped && (c >= '\u0080' && c <= '\u00ff')) { + switch (gx[gr]) { + case '0': + if (c >= '\u00df' && c <= '\u00fe') { + c = DECSPECIAL[c - '\u00df']; + mapped = true; + } + break; + case '<': + case 'A': + case 'B': + mapped = true; + break; + default: + debug("Unsupported GR mapping: " + gx[gr]); + break; + } + } + } + if (!mapped && useibmcharset) + c = map_cp850_unicode(c); + + /*if(true || (statusmode == 0)) { */ + if (isWide) { + if (C >= columns - 1) { + if (wraparound) { + int bot = rows; + + // If we're in the scroll region, check against the bottom margin + if (R <= getBottomMargin() && R >= getTopMargin()) + bot = getBottomMargin() + 1; + + if (R < bot - 1) + R++; + else { + if (debug > 3) debug("scrolling due to wrap at " + R); + insertLine(R, 1, SCROLL_UP); + } + C = 0; + } else { + // cursor stays on last wide character. + C = columns - 2; + } + } + } + + if (insertmode == 1) { + if (isWide) { + insertChar(C++, R, c, attributes | FULLWIDTH); + insertChar(C, R, ' ', attributes | FULLWIDTH); + } else + insertChar(C, R, c, attributes); + } else { + if (isWide) { + putChar(C++, R, c, attributes | FULLWIDTH); + putChar(C, R, ' ', attributes | FULLWIDTH); + } else + putChar(C, R, c, attributes); + } + + /* + } else { + if (insertmode==1) { + insertChar(C, rows, c, attributes); + } else { + putChar(C, rows, c, attributes); + } + } + */ + C++; + break; + } + } /* switch(c) */ + break; + case TSTATE_OSC: + if ((c < 0x20) && (c != ESC)) {// NP - No printing character + handle_osc(osc); + term_state = TSTATE_DATA; + break; + } + //but check for vt102 ESC \ + if (c == '\\' && osc.charAt(osc.length() - 1) == ESC) { + handle_osc(osc); + term_state = TSTATE_DATA; + break; + } + osc = osc + c; + break; + case TSTATE_ESCSPACE: + term_state = TSTATE_DATA; + switch (c) { + case 'F': /* S7C1T, Disable output of 8-bit controls, use 7-bit */ + output8bit = false; + break; + case 'G': /* S8C1T, Enable output of 8-bit control codes*/ + output8bit = true; + break; + default: + debug("ESC <space> " + c + " unhandled."); + } + break; + case TSTATE_ESC: + term_state = TSTATE_DATA; + switch (c) { + case ' ': + term_state = TSTATE_ESCSPACE; + break; + case '#': + term_state = TSTATE_ESCSQUARE; + break; + case 'c': + /* Hard terminal reset */ + reset(); + break; + case '[': + DCEvar = 0; + DCEvars[0] = 0; + DCEvars[1] = 0; + DCEvars[2] = 0; + DCEvars[3] = 0; + term_state = TSTATE_CSI; + break; + case ']': + osc = ""; + term_state = TSTATE_OSC; + break; + case 'P': + dcs = ""; + term_state = TSTATE_DCS; + break; + case 'A': /* CUU */ + R--; + if (R < 0) R = 0; + break; + case 'B': /* CUD */ + R++; + if (R >= rows) R = rows - 1; + break; + case 'C': + C++; + if (C >= columns) C = columns - 1; + break; + case 'I': // RI + insertLine(R, 1, SCROLL_DOWN); + break; + case 'E': /* NEL */ + if (R == getBottomMargin() || R == rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + C = 0; + if (debug > 1) + debug("ESC E (at " + R + ")"); + break; + case 'D': /* IND */ + if (R == getBottomMargin() || R == rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + if (debug > 1) + debug("ESC D (at " + R + " )"); + break; + case 'J': /* erase to end of screen */ + if (R < rows - 1) + deleteArea(0, R + 1, columns, rows - R - 1, attributes); + if (C < columns - 1) + deleteArea(C, R, columns - C, 1, attributes); + break; + case 'K': + if (C < columns - 1) + deleteArea(C, R, columns - C, 1, attributes); + break; + case 'M': // RI + debug("ESC M : R is "+R+", tm is "+getTopMargin()+", bm is "+getBottomMargin()); + if (R > getTopMargin()) { // just go up 1 line. + R--; + } else { // scroll down + insertLine(R, 1, SCROLL_DOWN); + } + /* else do nothing ; */ + if (debug > 2) + debug("ESC M "); + break; + case 'H': + if (debug > 1) + debug("ESC H at " + C); + /* right border probably ...*/ + if (C >= columns) + C = columns - 1; + Tabs[C] = 1; + break; + case 'N': // SS2 + onegl = 2; + break; + case 'O': // SS3 + onegl = 3; + break; + case '=': + /*application keypad*/ + if (debug > 0) + debug("ESC ="); + keypadmode = true; + break; + case '<': /* vt52 mode off */ + vt52mode = false; + break; + case '>': /*normal keypad*/ + if (debug > 0) + debug("ESC >"); + keypadmode = false; + break; + case '7': /* DECSC: save cursor, attributes */ + Sc = C; + Sr = R; + Sgl = gl; + Sgr = gr; + Sa = attributes; + Sgx = new char[4]; + for (int i = 0; i < 4; i++) Sgx[i] = gx[i]; + if (debug > 1) + debug("ESC 7"); + break; + case '8': /* DECRC: restore cursor, attributes */ + C = Sc; + R = Sr; + gl = Sgl; + gr = Sgr; + if (Sgx != null) + for (int i = 0; i < 4; i++) gx[i] = Sgx[i]; + attributes = Sa; + if (debug > 1) + debug("ESC 8"); + break; + case '(': /* Designate G0 Character set (ISO 2022) */ + term_state = TSTATE_SETG0; + usedcharsets = true; + break; + case ')': /* Designate G1 character set (ISO 2022) */ + term_state = TSTATE_SETG1; + usedcharsets = true; + break; + case '*': /* Designate G2 Character set (ISO 2022) */ + term_state = TSTATE_SETG2; + usedcharsets = true; + break; + case '+': /* Designate G3 Character set (ISO 2022) */ + term_state = TSTATE_SETG3; + usedcharsets = true; + break; + case '~': /* Locking Shift 1, right */ + gr = 1; + usedcharsets = true; + break; + case 'n': /* Locking Shift 2 */ + gl = 2; + usedcharsets = true; + break; + case '}': /* Locking Shift 2, right */ + gr = 2; + usedcharsets = true; + break; + case 'o': /* Locking Shift 3 */ + gl = 3; + usedcharsets = true; + break; + case '|': /* Locking Shift 3, right */ + gr = 3; + usedcharsets = true; + break; + case 'Y': /* vt52 cursor address mode , next chars are x,y */ + term_state = TSTATE_VT52Y; + break; + case '_': + term_state = TSTATE_TITLE; + break; + case '\\': + // TODO save title + term_state = TSTATE_DATA; + break; + default: + debug("ESC unknown letter: " + c + " (" + ((int) c) + ")"); + break; + } + break; + case TSTATE_VT52X: + C = c - 37; + if (C < 0) + C = 0; + else if (C >= width) + C = width - 1; + term_state = TSTATE_VT52Y; + break; + case TSTATE_VT52Y: + R = c - 37; + if (R < 0) + R = 0; + else if (R >= height) + R = height - 1; + term_state = TSTATE_DATA; + break; + case TSTATE_SETG0: + if (c != '0' && c != 'A' && c != 'B' && c != '<') + debug("ESC ( " + c + ": G0 char set? (" + ((int) c) + ")"); + else { + if (debug > 2) debug("ESC ( : G0 char set (" + c + " " + ((int) c) + ")"); + gx[0] = c; + } + term_state = TSTATE_DATA; + break; + case TSTATE_SETG1: + if (c != '0' && c != 'A' && c != 'B' && c != '<') { + debug("ESC ) " + c + " (" + ((int) c) + ") :G1 char set?"); + } else { + if (debug > 2) debug("ESC ) :G1 char set (" + c + " " + ((int) c) + ")"); + gx[1] = c; + } + term_state = TSTATE_DATA; + break; + case TSTATE_SETG2: + if (c != '0' && c != 'A' && c != 'B' && c != '<') + debug("ESC*:G2 char set? (" + ((int) c) + ")"); + else { + if (debug > 2) debug("ESC*:G2 char set (" + c + " " + ((int) c) + ")"); + gx[2] = c; + } + term_state = TSTATE_DATA; + break; + case TSTATE_SETG3: + if (c != '0' && c != 'A' && c != 'B' && c != '<') + debug("ESC+:G3 char set? (" + ((int) c) + ")"); + else { + if (debug > 2) debug("ESC+:G3 char set (" + c + " " + ((int) c) + ")"); + gx[3] = c; + } + term_state = TSTATE_DATA; + break; + case TSTATE_ESCSQUARE: + switch (c) { + case '8': + for (int i = 0; i < columns; i++) + for (int j = 0; j < rows; j++) + putChar(i, j, 'E', 0); + break; + default: + debug("ESC # " + c + " not supported."); + break; + } + term_state = TSTATE_DATA; + break; + case TSTATE_DCS: + if (c == '\\' && dcs.charAt(dcs.length() - 1) == ESC) { + handle_dcs(dcs); + term_state = TSTATE_DATA; + break; + } + dcs = dcs + c; + break; + + case TSTATE_DCEQ: + term_state = TSTATE_DATA; + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48; + term_state = TSTATE_DCEQ; + break; + case ';': + DCEvar++; + DCEvars[DCEvar] = 0; + term_state = TSTATE_DCEQ; + break; + case 's': // XTERM_SAVE missing! + if (true || debug > 1) + debug("ESC [ ? " + DCEvars[0] + " s unimplemented!"); + break; + case 'r': // XTERM_RESTORE + if (true || debug > 1) + debug("ESC [ ? " + DCEvars[0] + " r"); + /* DEC Mode reset */ + for (int i = 0; i <= DCEvar; i++) { + switch (DCEvars[i]) { + case 3: /* 80 columns*/ + setScreenSize(80, height, true); + break; + case 4: /* scrolling mode, smooth */ + break; + case 5: /* light background */ + break; + case 6: /* DECOM (Origin Mode) move inside margins. */ + moveoutsidemargins = true; + break; + case 7: /* DECAWM: Autowrap Mode */ + wraparound = false; + break; + case 12:/* local echo off */ + break; + case 9: /* X10 mouse */ + case 1000: /* xterm style mouse report on */ + case 1001: + case 1002: + case 1003: + mouserpt = DCEvars[i]; + break; + default: + debug("ESC [ ? " + DCEvars[0] + " r, unimplemented!"); + } + } + break; + case 'h': // DECSET + if (debug > 0) + debug("ESC [ ? " + DCEvars[0] + " h"); + /* DEC Mode set */ + for (int i = 0; i <= DCEvar; i++) { + switch (DCEvars[i]) { + case 1: /* Application cursor keys */ + KeyUp[0] = "\u001bOA"; + KeyDown[0] = "\u001bOB"; + KeyRight[0] = "\u001bOC"; + KeyLeft[0] = "\u001bOD"; + break; + case 2: /* DECANM */ + vt52mode = false; + break; + case 3: /* 132 columns*/ + setScreenSize(132, height, true); + break; + case 6: /* DECOM: move inside margins. */ + moveoutsidemargins = false; + break; + case 7: /* DECAWM: Autowrap Mode */ + wraparound = true; + break; + case 25: /* turn cursor on */ + showCursor(true); + break; + case 9: /* X10 mouse */ + case 1000: /* xterm style mouse report on */ + case 1001: + case 1002: + case 1003: + mouserpt = DCEvars[i]; + break; + + /* unimplemented stuff, fall through */ + /* 4 - scrolling mode, smooth */ + /* 5 - light background */ + /* 12 - local echo off */ + /* 18 - DECPFF - Printer Form Feed Mode -> On */ + /* 19 - DECPEX - Printer Extent Mode -> Screen */ + default: + debug("ESC [ ? " + DCEvars[0] + " h, unsupported."); + break; + } + } + break; + case 'i': // DEC Printer Control, autoprint, echo screenchars to printer + // This is different to CSI i! + // Also: "Autoprint prints a final display line only when the + // cursor is moved off the line by an autowrap or LF, FF, or + // VT (otherwise do not print the line)." + switch (DCEvars[0]) { + case 1: + if (debug > 1) + debug("CSI ? 1 i : Print line containing cursor"); + break; + case 4: + if (debug > 1) + debug("CSI ? 4 i : Start passthrough printing"); + break; + case 5: + if (debug > 1) + debug("CSI ? 4 i : Stop passthrough printing"); + break; + } + break; + case 'l': //DECRST + /* DEC Mode reset */ + if (debug > 0) + debug("ESC [ ? " + DCEvars[0] + " l"); + for (int i = 0; i <= DCEvar; i++) { + switch (DCEvars[i]) { + case 1: /* Application cursor keys */ + KeyUp[0] = "\u001b[A"; + KeyDown[0] = "\u001b[B"; + KeyRight[0] = "\u001b[C"; + KeyLeft[0] = "\u001b[D"; + break; + case 2: /* DECANM */ + vt52mode = true; + break; + case 3: /* 80 columns*/ + setScreenSize(80, height, true); + break; + case 6: /* DECOM: move outside margins. */ + moveoutsidemargins = true; + break; + case 7: /* DECAWM: Autowrap Mode OFF */ + wraparound = false; + break; + case 25: /* turn cursor off */ + showCursor(false); + break; + /* Unimplemented stuff: */ + /* 4 - scrolling mode, jump */ + /* 5 - dark background */ + /* 7 - DECAWM - no wrap around mode */ + /* 12 - local echo on */ + /* 18 - DECPFF - Printer Form Feed Mode -> Off*/ + /* 19 - DECPEX - Printer Extent Mode -> Scrolling Region */ + case 9: /* X10 mouse */ + case 1000: /* xterm style mouse report OFF */ + case 1001: + case 1002: + case 1003: + mouserpt = 0; + break; + default: + debug("ESC [ ? " + DCEvars[0] + " l, unsupported."); + break; + } + } + break; + case 'n': + if (debug > 0) + debug("ESC [ ? " + DCEvars[0] + " n"); + switch (DCEvars[0]) { + case 15: + /* printer? no printer. */ + write((ESC) + "[?13n", false); + debug("ESC[5n"); + break; + default: + debug("ESC [ ? " + DCEvars[0] + " n, unsupported."); + break; + } + break; + default: + debug("ESC [ ? " + DCEvars[0] + " " + c + ", unsupported."); + break; + } + break; + case TSTATE_CSI_EX: + term_state = TSTATE_DATA; + switch (c) { + case ESC: + term_state = TSTATE_ESC; + break; + default: + debug("Unknown character ESC[! character is " + (int) c); + break; + } + break; + case TSTATE_CSI_TICKS: + term_state = TSTATE_DATA; + switch (c) { + case 'p': + debug("Conformance level: " + DCEvars[0] + " (unsupported)," + DCEvars[1]); + if (DCEvars[0] == 61) { + output8bit = false; + break; + } + if (DCEvars[1] == 1) { + output8bit = false; + } else { + output8bit = true; /* 0 or 2 */ + } + break; + default: + debug("Unknown ESC [... \"" + c); + break; + } + break; + case TSTATE_CSI_EQUAL: + term_state = TSTATE_DATA; + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48; + term_state = TSTATE_CSI_EQUAL; + break; + case ';': + DCEvar++; + DCEvars[DCEvar] = 0; + term_state = TSTATE_CSI_EQUAL; + break; + + case 'F': /* SCO ANSI foreground */ + { + int newcolor; + + debug("ESC [ = "+DCEvars[0]+" F"); + + attributes &= ~COLOR_FG; + newcolor = ((DCEvars[0] & 1) << 2) | + (DCEvars[0] & 2) | + ((DCEvars[0] & 4) >> 2) ; + attributes |= (newcolor+1) << COLOR_FG_SHIFT; + + break; + } + case 'G': /* SCO ANSI background */ + { + int newcolor; + + debug("ESC [ = "+DCEvars[0]+" G"); + + attributes &= ~COLOR_BG; + newcolor = ((DCEvars[0] & 1) << 2) | + (DCEvars[0] & 2) | + ((DCEvars[0] & 4) >> 2) ; + attributes |= (newcolor+1) << COLOR_BG_SHIFT; + break; + } + + default: + debugStr.append("Unknown ESC [ = "); + for (int i=0;i<=DCEvar;i++) { + debugStr.append(DCEvars[i]) + .append(','); + } + debugStr.append(c); + debug(debugStr.toString()); + debugStr.setLength(0); + break; + } + break; + case TSTATE_CSI_DOLLAR: + term_state = TSTATE_DATA; + switch (c) { + case '}': + debug("Active Status Display now " + DCEvars[0]); + statusmode = DCEvars[0]; + break; + /* bad documentation? + case '-': + debug("Set Status Display now "+DCEvars[0]); + break; + */ + case '~': + debug("Status Line mode now " + DCEvars[0]); + break; + default: + debug("UNKNOWN Status Display code " + c + ", with Pn=" + DCEvars[0]); + break; + } + break; + case TSTATE_CSI: + term_state = TSTATE_DATA; + switch (c) { + case '"': + term_state = TSTATE_CSI_TICKS; + break; + case '$': + term_state = TSTATE_CSI_DOLLAR; + break; + case '=': + term_state = TSTATE_CSI_EQUAL; + break; + case '!': + term_state = TSTATE_CSI_EX; + break; + case '?': + DCEvar = 0; + DCEvars[0] = 0; + term_state = TSTATE_DCEQ; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48; + term_state = TSTATE_CSI; + break; + case ';': + DCEvar++; + DCEvars[DCEvar] = 0; + term_state = TSTATE_CSI; + break; + case 'c':/* send primary device attributes */ + /* send (ESC[?61c) */ + + String subcode = ""; + if (terminalID.equals("vt320")) subcode = "63;"; + if (terminalID.equals("vt220")) subcode = "62;"; + if (terminalID.equals("vt100")) subcode = "61;"; + write((ESC) + "[?" + subcode + "1;2c", false); + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " c"); + break; + case 'q': + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " q"); + break; + case 'g': + /* used for tabsets */ + switch (DCEvars[0]) { + case 3:/* clear them */ + Tabs = new byte[width]; + break; + case 0: + Tabs[C] = 0; + break; + } + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " g"); + break; + case 'h': + switch (DCEvars[0]) { + case 4: + insertmode = 1; + break; + case 20: + debug("Setting CRLF to TRUE"); + sendcrlf = true; + break; + default: + debug("unsupported: ESC [ " + DCEvars[0] + " h"); + break; + } + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " h"); + break; + case 'i': // Printer Controller mode. + // "Transparent printing sends all output, except the CSI 4 i + // termination string, to the printer and not the screen, + // uses an 8-bit channel if no parity so NUL and DEL will be + // seen by the printer and by the termination recognizer code, + // and all translation and character set selections are + // bypassed." + switch (DCEvars[0]) { + case 0: + if (debug > 1) + debug("CSI 0 i: Print Screen, not implemented."); + break; + case 4: + if (debug > 1) + debug("CSI 4 i: Enable Transparent Printing, not implemented."); + break; + case 5: + if (debug > 1) + debug("CSI 4/5 i: Disable Transparent Printing, not implemented."); + break; + default: + debug("ESC [ " + DCEvars[0] + " i, unimplemented!"); + } + break; + case 'l': + switch (DCEvars[0]) { + case 4: + insertmode = 0; + break; + case 20: + debug("Setting CRLF to FALSE"); + sendcrlf = false; + break; + default: + debug("ESC [ " + DCEvars[0] + " l, unimplemented!"); + break; + } + break; + case 'A': // CUU + { + int limit; + /* FIXME: xterm only cares about 0 and topmargin */ + if (R >= getTopMargin()) { + limit = getTopMargin(); + } else + limit = 0; + if (DCEvars[0] == 0) + R--; + else + R -= DCEvars[0]; + if (R < limit) + R = limit; + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " A"); + break; + } + case 'B': // CUD + /* cursor down n (1) times */ + { + int limit; + if (R <= getBottomMargin()) { + limit = getBottomMargin(); + } else + limit = rows - 1; + if (DCEvars[0] == 0) + R++; + else + R += DCEvars[0]; + if (R > limit) + R = limit; + else { + if (debug > 2) debug("Not limited."); + } + if (debug > 2) debug("to: " + R); + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " B (at C=" + C + ")"); + break; + } + case 'C': + if (DCEvars[0] == 0) + DCEvars[0] = 1; + while (DCEvars[0]-- > 0) { + C++; + } + if (C >= columns) + C = columns - 1; + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " C"); + break; + case 'd': // CVA + R = DCEvars[0] - 1; + if (R < 0) + R = 0; + else if (R >= height) + R = height - 1; + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " d"); + break; + case 'D': + if (DCEvars[0] == 0) + DCEvars[0] = 1; + while (DCEvars[0]-- > 0) { + C--; + } + if (C < 0) C = 0; + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " D"); + break; + case 'r': // DECSTBM + if (DCEvar > 0) // Ray: Any argument is optional + { + R = DCEvars[1] - 1; + if (R < 0) + R = rows - 1; + else if (R >= rows) { + R = rows - 1; + } + } else + R = rows - 1; + int bot = R; + if (R >= DCEvars[0]) { + R = DCEvars[0] - 1; + if (R < 0) + R = 0; + } + setMargins(R, bot); + _SetCursor(0, 0); + if (debug > 1) + debug("ESC [" + DCEvars[0] + " ; " + DCEvars[1] + " r"); + break; + case 'G': /* CUP / cursor absolute column */ + C = DCEvars[0]; + if (C < 0) + C = 0; + else if (C >= width) + C = width - 1; + if (debug > 1) debug("ESC [ " + DCEvars[0] + " G"); + break; + case 'H': /* CUP / cursor position */ + /* gets 2 arguments */ + _SetCursor(DCEvars[0] - 1, DCEvars[1] - 1); + if (debug > 2) { + debug("ESC [ " + DCEvars[0] + ";" + DCEvars[1] + " H, moveoutsidemargins " + moveoutsidemargins); + debug(" -> R now " + R + ", C now " + C); + } + break; + case 'f': /* move cursor 2 */ + /* gets 2 arguments */ + R = DCEvars[0] - 1; + C = DCEvars[1] - 1; + if (C < 0) + C = 0; + else if (C >= width) + C = width - 1; + if (R < 0) + R = 0; + else if (R >= height) + R = height - 1; + if (debug > 2) + debug("ESC [ " + DCEvars[0] + ";" + DCEvars[1] + " f"); + break; + case 'S': /* ind aka 'scroll forward' */ + if (DCEvars[0] == 0) + insertLine(getBottomMargin(), SCROLL_UP); + else + insertLine(getBottomMargin(), DCEvars[0], SCROLL_UP); + break; + case 'L': + /* insert n lines */ + if (DCEvars[0] == 0) + insertLine(R, SCROLL_DOWN); + else + insertLine(R, DCEvars[0], SCROLL_DOWN); + if (debug > 1) + debug("ESC [ " + DCEvars[0] + "" + (c) + " (at R " + R + ")"); + break; + case 'T': /* 'ri' aka scroll backward */ + if (DCEvars[0] == 0) + insertLine(getTopMargin(), SCROLL_DOWN); + else + insertLine(getTopMargin(), DCEvars[0], SCROLL_DOWN); + break; + case 'M': + if (debug > 1) + debug("ESC [ " + DCEvars[0] + "" + (c) + " at R=" + R); + if (DCEvars[0] == 0) + deleteLine(R); + else + for (int i = 0; i < DCEvars[0]; i++) + deleteLine(R); + break; + case 'K': + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " K"); + /* clear in line */ + switch (DCEvars[0]) { + case 6: /* 97801 uses ESC[6K for delete to end of line */ + case 0:/*clear to right*/ + if (C < columns - 1) + deleteArea(C, R, columns - C, 1, attributes); + break; + case 1:/*clear to the left, including this */ + if (C > 0) + deleteArea(0, R, C + 1, 1, attributes); + break; + case 2:/*clear whole line */ + deleteArea(0, R, columns, 1, attributes); + break; + } + break; + case 'J': + /* clear below current line */ + switch (DCEvars[0]) { + case 0: + if (R < rows - 1) + deleteArea(0, R + 1, columns, rows - R - 1, attributes); + if (C < columns - 1) + deleteArea(C, R, columns - C, 1, attributes); + break; + case 1: + if (R > 0) + deleteArea(0, 0, columns, R, attributes); + if (C > 0) + deleteArea(0, R, C + 1, 1, attributes);// include up to and including current + break; + case 2: + deleteArea(0, 0, columns, rows, attributes); + break; + } + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " J"); + break; + case '@': + if (DCEvars[0] == 0) DCEvars[0] = 1; + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " @"); + for (int i = 0; i < DCEvars[0]; i++) + insertChar(C, R, ' ', attributes); + break; + case 'X': + { + int toerase = DCEvars[0]; + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " X, C=" + C + ",R=" + R); + if (toerase == 0) + toerase = 1; + if (toerase + C > columns) + toerase = columns - C; + deleteArea(C, R, toerase, 1, attributes); + // does not change cursor position + break; + } + case 'P': + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " P, C=" + C + ",R=" + R); + if (DCEvars[0] == 0) DCEvars[0] = 1; + for (int i = 0; i < DCEvars[0]; i++) + deleteChar(C, R); + break; + case 'n': + switch (DCEvars[0]) { + case 5: /* malfunction? No malfunction. */ + writeSpecial((ESC) + "[0n"); + if (debug > 1) + debug("ESC[5n"); + break; + case 6: + // DO NOT offset R and C by 1! (checked against /usr/X11R6/bin/resize + // FIXME check again. + // FIXME: but vttest thinks different??? + writeSpecial((ESC) + "[" + R + ";" + C + "R"); + if (debug > 1) + debug("ESC[6n"); + break; + default: + if (debug > 0) + debug("ESC [ " + DCEvars[0] + " n??"); + break; + } + break; + case 's': /* DECSC - save cursor */ + Sc = C; + Sr = R; + Sa = attributes; + if (debug > 3) + debug("ESC[s"); + break; + case 'u': /* DECRC - restore cursor */ + C = Sc; + R = Sr; + attributes = Sa; + if (debug > 3) + debug("ESC[u"); + break; + case 'm': /* attributes as color, bold , blink,*/ + if (debug > 3) + debug("ESC [ "); + if (DCEvar == 0 && DCEvars[0] == 0) + attributes = 0; + for (int i = 0; i <= DCEvar; i++) { + switch (DCEvars[i]) { + case 0: + if (DCEvar > 0) { + if (terminalID.equals("scoansi")) { + attributes &= COLOR; /* Keeps color. Strange but true. */ + } else { + attributes = 0; + } + } + break; + case 1: + attributes |= BOLD; + attributes &= ~LOW; + break; + case 2: + /* SCO color hack mode */ + if (terminalID.equals("scoansi") && ((DCEvar - i) >= 2)) { + int ncolor; + attributes &= ~(COLOR | BOLD); + + ncolor = DCEvars[i + 1]; + if ((ncolor & 8) == 8) + attributes |= BOLD; + ncolor = ((ncolor & 1) << 2) | (ncolor & 2) | ((ncolor & 4) >> 2); + attributes |= ((ncolor) + 1) << COLOR_FG_SHIFT; + ncolor = DCEvars[i + 2]; + ncolor = ((ncolor & 1) << 2) | (ncolor & 2) | ((ncolor & 4) >> 2); + attributes |= ((ncolor) + 1) << COLOR_BG_SHIFT; + i += 2; + } else { + attributes |= LOW; + } + break; + case 3: /* italics */ + attributes |= INVERT; + break; + case 4: + attributes |= UNDERLINE; + break; + case 7: + attributes |= INVERT; + break; + case 8: + attributes |= INVISIBLE; + break; + case 5: /* blink on */ + break; + /* 10 - ANSI X3.64-1979, select primary font, don't display control + * chars, don't set bit 8 on output */ + case 10: + gl = 0; + usedcharsets = true; + break; + /* 11 - ANSI X3.64-1979, select second alt. font, display control + * chars, set bit 8 on output */ + case 11: /* SMACS , as */ + case 12: + gl = 1; + usedcharsets = true; + break; + case 21: /* normal intensity */ + attributes &= ~(LOW | BOLD); + break; + case 23: /* italics off */ + attributes &= ~INVERT; + break; + case 25: /* blinking off */ + break; + case 27: + attributes &= ~INVERT; + break; + case 28: + attributes &= ~INVISIBLE; + break; + case 24: + attributes &= ~UNDERLINE; + break; + case 22: + attributes &= ~BOLD; + break; + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + attributes &= ~COLOR_FG; + attributes |= ((DCEvars[i] - 30) + 1)<< COLOR_FG_SHIFT; + break; + case 38: + if (DCEvars[i+1] == 5) { + attributes &= ~COLOR_FG; + attributes |= ((DCEvars[i + 2]) + 1) << COLOR_FG_SHIFT; + i += 2; + } + break; + case 39: + attributes &= ~COLOR_FG; + break; + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + attributes &= ~COLOR_BG; + attributes |= ((DCEvars[i] - 40) + 1) << COLOR_BG_SHIFT; + break; + case 48: + if (DCEvars[i+1] == 5) { + attributes &= ~COLOR_BG; + attributes |= (DCEvars[i + 2] + 1) << COLOR_BG_SHIFT; + i += 2; + } + break; + case 49: + attributes &= ~COLOR_BG; + break; + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: + attributes &= ~COLOR_FG; + attributes |= ((DCEvars[i] - 82) + 1) << COLOR_FG_SHIFT; + break; + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: + attributes &= ~COLOR_BG; + attributes |= ((DCEvars[i] - 92) + 1) << COLOR_BG_SHIFT; + break; + + default: + debugStr.append("ESC [ ") + .append(DCEvars[i]) + .append(" m unknown..."); + debug(debugStr.toString()); + debugStr.setLength(0); + break; + } + if (debug > 3) { + debugStr.append(DCEvars[i]) + .append(';'); + debug(debugStr.toString()); + debugStr.setLength(0); + } + } + if (debug > 3) { + debugStr.append(" (attributes = ") + .append(attributes) + .append(")m"); + debug(debugStr.toString()); + debugStr.setLength(0); + } + break; + default: + debugStr.append("ESC [ unknown letter: ") + .append(c) + .append(" (") + .append((int)c) + .append(')'); + debug(debugStr.toString()); + debugStr.setLength(0); + break; + } + break; + case TSTATE_TITLE: + switch (c) { + case ESC: + term_state = TSTATE_ESC; + break; + default: + // TODO save title + break; + } + break; + default: + term_state = TSTATE_DATA; + break; + } + + setCursorPosition(C, R); + } + + /* hard reset the terminal */ + public void reset() { + gx[0] = 'B'; + gx[1] = 'B'; + gx[2] = 'B'; + gx[3] = 'B'; + + gl = 0; // default GL to G0 + gr = 2; // default GR to G2 + + onegl = -1; // Single shift override + + /* reset tabs */ + int nw = width; + if (nw < 132) nw = 132; + Tabs = new byte[nw]; + for (int i = 0; i < nw; i += 8) { + Tabs[i] = 1; + } + + deleteArea(0, 0, width, height, attributes); + setMargins(0, height); + C = R = 0; + _SetCursor(0, 0); + + if (display != null) + display.resetColors(); + + showCursor(true); + /*FIXME:*/ + term_state = TSTATE_DATA; + } +} diff --git a/app/src/main/java/net/sourceforge/jsocks/Authentication.java b/app/src/main/java/net/sourceforge/jsocks/Authentication.java new file mode 100644 index 0000000..18cc48b --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/Authentication.java @@ -0,0 +1,34 @@ +package net.sourceforge.jsocks; + +/** + The Authentication interface provides for performing method specific + authentication for SOCKS5 connections. +*/ +public interface Authentication{ + /** + This method is called when SOCKS5 server have selected a particular + authentication method, for whch an implementaion have been registered. + + <p> + This method should return an array {inputstream,outputstream + [,UDPEncapsulation]}. The reason for that is that SOCKS5 protocol + allows to have method specific encapsulation of data on the socket for + purposes of integrity or security. And this encapsulation should be + performed by those streams returned from the method. It is also possible + to encapsulate datagrams. If authentication method supports such + encapsulation an instance of the UDPEncapsulation interface should be + returned as third element of the array, otherwise either null should be + returned as third element, or array should contain only 2 elements. + + @param methodId Authentication method selected by the server. + @param proxySocket Socket used to conect to the proxy. + @return Two or three element array containing + Input/Output streams which should be used on this connection. + Third argument is optional and should contain an instance + of UDPEncapsulation. It should be provided if the authentication + method used requires any encapsulation to be done on the + datagrams. + */ + Object[] doSocksAuthentication(int methodId,java.net.Socket proxySocket) + throws java.io.IOException; +} diff --git a/app/src/main/java/net/sourceforge/jsocks/AuthenticationNone.java b/app/src/main/java/net/sourceforge/jsocks/AuthenticationNone.java new file mode 100644 index 0000000..f28193a --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/AuthenticationNone.java @@ -0,0 +1,17 @@ +package net.sourceforge.jsocks; + +/** + SOCKS5 none authentication. Dummy class does almost nothing. +*/ +public class AuthenticationNone implements Authentication{ + + public Object[] doSocksAuthentication(int methodId, + java.net.Socket proxySocket) + throws java.io.IOException{ + + if(methodId!=0) return null; + + return new Object[] { proxySocket.getInputStream(), + proxySocket.getOutputStream()}; + } +} diff --git a/app/src/main/java/net/sourceforge/jsocks/Proxy.java b/app/src/main/java/net/sourceforge/jsocks/Proxy.java new file mode 100644 index 0000000..381c0a0 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/Proxy.java @@ -0,0 +1,404 @@ +package net.sourceforge.jsocks; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + Abstract class Proxy, base for classes Socks4Proxy and Socks5Proxy. + Defines methods for specifying default proxy, to be + used by all classes of this package. +*/ + +public abstract class Proxy{ + +//Data members + //protected InetRange directHosts = new InetRange(); + + protected InetAddress proxyIP = null; + protected String proxyHost = null; + protected int proxyPort; + protected Socket proxySocket = null; + + protected InputStream in; + protected OutputStream out; + + protected int version; + + protected Proxy chainProxy = null; + + +//Protected static/class variables + protected static Proxy defaultProxy = null; + +//Constructors +//==================== + Proxy(String proxyHost, int proxyPort) throws UnknownHostException { + this.proxyHost = proxyHost; + this.proxyIP = InetAddress.getByName(proxyHost); + this.proxyPort = proxyPort; + } + + Proxy(Proxy chainProxy,InetAddress proxyIP,int proxyPort){ + this.chainProxy = chainProxy; + this.proxyIP = proxyIP; + this.proxyPort = proxyPort; + } + + Proxy(InetAddress proxyIP,int proxyPort){ + this.proxyIP = proxyIP; + this.proxyPort = proxyPort; + } + + Proxy(Proxy p){ + this.proxyIP = p.proxyIP; + this.proxyPort = p.proxyPort; + this.version = p.version; + } + +//Public instance methods +//======================== + + /** + Get the port on which proxy server is running. + * @return Proxy port. + */ + public int getPort(){ + return proxyPort; + } + /** + Get the ip address of the proxy server host. + * @return Proxy InetAddress. + */ + public InetAddress getInetAddress(){ + return proxyIP; + } + + /** + Get string representation of this proxy. + * @returns string in the form:proxyHost:proxyPort \t Version versionNumber + */ + public String toString(){ + return (""+proxyIP.getHostName()+":"+proxyPort+"\tVersion "+version); + } + + +//Public Static(Class) Methods +//============================== + + /** + * Sets SOCKS4 proxy as default. + @param hostName Host name on which SOCKS4 server is running. + @param port Port on which SOCKS4 server is running. + @param user Username to use for communications with proxy. + */ + public static void setDefaultProxy(String hostName,int port,String user) + throws UnknownHostException{ + defaultProxy = new Socks4Proxy(hostName,port,user); + } + + /** + * Sets SOCKS4 proxy as default. + @param ipAddress Host address on which SOCKS4 server is running. + @param port Port on which SOCKS4 server is running. + @param user Username to use for communications with proxy. + */ + public static void setDefaultProxy(InetAddress ipAddress,int port, + String user){ + defaultProxy = new Socks4Proxy(ipAddress,port,user); + } + /** + * Sets SOCKS5 proxy as default. + * Default proxy only supports no-authentication. + @param hostName Host name on which SOCKS5 server is running. + @param port Port on which SOCKS5 server is running. + */ + public static void setDefaultProxy(String hostName,int port) + throws UnknownHostException{ + defaultProxy = new Socks5Proxy(hostName,port); + } + /** + * Sets SOCKS5 proxy as default. + * Default proxy only supports no-authentication. + @param ipAddress Host address on which SOCKS5 server is running. + @param port Port on which SOCKS5 server is running. + */ + public static void setDefaultProxy(InetAddress ipAddress,int port){ + defaultProxy = new Socks5Proxy(ipAddress,port); + } + /** + * Sets default proxy. + @param p Proxy to use as default proxy. + */ + public static void setDefaultProxy(Proxy p){ + defaultProxy = p; + } + + /** + Get current default proxy. + * @return Current default proxy, or null if none is set. + */ + public static Proxy getDefaultProxy(){ + return defaultProxy; + } + + /** + Parses strings in the form: host[:port:user:password], and creates + proxy from information obtained from parsing. + <p> + Defaults: port = 1080.<br> + If user specified but not password, creates Socks4Proxy, if user + not specified creates Socks5Proxy, if both user and password are + speciefied creates Socks5Proxy with user/password authentication. + @param proxy_entry String in the form host[:port:user:password] + @return Proxy created from the string, null if entry was somehow + invalid(host unknown for example, or empty string) + */ + public static Proxy parseProxy(String proxy_entry){ + + String proxy_host; + int proxy_port = 1080; + String proxy_user = null; + String proxy_password = null; + Proxy proxy; + + java.util.StringTokenizer st = new java.util.StringTokenizer( + proxy_entry,":"); + if(st.countTokens() < 1) return null; + + proxy_host = st.nextToken(); + if(st.hasMoreTokens()) + try{ + proxy_port = Integer.parseInt(st.nextToken().trim()); + }catch(NumberFormatException nfe){} + + if(st.hasMoreTokens()) + proxy_user = st.nextToken(); + + if(st.hasMoreTokens()) + proxy_password = st.nextToken(); + + try{ + if(proxy_user == null) + proxy = new Socks5Proxy(proxy_host,proxy_port); + else if(proxy_password == null) + proxy = new Socks4Proxy(proxy_host,proxy_port,proxy_user); + else{ + proxy = new Socks5Proxy(proxy_host,proxy_port); + /* + UserPasswordAuthentication upa = new UserPasswordAuthentication( + proxy_user, proxy_password); + + ((Socks5Proxy)proxy).setAuthenticationMethod(upa.METHOD_ID,upa); + */ + } + }catch(UnknownHostException uhe){ + return null; + } + + return proxy; + } + + +//Protected Methods +//================= + + protected void startSession()throws SocksException{ + try{ + proxySocket = new Socket(proxyIP,proxyPort); + in = proxySocket.getInputStream(); + out = proxySocket.getOutputStream(); + }catch(SocksException se){ + throw se; + }catch(IOException io_ex){ + throw new SocksException(SOCKS_PROXY_IO_ERROR,""+io_ex); + } + } + + protected abstract Proxy copy(); + protected abstract ProxyMessage formMessage(int cmd,InetAddress ip,int port); + protected abstract ProxyMessage formMessage(int cmd,String host,int port) + throws UnknownHostException; + protected abstract ProxyMessage formMessage(InputStream in) + throws SocksException, + IOException; + + + protected ProxyMessage connect(InetAddress ip,int port) + throws SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_CONNECT, + ip,port); + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + } + protected ProxyMessage connect(String host,int port) + throws UnknownHostException,SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_CONNECT, + host,port); + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + } + + protected ProxyMessage bind(InetAddress ip,int port) + throws SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_BIND, + ip,port); + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + } + protected ProxyMessage bind(String host,int port) + throws UnknownHostException,SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_BIND, + host,port); + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + } + + protected ProxyMessage accept() + throws IOException,SocksException{ + ProxyMessage msg; + try{ + msg = formMessage(in); + }catch(InterruptedIOException iioe){ + throw iioe; + }catch(IOException io_ex){ + endSession(); + throw new SocksException(SOCKS_PROXY_IO_ERROR,"While Trying accept:" + +io_ex); + } + return msg; + } + + protected ProxyMessage udpAssociate(InetAddress ip,int port) + throws SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_UDP_ASSOCIATE, + ip,port); + if(request != null) + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + //Only get here if request was null + endSession(); + throw new SocksException(SOCKS_METHOD_NOTSUPPORTED, + "This version of proxy does not support UDP associate, use version 5"); + } + protected ProxyMessage udpAssociate(String host,int port) + throws UnknownHostException,SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_UDP_ASSOCIATE, + host,port); + if(request != null) return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + //Only get here if request was null + endSession(); + throw new SocksException(SOCKS_METHOD_NOTSUPPORTED, + "This version of proxy does not support UDP associate, use version 5"); + } + + + protected void endSession(){ + try{ + if(proxySocket!=null) proxySocket.close(); + proxySocket = null; + }catch(IOException io_ex){ + } + } + + /** + *Sends the request to SOCKS server + */ + protected void sendMsg(ProxyMessage msg)throws SocksException, + IOException{ + msg.write(out); + } + + /** + * Reads the reply from the SOCKS server + */ + protected ProxyMessage readMsg()throws SocksException, + IOException{ + return formMessage(in); + } + /** + *Sends the request reads reply and returns it + *throws exception if something wrong with IO + *or the reply code is not zero + */ + protected ProxyMessage exchange(ProxyMessage request) + throws SocksException{ + ProxyMessage reply; + try{ + request.write(out); + reply = formMessage(in); + }catch(SocksException s_ex){ + throw s_ex; + }catch(IOException ioe){ + throw(new SocksException(SOCKS_PROXY_IO_ERROR,""+ioe)); + } + return reply; + } + + +//Private methods +//=============== + + +//Constants + + public static final int SOCKS_SUCCESS =0; + public static final int SOCKS_FAILURE =1; + public static final int SOCKS_BADCONNECT =2; + public static final int SOCKS_BADNETWORK =3; + public static final int SOCKS_HOST_UNREACHABLE =4; + public static final int SOCKS_CONNECTION_REFUSED =5; + public static final int SOCKS_TTL_EXPIRE =6; + public static final int SOCKS_CMD_NOT_SUPPORTED =7; + public static final int SOCKS_ADDR_NOT_SUPPORTED =8; + + public static final int SOCKS_NO_PROXY =1<<16; + public static final int SOCKS_PROXY_NO_CONNECT =2<<16; + public static final int SOCKS_PROXY_IO_ERROR =3<<16; + public static final int SOCKS_AUTH_NOT_SUPPORTED =4<<16; + public static final int SOCKS_AUTH_FAILURE =5<<16; + public static final int SOCKS_JUST_ERROR =6<<16; + + public static final int SOCKS_DIRECT_FAILED =7<<16; + public static final int SOCKS_METHOD_NOTSUPPORTED =8<<16; + + + public static final int SOCKS_CMD_CONNECT =0x1; + static final int SOCKS_CMD_BIND =0x2; + static final int SOCKS_CMD_UDP_ASSOCIATE =0x3; + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/ProxyMessage.java b/app/src/main/java/net/sourceforge/jsocks/ProxyMessage.java new file mode 100644 index 0000000..442c380 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/ProxyMessage.java @@ -0,0 +1,109 @@ +package net.sourceforge.jsocks; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + Abstract class which describes SOCKS4/5 response/request. +*/ +public abstract class ProxyMessage{ + /** Host as an IP address */ + public InetAddress ip=null; + /** SOCKS version, or version of the response for SOCKS4*/ + public int version; + /** Port field of the request/response*/ + public int port; + /** Request/response code as an int*/ + public int command; + /** Host as string.*/ + public String host=null; + /** User field for SOCKS4 request messages*/ + public String user=null; + + ProxyMessage(int command,InetAddress ip,int port){ + this.command = command; + this.ip = ip; + this.port = port; + } + + ProxyMessage(){ + } + + + /** + Initialises Message from the stream. Reads server response from + given stream. + @param in Input stream to read response from. + @throws SocksException If server response code is not SOCKS_SUCCESS(0), or + if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public abstract void read(InputStream in) + throws SocksException, + IOException; + + + /** + Initialises Message from the stream. Reads server response or client + request from given stream. + + @param in Input stream to read response from. + @param clinetMode If true read server response, else read client request. + @throws SocksException If server response code is not SOCKS_SUCCESS(0) and + reading in client mode, or if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public abstract void read(InputStream in,boolean client_mode) + throws SocksException, + IOException; + + + /** + Writes the message to the stream. + @param out Output stream to which message should be written. + */ + public abstract void write(OutputStream out)throws SocksException, + IOException; + + /** + Get the Address field of this message as InetAddress object. + @return Host address or null, if one can't be determined. + */ + public InetAddress getInetAddress() throws UnknownHostException{ + return ip; + } + + + /** + Get string representaion of this message. + @return string representation of this message. + */ + public String toString(){ + return + "Proxy Message:\n"+ + "Version:"+ version+"\n"+ + "Command:"+ command+"\n"+ + "IP: "+ ip+"\n"+ + "Port: "+ port+"\n"+ + "User: "+ user+"\n" ; + } + +//Package methods +////////////////// + + static final String bytes2IPV4(byte[] addr,int offset){ + String hostName = ""+(addr[offset] & 0xFF); + for(int i = offset+1;i<offset+4;++i) + hostName+="."+(addr[i] & 0xFF); + return hostName; + } + + static final String bytes2IPV6(byte[] addr,int offset){ + //Have no idea how they look like! + return null; + } + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/ProxyServer.java b/app/src/main/java/net/sourceforge/jsocks/ProxyServer.java new file mode 100644 index 0000000..225149d --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/ProxyServer.java @@ -0,0 +1,591 @@ +package net.sourceforge.jsocks; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PushbackInputStream; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.NoRouteToHostException; +import java.net.ServerSocket; +import java.net.Socket; + +import net.sourceforge.jsocks.server.ServerAuthenticator; + +/** + SOCKS4 and SOCKS5 proxy, handles both protocols simultaniously. + Implements all SOCKS commands, including UDP relaying. + <p> + In order to use it you will need to implement ServerAuthenticator + interface. There is an implementation of this interface which does + no authentication ServerAuthenticatorNone, but it is very dangerous + to use, as it will give access to your local network to anybody + in the world. One should never use this authentication scheme unless + one have pretty good reason to do so. + There is a couple of other authentication schemes in socks.server package. + @see socks.server.ServerAuthenticator +*/ +public class ProxyServer implements Runnable{ + + ServerAuthenticator auth; + ProxyMessage msg = null; + + Socket sock=null,remote_sock=null; + ServerSocket ss=null; + UDPRelayServer relayServer = null; + InputStream in,remote_in; + OutputStream out,remote_out; + + int mode; + static final int START_MODE = 0; + static final int ACCEPT_MODE = 1; + static final int PIPE_MODE = 2; + static final int ABORT_MODE = 3; + + static final int BUF_SIZE = 8192; + + Thread pipe_thread1,pipe_thread2; + long lastReadTime; + + protected static int iddleTimeout = 180000; //3 minutes + static int acceptTimeout = 180000; //3 minutes + + static PrintStream log = null; + static Proxy proxy; + + +//Public Constructors +///////////////////// + + + /** + Creates a proxy server with given Authentication scheme. + @param auth Authentication scheme to be used. + */ + public ProxyServer(ServerAuthenticator auth){ + this.auth = auth; + } + +//Other constructors +//////////////////// + + protected ProxyServer(ServerAuthenticator auth,Socket s){ + this.auth = auth; + this.sock = s; + mode = START_MODE; + } + +//Public methods +///////////////// + + /** + Set the logging stream. Specifying null disables logging. + */ + public static void setLog(OutputStream out){ + if(out == null){ + log = null; + }else{ + log = new PrintStream(out,true); + } + + UDPRelayServer.log = log; + } + + /** + Set proxy. + <p> + Allows Proxy chaining so that one Proxy server is connected to another + and so on. If proxy supports SOCKSv4, then only some SOCKSv5 requests + can be handled, UDP would not work, however CONNECT and BIND will be + translated. + + @param p Proxy which should be used to handle user requests. + */ + public static void setProxy(Proxy p){ + proxy =p; + UDPRelayServer.proxy = proxy; + } + + /** + Get proxy. + @return Proxy wich is used to handle user requests. + */ + public static Proxy getProxy(){ + return proxy; + } + + /** + Sets the timeout for connections, how long shoud server wait + for data to arrive before dropping the connection.<br> + Zero timeout implies infinity.<br> + Default timeout is 3 minutes. + */ + public static void setIddleTimeout(int timeout){ + iddleTimeout = timeout; + } + /** + Sets the timeout for BIND command, how long the server should + wait for the incoming connection.<br> + Zero timeout implies infinity.<br> + Default timeout is 3 minutes. + */ + public static void setAcceptTimeout(int timeout){ + acceptTimeout = timeout; + } + + /** + Sets the timeout for UDPRelay server.<br> + Zero timeout implies infinity.<br> + Default timeout is 3 minutes. + */ + public static void setUDPTimeout(int timeout){ + UDPRelayServer.setTimeout(timeout); + } + + /** + Sets the size of the datagrams used in the UDPRelayServer.<br> + Default size is 64K, a bit more than maximum possible size of the + datagram. + */ + public static void setDatagramSize(int size){ + UDPRelayServer.setDatagramSize(size); + } + + + /** + Start the Proxy server at given port.<br> + This methods blocks. + */ + public void start(int port){ + start(port,5,null); + } + + /** + Create a server with the specified port, listen backlog, and local + IP address to bind to. The localIP argument can be used on a multi-homed + host for a ServerSocket that will only accept connect requests to one of + its addresses. If localIP is null, it will default accepting connections + on any/all local addresses. The port must be between 0 and 65535, + inclusive. <br> + This methods blocks. + */ + public void start(int port,int backlog,InetAddress localIP){ + try{ + ss = new ServerSocket(port,backlog,localIP); + log("Starting SOCKS Proxy on:"+ss.getInetAddress().getHostAddress()+":" + +ss.getLocalPort()); + while(true){ + Socket s = ss.accept(); + log("Accepted from:"+s.getInetAddress().getHostName()+":" + +s.getPort()); + ProxyServer ps = new ProxyServer(auth,s); + (new Thread(ps)).start(); + } + }catch(IOException ioe){ + ioe.printStackTrace(); + }finally{ + } + } + + /** + Stop server operation.It would be wise to interrupt thread running the + server afterwards. + */ + public void stop(){ + try{ + if(ss != null) ss.close(); + }catch(IOException ioe){ + } + } + +//Runnable interface +//////////////////// + public void run(){ + switch(mode){ + case START_MODE: + try{ + startSession(); + }catch(IOException ioe){ + handleException(ioe); + //ioe.printStackTrace(); + }finally{ + abort(); + if(auth!=null) auth.endSession(); + log("Main thread(client->remote)stopped."); + } + break; + case ACCEPT_MODE: + try{ + doAccept(); + mode = PIPE_MODE; + pipe_thread1.interrupt(); //Tell other thread that connection have + //been accepted. + pipe(remote_in,out); + }catch(IOException ioe){ + //log("Accept exception:"+ioe); + handleException(ioe); + }finally{ + abort(); + log("Accept thread(remote->client) stopped"); + } + break; + case PIPE_MODE: + try{ + pipe(remote_in,out); + }catch(IOException ioe){ + }finally{ + abort(); + log("Support thread(remote->client) stopped"); + } + break; + case ABORT_MODE: + break; + default: + log("Unexpected MODE "+mode); + } + } + +//Private methods +///////////////// + private void startSession() throws IOException{ + sock.setSoTimeout(iddleTimeout); + + try{ + auth = auth.startSession(sock); + }catch(IOException ioe){ + log("Auth throwed exception:"+ioe); + auth = null; + return; + } + + if(auth == null){ //Authentication failed + log("Authentication failed"); + return; + } + + in = auth.getInputStream(); + out = auth.getOutputStream(); + + msg = readMsg(in); + handleRequest(msg); + } + + protected void handleRequest(ProxyMessage msg) + throws IOException{ + if(!auth.checkRequest(msg)) throw new + SocksException(Proxy.SOCKS_FAILURE); + + if(msg.ip == null){ + if(msg instanceof Socks5Message){ + msg.ip = InetAddress.getByName(msg.host); + }else + throw new SocksException(Proxy.SOCKS_FAILURE); + } + log(msg); + + switch(msg.command){ + case Proxy.SOCKS_CMD_CONNECT: + onConnect(msg); + break; + case Proxy.SOCKS_CMD_BIND: + onBind(msg); + break; + case Proxy.SOCKS_CMD_UDP_ASSOCIATE: + onUDP(msg); + break; + default: + throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED); + } + } + + private void handleException(IOException ioe){ + //If we couldn't read the request, return; + if(msg == null) return; + //If have been aborted by other thread + if(mode == ABORT_MODE) return; + //If the request was successfully completed, but exception happened later + if(mode == PIPE_MODE) return; + + int error_code = Proxy.SOCKS_FAILURE; + + if(ioe instanceof SocksException) + error_code = ((SocksException)ioe).errCode; + else if(ioe instanceof NoRouteToHostException) + error_code = Proxy.SOCKS_HOST_UNREACHABLE; + else if(ioe instanceof ConnectException) + error_code = Proxy.SOCKS_CONNECTION_REFUSED; + else if(ioe instanceof InterruptedIOException) + error_code = Proxy.SOCKS_TTL_EXPIRE; + + if(error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED || error_code < 0){ + error_code = Proxy.SOCKS_FAILURE; + } + + sendErrorMessage(error_code); + } + + private void onConnect(ProxyMessage msg) throws IOException{ + Socket s; + ProxyMessage response = null; + + s = new Socket(msg.ip,msg.port); + + log("Connected to "+s.getInetAddress()+":"+s.getPort()); + + if(msg instanceof Socks5Message){ + response = new Socks5Message(Proxy.SOCKS_SUCCESS, + s.getLocalAddress(), + s.getLocalPort()); + }else{ + response = new Socks4Message(Socks4Message.REPLY_OK, + s.getLocalAddress(),s.getLocalPort()); + + } + response.write(out); + startPipe(s); + } + + private void onBind(ProxyMessage msg) throws IOException{ + ProxyMessage response = null; + + if(proxy == null) + ss = new ServerSocket(0); + else + ss = new SocksServerSocket(proxy, msg.ip, msg.port); + + ss.setSoTimeout(acceptTimeout); + + log("Trying accept on "+ss.getInetAddress()+":"+ss.getLocalPort()); + + if(msg.version == 5) + response = new Socks5Message(Proxy.SOCKS_SUCCESS,ss.getInetAddress(), + ss.getLocalPort()); + else + response = new Socks4Message(Socks4Message.REPLY_OK, + ss.getInetAddress(), + ss.getLocalPort()); + response.write(out); + + mode = ACCEPT_MODE; + + pipe_thread1 = Thread.currentThread(); + pipe_thread2 = new Thread(this); + pipe_thread2.start(); + + //Make timeout infinit. + sock.setSoTimeout(0); + int eof=0; + + try{ + while((eof=in.read())>=0){ + if(mode != ACCEPT_MODE){ + if(mode != PIPE_MODE) return;//Accept failed + + remote_out.write(eof); + break; + } + } + }catch(EOFException eofe){ + //System.out.println("EOF exception"); + return;//Connection closed while we were trying to accept. + }catch(InterruptedIOException iioe){ + //Accept thread interrupted us. + //System.out.println("Interrupted"); + if(mode != PIPE_MODE) + return;//If accept thread was not successfull return. + }finally{ + //System.out.println("Finnaly!"); + } + + if(eof < 0)//Connection closed while we were trying to accept; + return; + + //Do not restore timeout, instead timeout is set on the + //remote socket. It does not make any difference. + + pipe(in,remote_out); + } + + private void onUDP(ProxyMessage msg) throws IOException{ + if(msg.ip.getHostAddress().equals("0.0.0.0")) + msg.ip = sock.getInetAddress(); + log("Creating UDP relay server for "+msg.ip+":"+msg.port); + relayServer = new UDPRelayServer(msg.ip,msg.port, + Thread.currentThread(),sock,auth); + + ProxyMessage response; + + response = new Socks5Message(Proxy.SOCKS_SUCCESS, + relayServer.relayIP,relayServer.relayPort); + + response.write(out); + + relayServer.start(); + + //Make timeout infinit. + sock.setSoTimeout(0); + try{ + while(in.read()>=0) /*do nothing*/; + }catch(EOFException eofe){ + } + } + +//Private methods +////////////////// + + private void doAccept() throws IOException{ + Socket s; + long startTime = System.currentTimeMillis(); + + while(true){ + s = ss.accept(); + if(s.getInetAddress().equals(msg.ip)){ + //got the connection from the right host + //Close listenning socket. + ss.close(); + break; + }else if(ss instanceof SocksServerSocket){ + //We can't accept more then one connection + s.close(); + ss.close(); + throw new SocksException(Proxy.SOCKS_FAILURE); + }else{ + if(acceptTimeout!=0){ //If timeout is not infinit + int newTimeout = acceptTimeout-(int)(System.currentTimeMillis()- + startTime); + if(newTimeout <= 0) throw new InterruptedIOException( + "In doAccept()"); + ss.setSoTimeout(newTimeout); + } + s.close(); //Drop all connections from other hosts + } + } + + //Accepted connection + remote_sock = s; + remote_in = s.getInputStream(); + remote_out = s.getOutputStream(); + + //Set timeout + remote_sock.setSoTimeout(iddleTimeout); + + log("Accepted from "+s.getInetAddress()+":"+s.getPort()); + + ProxyMessage response; + + if(msg.version == 5) + response = new Socks5Message(Proxy.SOCKS_SUCCESS, s.getInetAddress(), + s.getPort()); + else + response = new Socks4Message(Socks4Message.REPLY_OK, + s.getInetAddress(), s.getPort()); + response.write(out); + } + + protected ProxyMessage readMsg(InputStream in) throws IOException{ + PushbackInputStream push_in; + if(in instanceof PushbackInputStream) + push_in = (PushbackInputStream) in; + else + push_in = new PushbackInputStream(in); + + int version = push_in.read(); + push_in.unread(version); + + + ProxyMessage msg; + + if(version == 5){ + msg = new Socks5Message(push_in,false); + }else if(version == 4){ + msg = new Socks4Message(push_in,false); + }else{ + throw new SocksException(Proxy.SOCKS_FAILURE); + } + return msg; + } + + private void startPipe(Socket s){ + mode = PIPE_MODE; + remote_sock = s; + try{ + remote_in = s.getInputStream(); + remote_out = s.getOutputStream(); + pipe_thread1 = Thread.currentThread(); + pipe_thread2 = new Thread(this); + pipe_thread2.start(); + pipe(in,remote_out); + }catch(IOException ioe){ + } + } + + private void sendErrorMessage(int error_code){ + ProxyMessage err_msg; + if(msg instanceof Socks4Message) + err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED); + else + err_msg = new Socks5Message(error_code); + try{ + err_msg.write(out); + }catch(IOException ioe){} + } + + private synchronized void abort(){ + if(mode == ABORT_MODE) return; + mode = ABORT_MODE; + try{ + log("Aborting operation"); + if(remote_sock != null) remote_sock.close(); + if(sock != null) sock.close(); + if(relayServer!=null) relayServer.stop(); + if(ss!=null) ss.close(); + if(pipe_thread1 != null) pipe_thread1.interrupt(); + if(pipe_thread2 != null) pipe_thread2.interrupt(); + }catch(IOException ioe){} + } + + static final void log(String s){ + if(log != null){ + log.println(s); + log.flush(); + } + } + + static final void log(ProxyMessage msg){ + log("Request version:"+msg.version+ + "\tCommand: "+command2String(msg.command)); + log("IP:"+msg.ip +"\tPort:"+msg.port+ + (msg.version==4?"\tUser:"+msg.user:"")); + } + + private void pipe(InputStream in,OutputStream out) throws IOException{ + lastReadTime = System.currentTimeMillis(); + byte[] buf = new byte[BUF_SIZE]; + int len = 0; + while(len >= 0){ + try{ + if(len!=0){ + out.write(buf,0,len); + out.flush(); + } + len= in.read(buf); + lastReadTime = System.currentTimeMillis(); + }catch(InterruptedIOException iioe){ + if(iddleTimeout == 0) return;//Other thread interrupted us. + long timeSinceRead = System.currentTimeMillis() - lastReadTime; + if(timeSinceRead >= iddleTimeout - 1000) //-1s for adjustment. + return; + len = 0; + + } + } + } + static final String command_names[] = {"CONNECT","BIND","UDP_ASSOCIATE"}; + + static final String command2String(int cmd){ + if(cmd > 0 && cmd < 4) return command_names[cmd-1]; + else return "Unknown Command "+cmd; + } +} diff --git a/app/src/main/java/net/sourceforge/jsocks/Socks4Message.java b/app/src/main/java/net/sourceforge/jsocks/Socks4Message.java new file mode 100644 index 0000000..99fb211 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/Socks4Message.java @@ -0,0 +1,171 @@ +package net.sourceforge.jsocks; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + SOCKS4 Reply/Request message. +*/ + +public class Socks4Message extends ProxyMessage{ + + private byte[] msgBytes; + private int msgLength; + + /** + * Server failed reply, cmd command for failed request + */ + public Socks4Message(int cmd){ + super(cmd,null,0); + this.user = null; + + msgLength = 2; + msgBytes = new byte[2]; + + msgBytes[0] = (byte) 0; + msgBytes[1] = (byte) command; + } + + /** + * Server successfull reply + */ + public Socks4Message(int cmd,InetAddress ip,int port){ + this(0,cmd,ip,port,null); + } + + /** + * Client request + */ + public Socks4Message(int cmd,InetAddress ip,int port,String user){ + this(SOCKS_VERSION,cmd,ip,port,user); + } + + /** + * Most general constructor + */ + public Socks4Message(int version, int cmd, + InetAddress ip,int port,String user){ + super(cmd,ip,port); + this.user = user; + this.version = version; + + msgLength = user == null?8:9+user.length(); + msgBytes = new byte[msgLength]; + + msgBytes[0] = (byte) version; + msgBytes[1] = (byte) command; + msgBytes[2] = (byte) (port >> 8); + msgBytes[3] = (byte) port; + + byte[] addr; + + if(ip != null) + addr = ip.getAddress(); + else{ + addr = new byte[4]; + addr[0]=addr[1]=addr[2]=addr[3]=0; + } + System.arraycopy(addr,0,msgBytes,4,4); + + if(user != null){ + byte[] buf = user.getBytes(); + System.arraycopy(buf,0,msgBytes,8,buf.length); + msgBytes[msgBytes.length -1 ] = 0; + } + } + + /** + *Initialise from the stream + *If clientMode is true attempts to read a server response + *otherwise reads a client request + *see read for more detail + */ + public Socks4Message(InputStream in, boolean clientMode) throws IOException{ + msgBytes = null; + read(in,clientMode); + } + + @Override +public void read(InputStream in) throws IOException{ + read(in,true); + } + + @Override +public void read(InputStream in, boolean clientMode) throws IOException{ + boolean mode4a = false; + DataInputStream d_in = new DataInputStream(in); + version= d_in.readUnsignedByte(); + command = d_in.readUnsignedByte(); + if(clientMode && command != REPLY_OK){ + String errMsg; + if(command >REPLY_OK && command < REPLY_BAD_IDENTD) + errMsg = replyMessage[command-REPLY_OK]; + else + errMsg = "Unknown Reply Code"; + throw new SocksException(command,errMsg); + } + port = d_in.readUnsignedShort(); + byte[] addr = new byte[4]; + d_in.readFully(addr); + if (addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] != 0) + mode4a = true; + else { + ip=bytes2IP(addr); + host = ip.getHostName(); + } + if(!clientMode){ + StringBuilder sb = new StringBuilder(); + int b; + while ((b = in.read()) != 0) + sb.append((char) b); + user = sb.toString(); + if (mode4a) { + sb.setLength(0); + while ((b = in.read()) != 0) + sb.append((char) b); + host = sb.toString(); + } + } + } + @Override +public void write(OutputStream out) throws IOException{ + if(msgBytes == null){ + Socks4Message msg = new Socks4Message(version,command,ip,port,user); + msgBytes = msg.msgBytes; + msgLength = msg.msgLength; + } + out.write(msgBytes); + } + + //Class methods + static InetAddress bytes2IP(byte[] addr){ + String s = bytes2IPV4(addr,0); + try{ + return InetAddress.getByName(s); + }catch(UnknownHostException uh_ex){ + return null; + } + } + + //Constants + + static final String[] replyMessage ={ + "Request Granted", + "Request Rejected or Failed", + "Failed request, can't connect to Identd", + "Failed request, bad user name"}; + + static final int SOCKS_VERSION = 4; + + public final static int REQUEST_CONNECT = 1; + public final static int REQUEST_BIND = 2; + + public final static int REPLY_OK = 90; + public final static int REPLY_REJECTED = 91; + public final static int REPLY_NO_CONNECT = 92; + public final static int REPLY_BAD_IDENTD = 93; + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/Socks4Proxy.java b/app/src/main/java/net/sourceforge/jsocks/Socks4Proxy.java new file mode 100644 index 0000000..9a17fc2 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/Socks4Proxy.java @@ -0,0 +1,107 @@ +package net.sourceforge.jsocks; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + Proxy which describes SOCKS4 proxy. +*/ + +public class Socks4Proxy extends Proxy implements Cloneable{ + +//Data members + String user; + +//Public Constructors +//==================== + + /** + Creates the SOCKS4 proxy + @param p Proxy to use to connect to this proxy, allows proxy chaining. + @param proxyHost Address of the proxy server. + @param proxyPort Port of the proxy server + @param user User name to use for identification purposes. + @throws UnknownHostException If proxyHost can't be resolved. + */ + public Socks4Proxy(String proxyHost,int proxyPort,String user) + throws UnknownHostException{ + super(proxyHost,proxyPort); + this.user = new String(user); + version = 4; + } + + /** + Creates the SOCKS4 proxy + @param p Proxy to use to connect to this proxy, allows proxy chaining. + @param proxyIP Address of the proxy server. + @param proxyPort Port of the proxy server + @param user User name to use for identification purposes. + */ + public Socks4Proxy(Proxy p,InetAddress proxyIP,int proxyPort,String user){ + super(p,proxyIP,proxyPort); + this.user = new String(user); + version = 4; + } + + /** + Creates the SOCKS4 proxy + @param proxyIP Address of the proxy server. + @param proxyPort Port of the proxy server + @param user User name to use for identification purposes. + */ + public Socks4Proxy(InetAddress proxyIP,int proxyPort,String user){ + this(null,proxyIP,proxyPort,user); + } + +//Public instance methods +//======================== + + /** + * Creates a clone of this proxy. Changes made to the clone should not + * affect this object. + */ + public Object clone(){ + Socks4Proxy newProxy = new Socks4Proxy(proxyIP,proxyPort,user); + newProxy.chainProxy = chainProxy; + return newProxy; + } + + +//Public Static(Class) Methods +//============================== + + +//Protected Methods +//================= + + protected Proxy copy(){ + Socks4Proxy copy = new Socks4Proxy(proxyIP,proxyPort,user); + copy.chainProxy = chainProxy; + return copy; + } + + protected ProxyMessage formMessage(int cmd,InetAddress ip,int port){ + switch(cmd){ + case SOCKS_CMD_CONNECT: + cmd = Socks4Message.REQUEST_CONNECT; + break; + case SOCKS_CMD_BIND: + cmd = Socks4Message.REQUEST_BIND; + break; + default: + return null; + } + return new Socks4Message(cmd,ip,port,user); + } + protected ProxyMessage formMessage(int cmd,String host,int port) + throws UnknownHostException{ + return formMessage(cmd,InetAddress.getByName(host),port); + } + protected ProxyMessage formMessage(InputStream in) + throws SocksException, + IOException{ + return new Socks4Message(in,true); + } + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/Socks5DatagramSocket.java b/app/src/main/java/net/sourceforge/jsocks/Socks5DatagramSocket.java new file mode 100644 index 0000000..b847400 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/Socks5DatagramSocket.java @@ -0,0 +1,460 @@ +package net.sourceforge.jsocks; +import java.net.*; +import java.io.*; + +/** + Datagram socket to interract through the firewall.<BR> + Can be used same way as the normal DatagramSocket. One should + be carefull though with the datagram sizes used, as additional data + is present in both incomming and outgoing datagrams. + <p> + SOCKS5 protocol allows to send host address as either: + <ul> + <li> IPV4, normal 4 byte address. (10 bytes header size) + <li> IPV6, version 6 ip address (not supported by Java as for now). + 22 bytes header size. + <li> Host name,(7+length of the host name bytes header size). + </ul> + As with other Socks equivalents, direct addresses are handled + transparently, that is data will be send directly when required + by the proxy settings. + <p> + <b>NOTE:</b><br> + Unlike other SOCKS Sockets, it <b>does not</b> support proxy chaining, + and will throw an exception if proxy has a chain proxy attached. The + reason for that is not my laziness, but rather the restrictions of + the SOCKSv5 protocol. Basicaly SOCKSv5 proxy server, needs to know from + which host:port datagrams will be send for association, and returns address + to which datagrams should be send by the client, but it does not + inform client from which host:port it is going to send datagrams, in fact + there is even no guarantee they will be send at all and from the same address + each time. + + */ +public class Socks5DatagramSocket extends DatagramSocket{ + + InetAddress relayIP; + int relayPort; + Socks5Proxy proxy; + private boolean server_mode = false; + UDPEncapsulation encapsulation; + + + /** + Construct Datagram socket for communication over SOCKS5 proxy + server. This constructor uses default proxy, the one set with + Proxy.setDefaultProxy() method. If default proxy is not set or + it is set to version4 proxy, which does not support datagram + forwarding, throws SocksException. + + */ + public Socks5DatagramSocket() throws SocksException, + IOException{ + this(Proxy.defaultProxy,0,null); + } + /** + Construct Datagram socket for communication over SOCKS5 proxy + server. And binds it to the specified local port. + This constructor uses default proxy, the one set with + Proxy.setDefaultProxy() method. If default proxy is not set or + it is set to version4 proxy, which does not support datagram + forwarding, throws SocksException. + */ + public Socks5DatagramSocket(int port) throws SocksException, + IOException{ + this(Proxy.defaultProxy,port,null); + } + /** + Construct Datagram socket for communication over SOCKS5 proxy + server. And binds it to the specified local port and address. + This constructor uses default proxy, the one set with + Proxy.setDefaultProxy() method. If default proxy is not set or + it is set to version4 proxy, which does not support datagram + forwarding, throws SocksException. + */ + public Socks5DatagramSocket(int port,InetAddress ip) throws SocksException, + IOException{ + this(Proxy.defaultProxy,port,ip); + } + + /** + Constructs datagram socket for communication over specified proxy. + And binds it to the given local address and port. Address of null + and port of 0, signify any availabale port/address. + Might throw SocksException, if: + <ol> + <li> Given version of proxy does not support UDP_ASSOCIATE. + <li> Proxy can't be reached. + <li> Authorization fails. + <li> Proxy does not want to perform udp forwarding, for any reason. + </ol> + Might throw IOException if binding dtagram socket to given address/port + fails. + See java.net.DatagramSocket for more details. + */ + public Socks5DatagramSocket(Proxy p,int port,InetAddress ip) + throws SocksException, + IOException{ + super(port,ip); + if(p == null) throw new SocksException(Proxy.SOCKS_NO_PROXY); + if(!(p instanceof Socks5Proxy)) + throw new SocksException(-1,"Datagram Socket needs Proxy version 5"); + + if(p.chainProxy != null) + throw new SocksException(Proxy.SOCKS_JUST_ERROR, + "Datagram Sockets do not support proxy chaining."); + + proxy =(Socks5Proxy) p.copy(); + + ProxyMessage msg = proxy.udpAssociate(super.getLocalAddress(), + super.getLocalPort()); + relayIP = msg.ip; + if(relayIP.getHostAddress().equals("0.0.0.0")) relayIP = proxy.proxyIP; + relayPort = msg.port; + + encapsulation = proxy.udp_encapsulation; + + //debug("Datagram Socket:"+getLocalAddress()+":"+getLocalPort()+"\n"); + //debug("Socks5Datagram: "+relayIP+":"+relayPort+"\n"); + } + + /** + Used by UDPRelayServer. + */ + Socks5DatagramSocket(boolean server_mode,UDPEncapsulation encapsulation, + InetAddress relayIP,int relayPort) + throws IOException{ + super(); + this.server_mode = server_mode; + this.relayIP = relayIP; + this.relayPort = relayPort; + this.encapsulation = encapsulation; + this.proxy = null; + } + + /** + Sends the Datagram either through the proxy or directly depending + on current proxy settings and destination address. <BR> + + <B> NOTE: </B> DatagramPacket size should be at least 10 bytes less + than the systems limit. + + <P> + See documentation on java.net.DatagramSocket + for full details on how to use this method. + @param dp Datagram to send. + @throws IOException If error happens with I/O. + */ + public void send(DatagramPacket dp) throws IOException{ + //If the host should be accessed directly, send it as is. + if(!server_mode){ + super.send(dp); + //debug("Sending directly:"); + return; + } + + byte[] head = formHeader(dp.getAddress(),dp.getPort()); + byte[] buf = new byte[head.length + dp.getLength()]; + byte[] data = dp.getData(); + //Merge head and data + System.arraycopy(head,0,buf,0,head.length); + //System.arraycopy(data,dp.getOffset(),buf,head.length,dp.getLength()); + System.arraycopy(data,0,buf,head.length,dp.getLength()); + + if(encapsulation != null) + buf = encapsulation.udpEncapsulate(buf,true); + + super.send(new DatagramPacket(buf,buf.length,relayIP,relayPort)); + } + /** + This method allows to send datagram packets with address type DOMAINNAME. + SOCKS5 allows to specify host as names rather than ip addresses.Using + this method one can send udp datagrams through the proxy, without having + to know the ip address of the destination host. + <p> + If proxy specified for that socket has an option resolveAddrLocally set + to true host will be resolved, and the datagram will be send with address + type IPV4, if resolve fails, UnknownHostException is thrown. + @param dp Datagram to send, it should contain valid port and data + @param host Host name to which datagram should be send. + @throws IOException If error happens with I/O, or the host can't be + resolved when proxy settings say that hosts should be resolved locally. + @see Socks5Proxy#resolveAddrLocally(boolean) + */ + public void send(DatagramPacket dp, String host) throws IOException { + dp.setAddress(InetAddress.getByName(host)); + super.send(dp); + } + + /** + * Receives udp packet. If packet have arrived from the proxy relay server, + * it is processed and address and port of the packet are set to the + * address and port of sending host.<BR> + * If the packet arrived from anywhere else it is not changed.<br> + * <B> NOTE: </B> DatagramPacket size should be at least 10 bytes bigger + * than the largest packet you expect (this is for IPV4 addresses). + * For hostnames and IPV6 it is even more. + @param dp Datagram in which all relevent information will be copied. + */ + public void receive(DatagramPacket dp) throws IOException{ + super.receive(dp); + + if(server_mode){ + //Drop all datagrams not from relayIP/relayPort + int init_length = dp.getLength(); + int initTimeout = getSoTimeout(); + long startTime = System.currentTimeMillis(); + + while(!relayIP.equals(dp.getAddress()) || + relayPort != dp.getPort()){ + + //Restore datagram size + dp.setLength(init_length); + + //If there is a non-infinit timeout on this socket + //Make sure that it happens no matter how often unexpected + //packets arrive. + if(initTimeout != 0){ + int newTimeout = initTimeout - (int)(System.currentTimeMillis() - + startTime); + if(newTimeout <= 0) throw new InterruptedIOException( + "In Socks5DatagramSocket->receive()"); + setSoTimeout(newTimeout); + } + + super.receive(dp); + } + + //Restore timeout settings + if(initTimeout != 0) setSoTimeout(initTimeout); + + }else if(!relayIP.equals(dp.getAddress()) || + relayPort != dp.getPort()) + return; // Recieved direct packet + //If the datagram is not from the relay server, return it it as is. + + byte[] data; + data = dp.getData(); + + if(encapsulation != null) + data = encapsulation.udpEncapsulate(data,false); + + int offset = 0; //Java 1.1 + //int offset = dp.getOffset(); //Java 1.2 + + ByteArrayInputStream bIn = new ByteArrayInputStream(data,offset, + dp.getLength()); + + + ProxyMessage msg = new Socks5Message(bIn); + dp.setPort(msg.port); + dp.setAddress(msg.getInetAddress()); + + //what wasn't read by the Message is the data + int data_length = bIn.available(); + //Shift data to the left + System.arraycopy(data,offset+dp.getLength()-data_length, + data,offset,data_length); + + + dp.setLength(data_length); + } + + /** + * Returns port assigned by the proxy, to which datagrams are relayed. + * It is not the same port to which other party should send datagrams. + @return Port assigned by socks server to which datagrams are send + for association. + */ + public int getLocalPort(){ + if(server_mode) return super.getLocalPort(); + return relayPort; + } + /** + * Address assigned by the proxy, to which datagrams are send for relay. + * It is not necesseraly the same address, to which other party should send + * datagrams. + @return Address to which datagrams are send for association. + */ + public InetAddress getLocalAddress(){ + if(server_mode) return super.getLocalAddress(); + return relayIP; + } + + /** + * Closes datagram socket, and proxy connection. + */ + public void close(){ + if(!server_mode) proxy.endSession(); + super.close(); + } + + /** + This method checks wether proxy still runs udp forwarding service + for this socket. + <p> + This methods checks wether the primary connection to proxy server + is active. If it is, chances are that proxy continues to forward + datagrams being send from this socket. If it was closed, most likely + datagrams are no longer being forwarded by the server. + <p> + Proxy might decide to stop forwarding datagrams, in which case it + should close primary connection. This method allows to check, wether + this have been done. + <p> + You can specify timeout for which we should be checking EOF condition + on the primary connection. Timeout is in milliseconds. Specifying 0 as + timeout implies infinity, in which case method will block, until + connection to proxy is closed or an error happens, and then return false. + <p> + One possible scenario is to call isProxyactive(0) in separate thread, + and once it returned notify other threads about this event. + + @param timeout For how long this method should block, before returning. + @return true if connection to proxy is active, false if eof or error + condition have been encountered on the connection. + */ + public boolean isProxyAlive(int timeout){ + if(server_mode) return false; + if(proxy != null){ + try{ + proxy.proxySocket.setSoTimeout(timeout); + + int eof = proxy.in.read(); + if(eof < 0) return false; // EOF encountered. + else return true; // This really should not happen + + }catch(InterruptedIOException iioe){ + return true; // read timed out. + }catch(IOException ioe){ + return false; + } + } + return false; + } + +//PRIVATE METHODS +////////////////// + + + private byte[] formHeader(InetAddress ip, int port){ + Socks5Message request = new Socks5Message(0,ip,port); + request.data[0] = 0; + return request.data; + } + + +/*====================================================================== + +//Mainly Test functions +////////////////////// + + private String bytes2String(byte[] b){ + String s=""; + char[] hex_digit = { '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F'}; + for(int i=0;i<b.length;++i){ + int i1 = (b[i] & 0xF0) >> 4; + int i2 = b[i] & 0xF; + s+=hex_digit[i1]; + s+=hex_digit[i2]; + s+=" "; + } + return s; + } + private static final void debug(String s){ + if(DEBUG) + System.out.print(s); + } + + private static final boolean DEBUG = true; + + + public static void usage(){ + System.err.print( + "Usage: java Socks.SocksDatagramSocket host port [socksHost socksPort]\n"); + } + + static final int defaultProxyPort = 1080; //Default Port + static final String defaultProxyHost = "www-proxy"; //Default proxy + + public static void main(String args[]){ + int port; + String host; + int proxyPort; + String proxyHost; + InetAddress ip; + + if(args.length > 1 && args.length < 5){ + try{ + + host = args[0]; + port = Integer.parseInt(args[1]); + + proxyPort =(args.length > 3)? Integer.parseInt(args[3]) + : defaultProxyPort; + + host = args[0]; + ip = InetAddress.getByName(host); + + proxyHost =(args.length > 2)? args[2] + : defaultProxyHost; + + Proxy.setDefaultProxy(proxyHost,proxyPort); + Proxy p = Proxy.getDefaultProxy(); + p.addDirect("lux"); + + + DatagramSocket ds = new Socks5DatagramSocket(); + + + BufferedReader in = new BufferedReader( + new InputStreamReader(System.in)); + String s; + + System.out.print("Enter line:"); + s = in.readLine(); + + while(s != null){ + byte[] data = (s+"\r\n").getBytes(); + DatagramPacket dp = new DatagramPacket(data,0,data.length, + ip,port); + System.out.println("Sending to: "+ip+":"+port); + ds.send(dp); + dp = new DatagramPacket(new byte[1024],1024); + + System.out.println("Trying to recieve on port:"+ + ds.getLocalPort()); + ds.receive(dp); + System.out.print("Recieved:\n"+ + "From:"+dp.getAddress()+":"+dp.getPort()+ + "\n\n"+ + new String(dp.getData(),dp.getOffset(),dp.getLength())+"\n" + ); + System.out.print("Enter line:"); + s = in.readLine(); + + } + ds.close(); + System.exit(1); + + }catch(SocksException s_ex){ + System.err.println("SocksException:"+s_ex); + s_ex.printStackTrace(); + System.exit(1); + }catch(IOException io_ex){ + io_ex.printStackTrace(); + System.exit(1); + }catch(NumberFormatException num_ex){ + usage(); + num_ex.printStackTrace(); + System.exit(1); + } + + }else{ + usage(); + } + } +*/ + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/Socks5Message.java b/app/src/main/java/net/sourceforge/jsocks/Socks5Message.java new file mode 100644 index 0000000..ea2a321 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/Socks5Message.java @@ -0,0 +1,292 @@ +package net.sourceforge.jsocks; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + SOCKS5 request/response message. +*/ + +public class Socks5Message extends ProxyMessage{ + /** Address type of given message*/ + public int addrType; + + byte[] data; + + /** + Server error response. + @param cmd Error code. + */ + public Socks5Message(int cmd){ + super(cmd,null,0); + data = new byte[3]; + data[0] = SOCKS_VERSION; //Version. + data[1] = (byte)cmd; //Reply code for some kind of failure. + data[2] = 0; //Reserved byte. + } + + /** + Construct client request or server response. + @param cmd - Request/Response code. + @param ip - IP field. + @paarm port - port field. + */ + public Socks5Message(int cmd,InetAddress ip,int port){ + super(cmd,ip,port); + this.host = ip==null?"0.0.0.0":ip.getHostName(); + this.version = SOCKS_VERSION; + + byte[] addr; + + if(ip == null){ + addr = new byte[4]; + addr[0]=addr[1]=addr[2]=addr[3]=0; + }else + addr = ip.getAddress(); + + addrType = addr.length == 4 ? SOCKS_ATYP_IPV4 + : SOCKS_ATYP_IPV6; + + data = new byte[6+addr.length]; + data[0] = (byte) SOCKS_VERSION; //Version + data[1] = (byte) command; //Command + data[2] = (byte) 0; //Reserved byte + data[3] = (byte) addrType; //Address type + + //Put Address + System.arraycopy(addr,0,data,4,addr.length); + //Put port + data[data.length-2] = (byte)(port>>8); + data[data.length-1] = (byte)(port); + } + + + /** + Construct client request or server response. + @param cmd - Request/Response code. + @param hostName - IP field as hostName, uses ADDR_TYPE of HOSTNAME. + @paarm port - port field. + */ + public Socks5Message(int cmd,String hostName,int port){ + super(cmd,null,port); + this.host = hostName; + this.version = SOCKS_VERSION; + + //System.out.println("Doing ATYP_DOMAINNAME"); + + addrType = SOCKS_ATYP_DOMAINNAME; + byte addr[] = hostName.getBytes(); + + data =new byte[7+addr.length]; + data[0] = (byte) SOCKS_VERSION; //Version + data[1] = (byte) command; //Command + data[2] = (byte) 0; //Reserved byte + data[3] = (byte) SOCKS_ATYP_DOMAINNAME; //Address type + data[4] = (byte) addr.length; //Length of the address + + //Put Address + System.arraycopy(addr,0,data,5,addr.length); + //Put port + data[data.length-2] = (byte)(port >>8); + data[data.length-1] = (byte)(port); + } + + /** + Initialises Message from the stream. Reads server response from + given stream. + @param in Input stream to read response from. + @throws SocksException If server response code is not SOCKS_SUCCESS(0), or + if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public Socks5Message(InputStream in) throws SocksException, + IOException{ + this(in,true); + } + + /** + Initialises Message from the stream. Reads server response or client + request from given stream. + + @param in Input stream to read response from. + @param clinetMode If true read server response, else read client request. + @throws SocksException If server response code is not SOCKS_SUCCESS(0) and + reading in client mode, or if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public Socks5Message(InputStream in,boolean clientMode)throws SocksException, + IOException{ + read(in,clientMode); + } + + + /** + Initialises Message from the stream. Reads server response from + given stream. + @param in Input stream to read response from. + @throws SocksException If server response code is not SOCKS_SUCCESS(0), or + if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public void read(InputStream in) throws SocksException, + IOException{ + read(in,true); + } + + + /** + Initialises Message from the stream. Reads server response or client + request from given stream. + + @param in Input stream to read response from. + @param clinetMode If true read server response, else read client request. + @throws SocksException If server response code is not SOCKS_SUCCESS(0) and + reading in client mode, or if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public void read(InputStream in,boolean clientMode) throws SocksException, + IOException{ + data = null; + ip = null; + + DataInputStream di = new DataInputStream(in); + + version = di.readUnsignedByte(); + command = di.readUnsignedByte(); + if(clientMode && command != 0) + throw new SocksException(command); + + @SuppressWarnings("unused") + int reserved = di.readUnsignedByte(); + addrType = di.readUnsignedByte(); + + byte addr[]; + + switch(addrType){ + case SOCKS_ATYP_IPV4: + addr = new byte[4]; + di.readFully(addr); + host = bytes2IPV4(addr,0); + break; + case SOCKS_ATYP_IPV6: + addr = new byte[SOCKS_IPV6_LENGTH];//I believe it is 16 bytes,huge! + di.readFully(addr); + host = bytes2IPV6(addr,0); + break; + case SOCKS_ATYP_DOMAINNAME: + //System.out.println("Reading ATYP_DOMAINNAME"); + addr = new byte[di.readUnsignedByte()];//Next byte shows the length + di.readFully(addr); + host = new String(addr); + break; + default: + throw(new SocksException(Proxy.SOCKS_JUST_ERROR)); + } + + port = di.readUnsignedShort(); + + if(addrType != SOCKS_ATYP_DOMAINNAME && doResolveIP){ + try{ + ip = InetAddress.getByName(host); + }catch(UnknownHostException uh_ex){ + } + } + } + + /** + Writes the message to the stream. + @param out Output stream to which message should be written. + */ + public void write(OutputStream out)throws SocksException, + IOException{ + if(data == null){ + Socks5Message msg; + + if(addrType == SOCKS_ATYP_DOMAINNAME) + msg = new Socks5Message(command,host,port); + else{ + if(ip == null){ + try{ + ip = InetAddress.getByName(host); + }catch(UnknownHostException uh_ex){ + throw new SocksException(Proxy.SOCKS_JUST_ERROR); + } + } + msg = new Socks5Message(command,ip,port); + } + data = msg.data; + } + out.write(data); + } + + /** + Returns IP field of the message as IP, if the message was created + with ATYP of HOSTNAME, it will attempt to resolve the hostname, + which might fail. + @throws UnknownHostException if host can't be resolved. + */ + public InetAddress getInetAddress() throws UnknownHostException{ + if(ip!=null) return ip; + + return (ip=InetAddress.getByName(host)); + } + + /** + Returns string representation of the message. + */ + public String toString(){ + String s= + "Socks5Message:"+"\n"+ + "VN "+version+"\n"+ + "CMD "+command+"\n"+ + "ATYP "+addrType+"\n"+ + "ADDR "+host+"\n"+ + "PORT "+port+"\n"; + return s; + } + + + /** + *Wether to resolve hostIP returned from SOCKS server + *that is wether to create InetAddress object from the + *hostName string + */ + static public boolean resolveIP(){ return doResolveIP;} + + /** + *Wether to resolve hostIP returned from SOCKS server + *that is wether to create InetAddress object from the + *hostName string + *@param doResolve Wether to resolve hostIP from SOCKS server. + *@return Previous value. + */ + static public boolean resolveIP(boolean doResolve){ + boolean old = doResolveIP; + doResolveIP = doResolve; + return old; + } + + /* + private static final void debug(String s){ + if(DEBUG) + System.out.print(s); + } + private static final boolean DEBUG = false; + */ + + //SOCKS5 constants + public static final int SOCKS_VERSION =5; + + public static final int SOCKS_ATYP_IPV4 =0x1; //Where is 2?? + public static final int SOCKS_ATYP_DOMAINNAME =0x3; //!!!!rfc1928 + public static final int SOCKS_ATYP_IPV6 =0x4; + + public static final int SOCKS_IPV6_LENGTH =16; + + static boolean doResolveIP = true; + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/Socks5Proxy.java b/app/src/main/java/net/sourceforge/jsocks/Socks5Proxy.java new file mode 100644 index 0000000..aa9c643 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/Socks5Proxy.java @@ -0,0 +1,231 @@ +package net.sourceforge.jsocks; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.Hashtable; + +/** + SOCKS5 Proxy. +*/ + +public class Socks5Proxy extends Proxy implements Cloneable{ + +//Data members + private Hashtable<Integer, Authentication> authMethods = new Hashtable<Integer, Authentication>(); + private int selectedMethod; + + boolean resolveAddrLocally = true; + UDPEncapsulation udp_encapsulation=null; + + +//Public Constructors +//==================== + + /** + Creates SOCKS5 proxy. + @param proxyHost Host on which a Proxy server runs. + @param proxyPort Port on which a Proxy server listens for connections. + @throws UnknownHostException If proxyHost can't be resolved. + */ + public Socks5Proxy(String proxyHost,int proxyPort) + throws UnknownHostException{ + super(proxyHost,proxyPort); + version = 5; + setAuthenticationMethod(0,new AuthenticationNone()); + } + + + /** + Creates SOCKS5 proxy. + @param proxyIP Host on which a Proxy server runs. + @param proxyPort Port on which a Proxy server listens for connections. + */ + public Socks5Proxy(InetAddress proxyIP,int proxyPort){ + super(proxyIP,proxyPort); + version = 5; + setAuthenticationMethod(0,new AuthenticationNone()); + } + + +//Public instance methods +//======================== + + + /** + * Wether to resolve address locally or to let proxy do so. + <p> + SOCKS5 protocol allows to send host names rather then IPs in the + requests, this option controls wether the hostnames should be send + to the proxy server as names, or should they be resolved locally. + @param doResolve Wether to perform resolution locally. + @return Previous settings. + */ + public boolean resolveAddrLocally(boolean doResolve){ + boolean old = resolveAddrLocally; + resolveAddrLocally = doResolve; + return old; + } + /** + Get current setting on how the addresses should be handled. + @return Current setting for address resolution. + @see Socks5Proxy#resolveAddrLocally(boolean doResolve) + */ + public boolean resolveAddrLocally(){ + return resolveAddrLocally; + } + + /** + Adds another authentication method. + @param methodId Authentication method id, see rfc1928 + @param method Implementation of Authentication + @see Authentication + */ + public boolean setAuthenticationMethod(int methodId, + Authentication method){ + if(methodId<0 || methodId > 255) + return false; + if(method == null){ + //Want to remove a particular method + return (authMethods.remove(new Integer(methodId)) != null); + }else{//Add the method, or rewrite old one + authMethods.put(new Integer(methodId),method); + } + return true; + } + + /** + Get authentication method, which corresponds to given method id + @param methodId Authentication method id. + @return Implementation for given method or null, if one was not set. + */ + public Authentication getAuthenticationMethod(int methodId){ + Object method = authMethods.get(new Integer(methodId)); + if(method == null) return null; + return (Authentication)method; + } + + /** + Creates a clone of this Proxy. + */ + @SuppressWarnings("unchecked") +public Object clone(){ + Socks5Proxy newProxy = new Socks5Proxy(proxyIP,proxyPort); + newProxy.authMethods = (Hashtable<Integer, Authentication>) this.authMethods.clone(); + newProxy.resolveAddrLocally = resolveAddrLocally; + newProxy.chainProxy = chainProxy; + return newProxy; + } + +//Public Static(Class) Methods +//============================== + + +//Protected Methods +//================= + + protected Proxy copy(){ + Socks5Proxy copy = new Socks5Proxy(proxyIP,proxyPort); + copy.authMethods = this.authMethods; //same Hash, no copy + copy.chainProxy = this.chainProxy; + copy.resolveAddrLocally = this.resolveAddrLocally; + return copy; + } + /** + * + * + */ + protected void startSession()throws SocksException{ + super.startSession(); + Authentication auth; + Socket ps = proxySocket; //The name is too long + + try{ + + byte nMethods = (byte) authMethods.size(); //Number of methods + + byte[] buf = new byte[2+nMethods]; //2 is for VER,NMETHODS + buf[0] = (byte) version; + buf[1] = nMethods; //Number of methods + int i=2; + + Enumeration<Integer> ids = authMethods.keys(); + while(ids.hasMoreElements()) + buf[i++] = (byte)((Integer)ids.nextElement()).intValue(); + + out.write(buf); + out.flush(); + + int versionNumber = in.read(); + selectedMethod = in.read(); + + if(versionNumber < 0 || selectedMethod < 0){ + //EOF condition was reached + endSession(); + throw(new SocksException(SOCKS_PROXY_IO_ERROR, + "Connection to proxy lost.")); + } + if(versionNumber < version){ + //What should we do?? + } + if(selectedMethod == 0xFF){ //No method selected + ps.close(); + throw ( new SocksException(SOCKS_AUTH_NOT_SUPPORTED)); + } + + auth = getAuthenticationMethod(selectedMethod); + if(auth == null){ + //This shouldn't happen, unless method was removed by other + //thread, or the server stuffed up + throw(new SocksException(SOCKS_JUST_ERROR, + "Speciefied Authentication not found!")); + } + Object[] in_out = auth.doSocksAuthentication(selectedMethod,ps); + if(in_out == null){ + //Authentication failed by some reason + throw(new SocksException(SOCKS_AUTH_FAILURE)); + } + //Most authentication methods are expected to return + //simply the input/output streams associated with + //the socket. However if the auth. method requires + //some kind of encryption/decryption being done on the + //connection it should provide classes to handle I/O. + + in = (InputStream) in_out[0]; + out = (OutputStream) in_out[1]; + if(in_out.length > 2) + udp_encapsulation = (UDPEncapsulation) in_out[2]; + + }catch(SocksException s_ex){ + throw s_ex; + }catch(UnknownHostException uh_ex){ + throw(new SocksException(SOCKS_PROXY_NO_CONNECT)); + }catch(SocketException so_ex){ + throw(new SocksException(SOCKS_PROXY_NO_CONNECT)); + }catch(IOException io_ex){ + //System.err.println(io_ex); + throw(new SocksException(SOCKS_PROXY_IO_ERROR,""+io_ex)); + } + } + + protected ProxyMessage formMessage(int cmd,InetAddress ip,int port){ + return new Socks5Message(cmd,ip,port); + } + protected ProxyMessage formMessage(int cmd,String host,int port) + throws UnknownHostException{ + if(resolveAddrLocally) + return formMessage(cmd,InetAddress.getByName(host),port); + else + return new Socks5Message(cmd,host,port); + } + protected ProxyMessage formMessage(InputStream in) + throws SocksException, + IOException{ + return new Socks5Message(in); + } + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/SocksException.java b/app/src/main/java/net/sourceforge/jsocks/SocksException.java new file mode 100644 index 0000000..764587f --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/SocksException.java @@ -0,0 +1,80 @@ +package net.sourceforge.jsocks; + +/** + Exception thrown by various socks classes to indicate errors + with protocol or unsuccessful server responses. +*/ +public class SocksException extends java.io.IOException{ + private static final long serialVersionUID = 6141184566248512277L; + + /** + Construct a SocksException with given error code. + <p> + Tries to look up message which corresponds to this error code. + @param errCode Error code for this exception. + */ + public SocksException(int errCode){ + this.errCode = errCode; + if((errCode >> 16) == 0){ + //Server reply error message + errString = errCode <= serverReplyMessage.length ? + serverReplyMessage[errCode] : + UNASSIGNED_ERROR_MESSAGE; + }else{ + //Local error + errCode = (errCode >> 16) -1; + errString = errCode <= localErrorMessage.length ? + localErrorMessage[errCode] : + UNASSIGNED_ERROR_MESSAGE; + } + } + /** + Constructs a SocksException with given error code and message. + @param errCode Error code. + @param errString Error Message. + */ + public SocksException(int errCode,String errString){ + this.errCode = errCode; + this.errString = errString; + } + /** + Get the error code associated with this exception. + @return Error code associated with this exception. + */ + public int getErrorCode(){ + return errCode; + } + /** + Get human readable representation of this exception. + @return String represntation of this exception. + */ + public String toString(){ + return errString; + } + + static final String UNASSIGNED_ERROR_MESSAGE = + "Unknown error message"; + static final String serverReplyMessage[] = { + "Succeeded", + "General SOCKS server failure", + "Connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported" }; + + static final String localErrorMessage[] ={ + "SOCKS server not specified", + "Unable to contact SOCKS server", + "IO error", + "None of Authentication methods are supported", + "Authentication failed", + "General SOCKS fault" }; + + String errString; + public int errCode; + +}//End of SocksException class + diff --git a/app/src/main/java/net/sourceforge/jsocks/SocksServerSocket.java b/app/src/main/java/net/sourceforge/jsocks/SocksServerSocket.java new file mode 100644 index 0000000..179e9c4 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/SocksServerSocket.java @@ -0,0 +1,164 @@ +package net.sourceforge.jsocks; + +import java.net.*; +import java.io.*; + +/** + SocksServerSocket allows to accept connections from one particular + host through the SOCKS4 or SOCKS5 proxy. +*/ +public class SocksServerSocket extends ServerSocket{ + //Data members + protected Proxy proxy; + protected String localHost; + protected InetAddress localIP; + protected int localPort; + + boolean doing_direct = false; + InetAddress remoteAddr; + + /** + *Creates ServerSocket capable of accepting one connection + *through the firewall, uses given proxy. + *@param host Host from which the connection should be recieved. + *@param port Port number of the primary connection. + */ + public SocksServerSocket(String host, int port) throws SocksException, + UnknownHostException, IOException { + + super(0); + remoteAddr = InetAddress.getByName(host); + doDirect(); + } + + /** + * Creates ServerSocket capable of accepting one connection + * through the firewall, uses default Proxy. + *@param ip Host from which the connection should be recieved. + *@param port Port number of the primary connection. + */ + public SocksServerSocket(InetAddress ip, int port) throws SocksException, + IOException{ + this(Proxy.defaultProxy,ip,port); + } + + /** + *Creates ServerSocket capable of accepting one connection + *through the firewall, uses given proxy. + *@param ip Host from which the connection should be recieved. + *@param port Port number of the primary connection. + */ + public SocksServerSocket(Proxy p, InetAddress ip, int port) + throws SocksException, IOException { + super(0); + + remoteAddr = ip; + doDirect(); + } + + + /** + * Accepts the incoming connection. + */ + public Socket accept() throws IOException{ + Socket s; + + if(!doing_direct){ + if(proxy == null) return null; + + ProxyMessage msg = proxy.accept(); + s = msg.ip == null? new SocksSocket(msg.host,msg.port,proxy) + : new SocksSocket(msg.ip,msg.port,proxy); + //Set timeout back to 0 + proxy.proxySocket.setSoTimeout(0); + }else{ //Direct Connection + + //Mimic the proxy behaviour, + //only accept connections from the speciefed host. + while(true){ + s = super.accept(); + if(s.getInetAddress().equals(remoteAddr)){ + //got the connection from the right host + //Close listenning socket. + break; + }else + s.close(); //Drop all connections from other hosts + } + + } + proxy = null; + //Return accepted socket + return s; + } + + /** + * Closes the connection to proxy if socket have not been accepted, if + * the direct connection is used, closes direct ServerSocket. If the + * client socket have been allready accepted, does nothing. + */ + public void close() throws IOException{ + super.close(); + if(proxy != null) proxy.endSession(); + proxy = null; + } + + /** + Get the name of the host proxy is using to listen for incoming + connection. + <P> + Usefull when address is returned by proxy as the hostname. + @return the hostname of the address proxy is using to listen + for incoming connection. + */ + public String getHost(){ + return localHost; + } + + /** + * Get address assigned by proxy to listen for incomming + * connections, or the local machine address if doing direct + * connection. + */ + public InetAddress getInetAddress(){ + if(localIP == null){ + try{ + localIP = InetAddress.getByName(localHost); + }catch(UnknownHostException e){ + return null; + } + } + return localIP; + } + + /** + * Get port assigned by proxy to listen for incoming connections, or + the port chosen by local system, if accepting directly. + */ + public int getLocalPort(){ + return localPort; + } + + /** + Set Timeout. + + @param timeout Amount of time in milliseconds, accept should wait for + incoming connection before failing with exception. + Zero timeout implies infinity. + */ + public void setSoTimeout(int timeout) throws SocketException{ + super.setSoTimeout(timeout); + if(!doing_direct) proxy.proxySocket.setSoTimeout(timeout); + } + + +//Private Methods +////////////////// + + private void doDirect(){ + doing_direct = true; + localPort = super.getLocalPort(); + localIP = super.getInetAddress(); + localHost = localIP.getHostName(); + } + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/SocksSocket.java b/app/src/main/java/net/sourceforge/jsocks/SocksSocket.java new file mode 100644 index 0000000..cf9ff65 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/SocksSocket.java @@ -0,0 +1,291 @@ +package net.sourceforge.jsocks; + +import java.net.*; +import java.io.*; + +/** + * SocksSocket tryies to look very similar to normal Socket, + * while allowing connections through the SOCKS4 or 5 proxy. + * To use this class you will have to identify proxy you need + * to use, Proxy class allows you to set default proxy, which + * will be used by all Socks aware sockets. You can also create + * either Socks4Proxy or Socks5Proxy, and use them by passing to the + * appropriate constructors. + * <P> + * Using Socks package can be as easy as that: + * + * <pre><tt> + * + * import Socks.*; + * .... + * + * try{ + * //Specify SOCKS5 proxy + * Proxy.setDefaultProxy("socks-proxy",1080); + * + * //OR you still use SOCKS4 + * //Code below uses SOCKS4 proxy + * //Proxy.setDefaultProxy("socks-proxy",1080,userName); + * + * Socket s = SocksSocket("some.host.of.mine",13); + * readTimeFromSock(s); + * }catch(SocksException sock_ex){ + * //Usually it will turn in more or less meaningfull message + * System.err.println("SocksException:"+sock_ex); + * } + * + * </tt></pre> + *<P> + * However if the need exist for more control, like resolving addresses + * remotely, or using some non-trivial authentication schemes, it can be done. + */ + +public class SocksSocket extends Socket{ + //Data members + protected Proxy proxy; + protected String localHost, remoteHost; + protected InetAddress localIP, remoteIP; + protected int localPort,remotePort; + + private Socket directSock = null; + + /** + * Tryies to connect to given host and port + * using default proxy. If no default proxy speciefied + * it throws SocksException with error code SOCKS_NO_PROXY. + @param host Machine to connect to. + @param port Port to which to connect. + * @see SocksSocket#SocksSocket(Proxy,String,int) + * @see Socks5Proxy#resolveAddrLocally + */ + public SocksSocket(String host,int port) + throws SocksException,UnknownHostException{ + this(Proxy.defaultProxy,host,port); + } + /** + * Connects to host port using given proxy server. + @param p Proxy to use. + @param host Machine to connect to. + @param port Port to which to connect. + @throws UnknownHostException + If one of the following happens: + <ol> + + <li> Proxy settings say that address should be resolved locally, but + this fails. + <li> Proxy settings say that the host should be contacted directly but + host name can't be resolved. + </ol> + @throws SocksException + If one of the following happens: + <ul> + <li> Proxy is is null. + <li> Proxy settings say that the host should be contacted directly but + this fails. + <li> Socks Server can't be contacted. + <li> Authentication fails. + <li> Connection is not allowed by the SOCKS proxy. + <li> SOCKS proxy can't establish the connection. + <li> Any IO error occured. + <li> Any protocol error occured. + </ul> + @throws IOexception if anything is wrong with I/O. + @see Socks5Proxy#resolveAddrLocally + */ + public SocksSocket(Proxy p, String host, int port) throws SocksException, + UnknownHostException { + remoteHost = host; + remotePort = port; + remoteIP = InetAddress.getByName(host); + doDirect(); + } + + /** + Connects to given ip and port using given Proxy server. + @param p Proxy to use. + @param ip Machine to connect to. + @param port Port to which to connect. + + */ + public SocksSocket(InetAddress ip, int port) throws SocksException{ + this.remoteIP = ip; + this.remotePort = port; + this.remoteHost = ip.getHostName(); + doDirect(); + } + + + /** + * These 2 constructors are used by the SocksServerSocket. + * This socket simply overrides remoteHost, remotePort + */ + protected SocksSocket(String host,int port,Proxy proxy){ + this.remotePort = port; + this.proxy = proxy; + this.localIP = proxy.proxySocket.getLocalAddress(); + this.localPort = proxy.proxySocket.getLocalPort(); + this.remoteHost = host; + } + protected SocksSocket(InetAddress ip,int port,Proxy proxy){ + remoteIP = ip; + remotePort = port; + this.proxy = proxy; + this.localIP = proxy.proxySocket.getLocalAddress(); + this.localPort = proxy.proxySocket.getLocalPort(); + remoteHost = remoteIP.getHostName(); + } + + /** + * Same as Socket + */ + public void close() throws IOException{ + if(proxy!= null)proxy.endSession(); + proxy = null; + } + /** + * Same as Socket + */ + public InputStream getInputStream(){ + return proxy.in; + } + /** + * Same as Socket + */ + public OutputStream getOutputStream(){ + return proxy.out; + } + /** + * Same as Socket + */ + public int getPort(){ + return remotePort; + } + /** + * Returns remote host name, it is usefull in cases when addresses + * are resolved by proxy, and we can't create InetAddress object. + @return The name of the host this socket is connected to. + */ + public String getHost(){ + return remoteHost; + } + /** + * Get remote host as InetAddress object, might return null if + * addresses are resolved by proxy, and it is not possible to resolve + * it locally + @return Ip address of the host this socket is connected to, or null + if address was returned by the proxy as DOMAINNAME and can't be + resolved locally. + */ + public InetAddress getInetAddress(){ + if(remoteIP == null){ + try{ + remoteIP = InetAddress.getByName(remoteHost); + }catch(UnknownHostException e){ + return null; + } + } + return remoteIP; + } + + /** + * Get the port assigned by the proxy for the socket, not + * the port on locall machine as in Socket. + @return Port of the socket used on the proxy server. + */ + public int getLocalPort(){ + return localPort; + } + + /** + * Get address assigned by proxy to make a remote connection, + * it might be different from the host specified for the proxy. + * Can return null if socks server returned this address as hostname + * and it can't be resolved locally, use getLocalHost() then. + @return Address proxy is using to make a connection. + */ + public InetAddress getLocalAddress(){ + if(localIP == null){ + try{ + localIP = InetAddress.getByName(localHost); + }catch(UnknownHostException e){ + return null; + } + } + return localIP; + } + /** + Get name of the host, proxy has assigned to make a remote connection + for this socket. This method is usefull when proxy have returned + address as hostname, and we can't resolve it on this machine. + @return The name of the host proxy is using to make a connection. + */ + public String getLocalHost(){ + return localHost; + } + + /** + Same as socket. + */ + public void setSoLinger(boolean on,int val) throws SocketException{ + proxy.proxySocket.setSoLinger(on,val); + } + /** + Same as socket. + */ + public int getSoLinger(int timeout) throws SocketException{ + return proxy.proxySocket.getSoLinger(); + } + /** + Same as socket. + */ + public void setSoTimeout(int timeout) throws SocketException{ + proxy.proxySocket.setSoTimeout(timeout); + } + /** + Same as socket. + */ + public int getSoTimeout(int timeout) throws SocketException{ + return proxy.proxySocket.getSoTimeout(); + } + /** + Same as socket. + */ + public void setTcpNoDelay(boolean on) throws SocketException{ + proxy.proxySocket.setTcpNoDelay(on); + } + /** + Same as socket. + */ + public boolean getTcpNoDelay() throws SocketException{ + return proxy.proxySocket.getTcpNoDelay(); + } + + /** + Get string representation of the socket. + */ + public String toString(){ + if(directSock!=null) return "Direct connection:"+directSock; + return ("Proxy:"+proxy+";"+"addr:"+remoteHost+",port:"+remotePort + +",localport:"+localPort); + + } + +//Private Methods +////////////////// + + private void doDirect()throws SocksException{ + try{ + //System.out.println("IP:"+remoteIP+":"+remotePort); + directSock = new Socket(remoteIP,remotePort); + proxy.out = directSock.getOutputStream(); + proxy.in = directSock.getInputStream(); + proxy.proxySocket = directSock; + localIP = directSock.getLocalAddress(); + localPort = directSock.getLocalPort(); + }catch(IOException io_ex){ + throw new SocksException(Proxy.SOCKS_DIRECT_FAILED, + "Direct connect failed:"+io_ex); + } + } + +} diff --git a/app/src/main/java/net/sourceforge/jsocks/UDPEncapsulation.java b/app/src/main/java/net/sourceforge/jsocks/UDPEncapsulation.java new file mode 100644 index 0000000..e965942 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/UDPEncapsulation.java @@ -0,0 +1,29 @@ +package net.sourceforge.jsocks; +/** + This interface provides for datagram encapsulation for SOCKSv5 protocol. + <p> + SOCKSv5 allows for datagrams to be encapsulated for purposes of integrity + and/or authenticity. How it should be done is aggreed during the + authentication stage, and is authentication dependent. This interface is + provided to allow this encapsulation. + @see Authentication +*/ +public interface UDPEncapsulation{ + + /** + This method should provide any authentication depended transformation + on datagrams being send from/to the client. + + @param data Datagram data (including any SOCKS related bytes), to be + encapsulated/decapsulated. + @param out Wether the data is being send out. If true method should + encapsulate/encrypt data, otherwise it should decapsulate/ + decrypt data. + @throw IOException if for some reason data can be transformed correctly. + @return Should return byte array containing data after transformation. + It is possible to return same array as input, if transformation + only involves bit mangling, and no additional data is being + added or removed. + */ + byte[] udpEncapsulate(byte[] data, boolean out) throws java.io.IOException; +} diff --git a/app/src/main/java/net/sourceforge/jsocks/UDPRelayServer.java b/app/src/main/java/net/sourceforge/jsocks/UDPRelayServer.java new file mode 100644 index 0000000..dfa6016 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/UDPRelayServer.java @@ -0,0 +1,212 @@ +package net.sourceforge.jsocks; +import net.sourceforge.jsocks.server.*; +import java.net.*; +import java.io.*; + +/** + UDP Relay server, used by ProxyServer to perform udp forwarding. +*/ +class UDPRelayServer implements Runnable{ + + + DatagramSocket client_sock; + DatagramSocket remote_sock; + + Socket controlConnection; + + int relayPort; + InetAddress relayIP; + + Thread pipe_thread1,pipe_thread2; + Thread master_thread; + + ServerAuthenticator auth; + + long lastReadTime; + + static PrintStream log = null; + static Proxy proxy = null; + static int datagramSize = 0xFFFF;//64K, a bit more than max udp size + static int iddleTimeout = 180000;//3 minutes + + + /** + Constructs UDP relay server to communicate with client + on given ip and port. + @param clientIP Address of the client from whom datagrams + will be recieved and to whom they will be forwarded. + @param clientPort Clients port. + @param master_thread Thread which will be interrupted, when + UDP relay server stoppes for some reason. + @param controlConnection Socket which will be closed, before + interrupting the master thread, it is introduced due to a bug + in windows JVM which does not throw InterruptedIOException in + threads which block in I/O operation. + */ + public UDPRelayServer(InetAddress clientIP,int clientPort, + Thread master_thread, + Socket controlConnection, + ServerAuthenticator auth) + throws IOException{ + this.master_thread = master_thread; + this.controlConnection = controlConnection; + this.auth = auth; + + client_sock = new Socks5DatagramSocket(true,auth.getUdpEncapsulation(), + clientIP,clientPort); + relayPort = client_sock.getLocalPort(); + relayIP = client_sock.getLocalAddress(); + + if(relayIP.getHostAddress().equals("0.0.0.0")) + relayIP = InetAddress.getLocalHost(); + + if(proxy == null) + remote_sock = new DatagramSocket(); + else + remote_sock = new Socks5DatagramSocket(proxy,0,null); + } + + +//Public methods +///////////////// + + + /** + Sets the timeout for UDPRelay server.<br> + Zero timeout implies infinity.<br> + Default timeout is 3 minutes. + */ + + static public void setTimeout(int timeout){ + iddleTimeout = timeout; + } + + + /** + Sets the size of the datagrams used in the UDPRelayServer.<br> + Default size is 64K, a bit more than maximum possible size of the + datagram. + */ + static public void setDatagramSize(int size){ + datagramSize = size; + } + + /** + Port to which client should send datagram for association. + */ + public int getRelayPort(){ + return relayPort; + } + /** + IP address to which client should send datagrams for association. + */ + public InetAddress getRelayIP(){ + return relayIP; + } + + /** + Starts udp relay server. + Spawns two threads of execution and returns. + */ + public void start() throws IOException{ + remote_sock.setSoTimeout(iddleTimeout); + client_sock.setSoTimeout(iddleTimeout); + + log("Starting UDP relay server on "+relayIP+":"+relayPort); + log("Remote socket "+remote_sock.getLocalAddress()+":"+ + remote_sock.getLocalPort()); + + pipe_thread1 = new Thread(this,"pipe1"); + pipe_thread2 = new Thread(this,"pipe2"); + + lastReadTime = System.currentTimeMillis(); + + pipe_thread1.start(); + pipe_thread2.start(); + } + + /** + Stops Relay server. + <p> + Does not close control connection, does not interrupt master_thread. + */ + public synchronized void stop(){ + master_thread = null; + controlConnection = null; + abort(); + } + +//Runnable interface +//////////////////// + public void run(){ + try{ + if(Thread.currentThread().getName().equals("pipe1")) + pipe(remote_sock,client_sock,false); + else + pipe(client_sock,remote_sock,true); + }catch(IOException ioe){ + }finally{ + abort(); + log("UDP Pipe thread "+Thread.currentThread().getName()+" stopped."); + } + + } + +//Private methods +///////////////// + private synchronized void abort(){ + if(pipe_thread1 == null) return; + + log("Aborting UDP Relay Server"); + + remote_sock.close(); + client_sock.close(); + + if(controlConnection != null) + try{ controlConnection.close();} catch(IOException ioe){} + + if(master_thread!=null) master_thread.interrupt(); + + pipe_thread1.interrupt(); + pipe_thread2.interrupt(); + + pipe_thread1 = null; + } + + + static private void log(String s){ + if(log != null){ + log.println(s); + log.flush(); + } + } + + private void pipe(DatagramSocket from,DatagramSocket to,boolean out) + throws IOException{ + byte[] data = new byte[datagramSize]; + DatagramPacket dp = new DatagramPacket(data,data.length); + + while(true){ + try{ + from.receive(dp); + lastReadTime = System.currentTimeMillis(); + + if(auth.checkRequest(dp,out)) + to.send(dp); + + }catch(UnknownHostException uhe){ + log("Dropping datagram for unknown host"); + }catch(InterruptedIOException iioe){ + //log("Interrupted: "+iioe); + //If we were interrupted by other thread. + if(iddleTimeout == 0) return; + + //If last datagram was received, long time ago, return. + long timeSinceRead = System.currentTimeMillis() - lastReadTime; + if(timeSinceRead >= iddleTimeout -100) //-100 for adjustment + return; + } + dp.setLength(data.length); + } + } +} diff --git a/app/src/main/java/net/sourceforge/jsocks/server/ServerAuthenticator.java b/app/src/main/java/net/sourceforge/jsocks/server/ServerAuthenticator.java new file mode 100644 index 0000000..cb7f0af --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/server/ServerAuthenticator.java @@ -0,0 +1,120 @@ +package net.sourceforge.jsocks.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.DatagramPacket; +import java.net.Socket; + +import net.sourceforge.jsocks.ProxyMessage; +import net.sourceforge.jsocks.UDPEncapsulation; + +/** + Classes implementing this interface should provide socks server with + authentication and authorization of users. +**/ +public interface ServerAuthenticator{ + + /** + This method is called when a new connection accepted by the server. + <p> + At this point no data have been extracted from the connection. It is + responsibility of this method to ensure that the next byte in the + stream after this method have been called is the first byte of the + socks request message. For SOCKSv4 there is no authentication data and + the first byte in the stream is part of the request. With SOCKSv5 however + there is an authentication data first. It is expected that implementaions + will process this authentication data. + <p> + If authentication was successful an instance of ServerAuthentication + should be returned, it later will be used by the server to perform + authorization and some other things. If authentication fails null should + be returned, or an exception may be thrown. + + @param s Accepted Socket. + @return An instance of ServerAuthenticator to be used for this connection + or null + */ + ServerAuthenticator startSession(Socket s) throws IOException; + + /** + This method should return input stream which should be used on the + accepted socket. + <p> + SOCKSv5 allows to have multiple authentication methods, and these methods + might require some kind of transformations being made on the data. + <p> + This method is called on the object returned from the startSession + function. + */ + InputStream getInputStream(); + /** + This method should return output stream to use to write to the accepted + socket. + <p> + SOCKSv5 allows to have multiple authentication methods, and these methods + might require some kind of transformations being made on the data. + <p> + This method is called on the object returned from the startSession + function. + */ + OutputStream getOutputStream(); + + /** + This method should return UDPEncapsulation, which should be used + on the datagrams being send in/out. + <p> + If no transformation should be done on the datagrams, this method + should return null. + <p> + This method is called on the object returned from the startSession + function. + */ + + UDPEncapsulation getUdpEncapsulation(); + + /** + This method is called when a request have been read. + <p> + Implementation should decide wether to grant request or not. Returning + true implies granting the request, false means request should be rejected. + <p> + This method is called on the object returned from the startSession + function. + @param msg Request message. + @return true to grant request, false to reject it. + */ + boolean checkRequest(ProxyMessage msg); + + /** + This method is called when datagram is received by the server. + <p> + Implementaions should decide wether it should be forwarded or dropped. + It is expecteed that implementation will use datagram address and port + information to make a decision, as well as anything else. Address and + port of the datagram are always correspond to remote machine. It is + either destination or source address. If out is true address is destination + address, else it is a source address, address of the machine from which + datagram have been received for the client. + <p> + Implementaions should return true if the datagram is to be forwarded, and + false if the datagram should be dropped. + <p> + This method is called on the object returned from the startSession + function. + + @param out If true the datagram is being send out(from the client), + otherwise it is an incoming datagram. + @return True to forward datagram false drop it silently. + */ + boolean checkRequest(DatagramPacket dp, boolean out); + + /** + This method is called when session is completed. Either due to normal + termination or due to any error condition. + <p> + This method is called on the object returned from the startSession + function. + */ + void endSession(); +} diff --git a/app/src/main/java/net/sourceforge/jsocks/server/ServerAuthenticatorNone.java b/app/src/main/java/net/sourceforge/jsocks/server/ServerAuthenticatorNone.java new file mode 100644 index 0000000..e4edbe7 --- /dev/null +++ b/app/src/main/java/net/sourceforge/jsocks/server/ServerAuthenticatorNone.java @@ -0,0 +1,169 @@ +package net.sourceforge.jsocks.server; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.net.Socket; + +import net.sourceforge.jsocks.ProxyMessage; +import net.sourceforge.jsocks.UDPEncapsulation; + +/** + An implementation of ServerAuthenticator, which does <b>not</b> do + any authentication. +<P> +<FONT size="+3" color ="FF0000"> Warning!!</font><br> Should not be +used on machines which are not behind the firewall. +<p> +It is only provided to make implementing other authentication schemes +easier.<br> +For Example: <tt><pre> + class MyAuth extends socks.server.ServerAuthenticator{ + ... + public ServerAuthenticator startSession(java.net.Socket s){ + if(!checkHost(s.getInetAddress()) return null; + return super.startSession(s); + } + + boolean checkHost(java.net.Inetaddress addr){ + boolean allow; + //Do it somehow + return allow; + } + } +</pre></tt> +*/ +public class ServerAuthenticatorNone implements ServerAuthenticator{ + + static final byte[] socks5response = {5,0}; + + InputStream in; + OutputStream out; + + /** + Creates new instance of the ServerAuthenticatorNone. + */ + public ServerAuthenticatorNone(){ + this.in = null; + this.out = null; + } + /** + Constructs new ServerAuthenticatorNone object suitable for returning + from the startSession function. + @param in Input stream to return from getInputStream method. + @param out Output stream to return from getOutputStream method. + */ + public ServerAuthenticatorNone(InputStream in, OutputStream out){ + this.in = in; + this.out = out; + } + /** + Grants access to everyone.Removes authentication related bytes from + the stream, when a SOCKS5 connection is being made, selects an + authentication NONE. + */ + public ServerAuthenticator startSession(Socket s) + throws IOException{ + + PushbackInputStream in = new PushbackInputStream(s.getInputStream()); + OutputStream out = s.getOutputStream(); + + int version = in.read(); + if(version == 5){ + if(!selectSocks5Authentication(in,out,0)) + return null; + }else if(version == 4){ + //Else it is the request message allready, version 4 + in.unread(version); + }else + return null; + + + return new ServerAuthenticatorNone(in,out); + } + + /** + Get input stream. + @return Input stream speciefied in the constructor. + */ + public InputStream getInputStream(){ + return in; + } + /** + Get output stream. + @return Output stream speciefied in the constructor. + */ + public OutputStream getOutputStream(){ + return out; + } + /** + Allways returns null. + @return null + */ + public UDPEncapsulation getUdpEncapsulation(){ + return null; + } + + /** + Allways returns true. + */ + public boolean checkRequest(ProxyMessage msg){ + return true; + } + + /** + Allways returns true. + */ + public boolean checkRequest(java.net.DatagramPacket dp, boolean out){ + return true; + } + + /** + Does nothing. + */ + public void endSession(){ + } + + /** + Convinience routine for selecting SOCKSv5 authentication. + <p> + This method reads in authentication methods that client supports, + checks wether it supports given method. If it does, the notification + method is written back to client, that this method have been chosen + for authentication. If given method was not found, authentication + failure message is send to client ([5,FF]). + @param in Input stream, version byte should be removed from the stream + before calling this method. + @param out Output stream. + @param methodId Method which should be selected. + @return true if methodId was found, false otherwise. + */ + static public boolean selectSocks5Authentication(InputStream in, + OutputStream out, + int methodId) + throws IOException{ + + int num_methods = in.read(); + if (num_methods <= 0) return false; + byte method_ids[] = new byte[num_methods]; + byte response[] = new byte[2]; + boolean found = false; + + response[0] = (byte) 5; //SOCKS version + response[1] = (byte) 0xFF; //Not found, we are pessimistic + + int bread = 0; //bytes read so far + while(bread < num_methods) + bread += in.read(method_ids,bread,num_methods-bread); + + for(int i=0;i<num_methods;++i) + if(method_ids[i] == methodId){ + found = true; + response[1] = (byte) methodId; + break; + } + + out.write(response); + return found; + } +} diff --git a/app/src/main/java/org/apache/harmony/niochar/charset/additional/IBM437.java b/app/src/main/java/org/apache/harmony/niochar/charset/additional/IBM437.java new file mode 100644 index 0000000..d61ef59 --- /dev/null +++ b/app/src/main/java/org/apache/harmony/niochar/charset/additional/IBM437.java @@ -0,0 +1,423 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.niochar.charset.additional; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; + +/* TODO: support direct byte buffers +import org.apache.harmony.nio.AddressUtil; +import org.apache.harmony.niochar.CharsetProviderImpl; +*/ + +public class IBM437 extends Charset { + + public IBM437(String csName, String[] aliases) { + super(csName, aliases); + } + + public boolean contains(Charset cs) { + return cs.name().equalsIgnoreCase("IBM367") || cs.name().equalsIgnoreCase("IBM437") || cs.name().equalsIgnoreCase("US-ASCII") ; + } + + public CharsetDecoder newDecoder() { + return new Decoder(this); + } + + public CharsetEncoder newEncoder() { + return new Encoder(this); + } + + private static final class Decoder extends CharsetDecoder{ + private Decoder(Charset cs){ + super(cs, 1, 1); + + } + + private native int nDecode(char[] array, int arrPosition, int remaining, long outAddr, int absolutePos); + + + protected CoderResult decodeLoop(ByteBuffer bb, CharBuffer cb){ + int cbRemaining = cb.remaining(); +/* TODO: support direct byte buffers + if(CharsetProviderImpl.hasLoadedNatives() && bb.isDirect() && bb.hasRemaining() && cb.hasArray()){ + int toProceed = bb.remaining(); + int cbPos = cb.position(); + int bbPos = bb.position(); + boolean throwOverflow = false; + if( cbRemaining < toProceed ) { + toProceed = cbRemaining; + throwOverflow = true; + } + int res = nDecode(cb.array(), cb.arrayOffset()+cbPos, toProceed, AddressUtil.getDirectBufferAddress(bb), bbPos); + bb.position(bbPos+res); + cb.position(cbPos+res); + if(throwOverflow) return CoderResult.OVERFLOW; + }else{ +*/ + if(bb.hasArray() && cb.hasArray()) { + int rem = bb.remaining(); + rem = cbRemaining >= rem ? rem : cbRemaining; + byte[] bArr = bb.array(); + char[] cArr = cb.array(); + int bStart = bb.position(); + int cStart = cb.position(); + int i; + for(i=bStart; i<bStart+rem; i++) { + char in = (char)(bArr[i] & 0xFF); + if(in >= 26){ + int index = (int)in - 26; + cArr[cStart++] = (char)arr[index]; + }else { + cArr[cStart++] = (char)(in & 0xFF); + } + } + bb.position(i); + cb.position(cStart); + if(rem == cbRemaining && bb.hasRemaining()) return CoderResult.OVERFLOW; + } else { + while(bb.hasRemaining()){ + if( cbRemaining == 0 ) return CoderResult.OVERFLOW; + char in = (char)(bb.get() & 0xFF); + if(in >= 26){ + int index = (int)in - 26; + cb.put(arr[index]); + }else { + cb.put((char)(in & 0xFF)); + } + cbRemaining--; + } +/* + } +*/ + } + return CoderResult.UNDERFLOW; + } + + final static char[] arr = { + 0x001C,0x001B,0x007F,0x001D,0x001E,0x001F, + 0x0020,0x0021,0x0022,0x0023,0x0024,0x0025,0x0026,0x0027, + 0x0028,0x0029,0x002A,0x002B,0x002C,0x002D,0x002E,0x002F, + 0x0030,0x0031,0x0032,0x0033,0x0034,0x0035,0x0036,0x0037, + 0x0038,0x0039,0x003A,0x003B,0x003C,0x003D,0x003E,0x003F, + 0x0040,0x0041,0x0042,0x0043,0x0044,0x0045,0x0046,0x0047, + 0x0048,0x0049,0x004A,0x004B,0x004C,0x004D,0x004E,0x004F, + 0x0050,0x0051,0x0052,0x0053,0x0054,0x0055,0x0056,0x0057, + 0x0058,0x0059,0x005A,0x005B,0x005C,0x005D,0x005E,0x005F, + 0x0060,0x0061,0x0062,0x0063,0x0064,0x0065,0x0066,0x0067, + 0x0068,0x0069,0x006A,0x006B,0x006C,0x006D,0x006E,0x006F, + 0x0070,0x0071,0x0072,0x0073,0x0074,0x0075,0x0076,0x0077, + 0x0078,0x0079,0x007A,0x007B,0x007C,0x007D,0x007E,0x001A, + 0x00C7,0x00FC,0x00E9,0x00E2,0x00E4,0x00E0,0x00E5,0x00E7, + 0x00EA,0x00EB,0x00E8,0x00EF,0x00EE,0x00EC,0x00C4,0x00C5, + 0x00C9,0x00E6,0x00C6,0x00F4,0x00F6,0x00F2,0x00FB,0x00F9, + 0x00FF,0x00D6,0x00DC,0x00A2,0x00A3,0x00A5,0x20A7,0x0192, + 0x00E1,0x00ED,0x00F3,0x00FA,0x00F1,0x00D1,0x00AA,0x00BA, + 0x00BF,0x2310,0x00AC,0x00BD,0x00BC,0x00A1,0x00AB,0x00BB, + 0x2591,0x2592,0x2593,0x2502,0x2524,0x2561,0x2562,0x2556, + 0x2555,0x2563,0x2551,0x2557,0x255D,0x255C,0x255B,0x2510, + 0x2514,0x2534,0x252C,0x251C,0x2500,0x253C,0x255E,0x255F, + 0x255A,0x2554,0x2569,0x2566,0x2560,0x2550,0x256C,0x2567, + 0x2568,0x2564,0x2565,0x2559,0x2558,0x2552,0x2553,0x256B, + 0x256A,0x2518,0x250C,0x2588,0x2584,0x258C,0x2590,0x2580, + 0x03B1,0x00DF,0x0393,0x03C0,0x03A3,0x03C3,0x03BC,0x03C4, + 0x03A6,0x0398,0x03A9,0x03B4,0x221E,0x03C6,0x03B5,0x2229, + 0x2261,0x00B1,0x2265,0x2264,0x2320,0x2321,0x00F7,0x2248, + 0x00B0,0x2219,0x00B7,0x221A,0x207F,0x00B2,0x25A0,0x00A0 + }; + } + + private static final class Encoder extends CharsetEncoder{ + private Encoder(Charset cs){ + super(cs, 1, 1); + } + + private native void nEncode(long outAddr, int absolutePos, char[] array, int arrPosition, int[] res); + + protected CoderResult encodeLoop(CharBuffer cb, ByteBuffer bb){ + int bbRemaining = bb.remaining(); +/* TODO: support direct byte buffers + if(CharsetProviderImpl.hasLoadedNatives() && bb.isDirect() && cb.hasRemaining() && cb.hasArray()){ + int toProceed = cb.remaining(); + int cbPos = cb.position(); + int bbPos = bb.position(); + boolean throwOverflow = false; + if( bbRemaining < toProceed ) { + toProceed = bbRemaining; + throwOverflow = true; + } + int[] res = {toProceed, 0}; + nEncode(AddressUtil.getDirectBufferAddress(bb), bbPos, cb.array(), cb.arrayOffset()+cbPos, res); + if( res[0] <= 0 ) { + bb.position(bbPos-res[0]); + cb.position(cbPos-res[0]); + if(res[1]!=0) { + if(res[1] < 0) + return CoderResult.malformedForLength(-res[1]); + else + return CoderResult.unmappableForLength(res[1]); + } + }else{ + bb.position(bbPos+res[0]); + cb.position(cbPos+res[0]); + if(throwOverflow) return CoderResult.OVERFLOW; + } + }else{ +*/ + if(bb.hasArray() && cb.hasArray()) { + byte[] byteArr = bb.array(); + char[] charArr = cb.array(); + int rem = cb.remaining(); + int byteArrStart = bb.position(); + rem = bbRemaining <= rem ? bbRemaining : rem; + int x; + for(x = cb.position(); x < cb.position()+rem; x++) { + char c = charArr[x]; + if(c > (char)0x25A0){ + if (c >= 0xD800 && c <= 0xDFFF) { + if(x+1 < cb.limit()) { + char c1 = charArr[x+1]; + if(c1 >= 0xD800 && c1 <= 0xDFFF) { + cb.position(x); bb.position(byteArrStart); + return CoderResult.unmappableForLength(2); + } + } else { + cb.position(x); bb.position(byteArrStart); + return CoderResult.UNDERFLOW; + } + cb.position(x); bb.position(byteArrStart); + return CoderResult.malformedForLength(1); + } + cb.position(x); bb.position(byteArrStart); + return CoderResult.unmappableForLength(1); + }else{ + if(c < 0x1A) { + byteArr[byteArrStart++] = (byte)c; + } else { + int index = (int)c >> 8; + index = encodeIndex[index]; + if(index < 0) { + cb.position(x); bb.position(byteArrStart); + return CoderResult.unmappableForLength(1); + } + index <<= 8; + index += (int)c & 0xFF; + if((byte)arr[index] != 0){ + byteArr[byteArrStart++] = (byte)arr[index]; + }else{ + cb.position(x); bb.position(byteArrStart); + return CoderResult.unmappableForLength(1); + } + } + } + } + cb.position(x); + bb.position(byteArrStart); + if(rem == bbRemaining && cb.hasRemaining()) { + return CoderResult.OVERFLOW; + } + } else { + while(cb.hasRemaining()){ + if( bbRemaining == 0 ) return CoderResult.OVERFLOW; + char c = cb.get(); + if(c > (char)0x25A0){ + if (c >= 0xD800 && c <= 0xDFFF) { + if(cb.hasRemaining()) { + char c1 = cb.get(); + if(c1 >= 0xD800 && c1 <= 0xDFFF) { + cb.position(cb.position()-2); + return CoderResult.unmappableForLength(2); + } else { + cb.position(cb.position()-1); + } + } else { + cb.position(cb.position()-1); + return CoderResult.UNDERFLOW; + } + cb.position(cb.position()-1); + return CoderResult.malformedForLength(1); + } + cb.position(cb.position()-1); + return CoderResult.unmappableForLength(1); + }else{ + if(c < 0x1A) { + bb.put((byte)c); + } else { + int index = (int)c >> 8; + index = encodeIndex[index]; + if(index < 0) { + cb.position(cb.position()-1); + return CoderResult.unmappableForLength(1); + } + index <<= 8; + index += (int)c & 0xFF; + if((byte)arr[index] != 0){ + bb.put((byte)arr[index]); + }else{ + cb.position(cb.position()-1); + return CoderResult.unmappableForLength(1); + } + } + bbRemaining--; + } + } +/* TODO: support direct byte buffers + } +*/ + } + return CoderResult.UNDERFLOW; + } + + final static char arr[] = { + + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x7F,0x1B,0x1A,0x1D,0x1E,0x1F, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x1C, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xAD,0x9B,0x9C,0x00,0x9D,0x00,0x00,0x00,0x00,0xA6,0xAE,0xAA,0x00,0x00,0x00, + 0xF8,0xF1,0xFD,0x00,0x00,0x00,0x00,0xFA,0x00,0x00,0xA7,0xAF,0xAC,0xAB,0x00,0xA8, + 0x00,0x00,0x00,0x00,0x8E,0x8F,0x92,0x80,0x00,0x90,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xA5,0x00,0x00,0x00,0x00,0x99,0x00,0x00,0x00,0x00,0x00,0x9A,0x00,0x00,0xE1, + 0x85,0xA0,0x83,0x00,0x84,0x86,0x91,0x87,0x8A,0x82,0x88,0x89,0x8D,0xA1,0x8C,0x8B, + 0x00,0xA4,0x95,0xA2,0x93,0x00,0x94,0xF6,0x00,0x97,0xA3,0x96,0x81,0x00,0x00,0x98, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x9F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xE2,0x00,0x00,0x00,0x00,0xE9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xE4,0x00,0x00,0xE8,0x00,0x00,0xEA,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xE0,0x00,0x00,0xEB,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0x00,0x00,0x00, + 0xE3,0x00,0x00,0xE5,0xE7,0x00,0xED,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF9,0xFB,0x00,0x00,0x00,0xEC,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF7,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xF0,0x00,0x00,0xF3,0xF2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xA9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xF4,0xF5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0xC4,0x00,0xB3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDA,0x00,0x00,0x00, + 0xBF,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xD9,0x00,0x00,0x00,0xC3,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xB4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC2,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xC1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC5,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xCD,0xBA,0xD5,0xD6,0xC9,0xB8,0xB7,0xBB,0xD4,0xD3,0xC8,0xBE,0xBD,0xBC,0xC6,0xC7, + 0xCC,0xB5,0xB6,0xB9,0xD1,0xD2,0xCB,0xCF,0xD0,0xCA,0xD8,0xD7,0xCE,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xDF,0x00,0x00,0x00,0xDC,0x00,0x00,0x00,0xDB,0x00,0x00,0x00,0xDD,0x00,0x00,0x00, + 0xDE,0xB0,0xB1,0xB2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + final static int[] encodeIndex = { + 0,1,-1,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + 3,-1,4,5,-1,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + } +} diff --git a/app/src/main/java/org/connectbot/ActionBarWrapper.java b/app/src/main/java/org/connectbot/ActionBarWrapper.java new file mode 100644 index 0000000..f6bed8c --- /dev/null +++ b/app/src/main/java/org/connectbot/ActionBarWrapper.java @@ -0,0 +1,89 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import org.connectbot.util.PreferenceConstants; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActionBar; + +public abstract class ActionBarWrapper { + public interface OnMenuVisibilityListener { + public void onMenuVisibilityChanged(boolean isVisible); + } + + public static ActionBarWrapper getActionBar(Activity activity) { + if (PreferenceConstants.PRE_HONEYCOMB) + return new DummyActionBar(); + else + return new RealActionBar(activity); + } + + public void hide() { + } + + public void show() { + } + + public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { + } + + public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { + } + + private static class DummyActionBar extends ActionBarWrapper { + } + + /** + * Real ActionBar delegate that is only invoked on Honeycomb + * and later. + */ + @TargetApi(11) + private static class RealActionBar extends ActionBarWrapper { + private final ActionBar actionBar; + + public RealActionBar(Activity activity) { + actionBar = activity.getActionBar(); + } + + @Override + public void hide() { + actionBar.hide(); + } + + @Override + public void show() { + actionBar.show(); + } + + @Override + public void addOnMenuVisibilityListener(final OnMenuVisibilityListener listener) { + actionBar.addOnMenuVisibilityListener(new ActionBar.OnMenuVisibilityListener() { + public void onMenuVisibilityChanged(boolean isVisible) { + listener.onMenuVisibilityChanged(isVisible); + } + }); + } + + @Override + public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { + actionBar.setDisplayHomeAsUpEnabled(showHomeAsUp); + } + } +} diff --git a/app/src/main/java/org/connectbot/ColorsActivity.java b/app/src/main/java/org/connectbot/ColorsActivity.java new file mode 100644 index 0000000..be5dfb1 --- /dev/null +++ b/app/src/main/java/org/connectbot/ColorsActivity.java @@ -0,0 +1,362 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.List; + +import org.connectbot.util.Colors; +import org.connectbot.util.HostDatabase; +import org.connectbot.util.UberColorPickerDialog; +import org.connectbot.util.UberColorPickerDialog.OnColorChangedListener; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.MenuItem.OnMenuItemClickListener; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.GridView; +import android.widget.Spinner; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; + +/** + * @author Kenny Root + * + */ +public class ColorsActivity extends Activity implements OnItemClickListener, OnColorChangedListener, OnItemSelectedListener { + private GridView mColorGrid; + private Spinner mFgSpinner; + private Spinner mBgSpinner; + + private int mColorScheme; + + private List<Integer> mColorList; + private HostDatabase mHostDb; + + private int mCurrentColor = 0; + + private int[] mDefaultColors; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.act_colors); + + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_colors))); + + mColorScheme = HostDatabase.DEFAULT_COLOR_SCHEME; + + mHostDb = new HostDatabase(this); + + mColorList = Arrays.asList(mHostDb.getColorsForScheme(mColorScheme)); + mDefaultColors = mHostDb.getDefaultColorsForScheme(mColorScheme); + + mColorGrid = (GridView) findViewById(R.id.color_grid); + mColorGrid.setAdapter(new ColorsAdapter(true)); + mColorGrid.setOnItemClickListener(this); + mColorGrid.setSelection(0); + + mFgSpinner = (Spinner) findViewById(R.id.fg); + mFgSpinner.setAdapter(new ColorsAdapter(false, R.string.colors_fg_label)); + mFgSpinner.setSelection(mDefaultColors[0]); + mFgSpinner.setOnItemSelectedListener(this); + + mBgSpinner = (Spinner) findViewById(R.id.bg); + mBgSpinner.setAdapter(new ColorsAdapter(false, R.string.color_bg_label)); + mBgSpinner.setSelection(mDefaultColors[1]); + mBgSpinner.setOnItemSelectedListener(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (mHostDb != null) { + mHostDb.close(); + mHostDb = null; + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (mHostDb == null) + mHostDb = new HostDatabase(this); + } + + private class ColorsAdapter extends BaseAdapter { + private final boolean mSquareViews; + private final int mResourceLabel; + + public ColorsAdapter(boolean squareViews) { + this(squareViews, -1); + } + + public ColorsAdapter(boolean squareViews, int resourceLabel) { + mSquareViews = squareViews; + mResourceLabel = resourceLabel; + } + + public View getView(int position, View convertView, ViewGroup parent) { + ColorView c; + + if (convertView == null) { + c = new ColorView(ColorsActivity.this, mResourceLabel, mSquareViews); + } else { + c = (ColorView) convertView; + } + + c.setColor(mColorList.get(position)); + c.setNumber(position + 1); + + return c; + } + + public int getCount() { + return mColorList.size(); + } + + public Object getItem(int position) { + return mColorList.get(position); + } + + public long getItemId(int position) { + return position; + } + } + + private class ColorView extends View { + /** The font size displayed in the GridView entries. */ + private static final float FONT_SIZE_DP = 20f; + + /** Margin around the GridView entries. */ + private static final float MARGIN_DP = 10f; + + private final boolean mSquare; + + private final int mResourceLabel; + private final NumberFormat mNumberFormatter; + + private Paint mTextPaint; + private Paint mShadowPaint; + + // Things we paint + private int mBackgroundColor; + private String mText; + + private int mAscent; + private int mWidthCenter; + private int mHeightCenter; + + public ColorView(Context context, int resourceLabel, boolean square) { + super(context); + + mSquare = square; + mResourceLabel = resourceLabel; + + mNumberFormatter = NumberFormat.getIntegerInstance(getContext().getResources().getConfiguration().locale); + + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + + mTextPaint = new Paint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize((int) (metrics.density * FONT_SIZE_DP + 0.5f)); + mTextPaint.setColor(0xFFFFFFFF); + mTextPaint.setTextAlign(Paint.Align.CENTER); + + mShadowPaint = new Paint(mTextPaint); + mShadowPaint.setStyle(Paint.Style.STROKE); + mShadowPaint.setStrokeCap(Paint.Cap.ROUND); + mShadowPaint.setStrokeJoin(Paint.Join.ROUND); + mShadowPaint.setStrokeWidth(4f); + mShadowPaint.setColor(0xFF000000); + + int marginPx = (int) (MARGIN_DP * metrics.density + 0.5f); + setPadding(marginPx, marginPx, marginPx, marginPx); + } + + public void setColor(int color) { + mBackgroundColor = color; + } + + public void setNumber(int number) { + if (mResourceLabel != -1) { + mText = getContext().getResources().getString(mResourceLabel, number); + } else { + mText = mNumberFormatter.format(number); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = measureWidth(widthMeasureSpec); + + int height; + if (mSquare) { + //noinspection SuspiciousNameCombination + height = width; + } else { + height = measureHeight(heightMeasureSpec); + } + + mAscent = (int) mTextPaint.ascent(); + mWidthCenter = width / 2; + mHeightCenter = height / 2 - mAscent / 2; + + setMeasuredDimension(width, height); + } + + private int measureWidth(int measureSpec) { + int result; + + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else { + // Measure the text + result = (int) mTextPaint.measureText(mText) + getPaddingLeft() + + getPaddingRight(); + if (specMode == MeasureSpec.AT_MOST) { + // Respect AT_MOST value if that was what is called for by + // measureSpec + result = Math.min(result, specSize); + } + } + + return result; + } + + private int measureHeight(int measureSpec) { + int result; + + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + mAscent = (int) mTextPaint.ascent(); + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else { + // Measure the text (beware: ascent is a negative number) + result = (int) (-mAscent + mTextPaint.descent()) + + getPaddingTop() + getPaddingBottom(); + if (specMode == MeasureSpec.AT_MOST) { + // Respect AT_MOST value if that was what is called for by + // measureSpec + result = Math.min(result, specSize); + } + } + return result; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.drawColor(mBackgroundColor); + + canvas.drawText(mText, mWidthCenter, mHeightCenter, mShadowPaint); + canvas.drawText(mText, mWidthCenter, mHeightCenter, mTextPaint); + } + } + + private void editColor(int colorNumber) { + mCurrentColor = colorNumber; + new UberColorPickerDialog(this, this, mColorList.get(colorNumber)).show(); + } + + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + editColor(position); + } + + public void onNothingSelected(AdapterView<?> arg0) { } + + public void colorChanged(int value) { + mHostDb.setGlobalColor(mCurrentColor, value); + mColorList.set(mCurrentColor, value); + mColorGrid.invalidateViews(); + } + + public void onItemSelected(AdapterView<?> parent, View view, int position, + long id) { + boolean needUpdate = false; + if (parent == mFgSpinner) { + if (position != mDefaultColors[0]) { + mDefaultColors[0] = position; + needUpdate = true; + } + } else if (parent == mBgSpinner) { + if (position != mDefaultColors[1]) { + mDefaultColors[1] = position; + needUpdate = true; + } + } + + if (needUpdate) + mHostDb.setDefaultColorsForScheme(mColorScheme, mDefaultColors[0], mDefaultColors[1]); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + MenuItem reset = menu.add(R.string.menu_colors_reset); + reset.setAlphabeticShortcut('r'); + reset.setNumericShortcut('1'); + reset.setIcon(android.R.drawable.ic_menu_revert); + reset.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem arg0) { + // Reset each individual color to defaults. + for (int i = 0; i < Colors.defaults.length; i++) { + if (!mColorList.get(i).equals(Colors.defaults[i])) { + mHostDb.setGlobalColor(i, Colors.defaults[i]); + mColorList.set(i, Colors.defaults[i]); + } + } + mColorGrid.invalidateViews(); + + // Reset the default FG/BG colors as well. + mFgSpinner.setSelection(HostDatabase.DEFAULT_FG_COLOR); + mBgSpinner.setSelection(HostDatabase.DEFAULT_BG_COLOR); + mHostDb.setDefaultColorsForScheme(HostDatabase.DEFAULT_COLOR_SCHEME, + HostDatabase.DEFAULT_FG_COLOR, HostDatabase.DEFAULT_BG_COLOR); + + return true; + } + }); + + return true; + } +} diff --git a/app/src/main/java/org/connectbot/ConsoleActivity.java b/app/src/main/java/org/connectbot/ConsoleActivity.java new file mode 100644 index 0000000..2116d92 --- /dev/null +++ b/app/src/main/java/org/connectbot/ConsoleActivity.java @@ -0,0 +1,1184 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.lang.ref.WeakReference; +import java.util.List; + +import org.connectbot.bean.SelectionArea; +import org.connectbot.service.PromptHelper; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalKeyListener; +import org.connectbot.service.TerminalManager; +import org.connectbot.util.PreferenceConstants; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.preference.PreferenceManager; +import android.text.ClipboardManager; +import android.util.Log; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; +import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ViewFlipper; +import de.mud.terminal.vt320; + +public class ConsoleActivity extends Activity { + public final static String TAG = "ConnectBot.ConsoleActivity"; + + protected static final int REQUEST_EDIT = 1; + + private static final int CLICK_TIME = 400; + private static final float MAX_CLICK_DISTANCE = 25f; + private static final int KEYBOARD_DISPLAY_TIME = 1500; + + // Direction to shift the ViewFlipper + private static final int SHIFT_LEFT = 0; + private static final int SHIFT_RIGHT = 1; + + protected ViewFlipper flip = null; + protected TerminalManager bound = null; + protected LayoutInflater inflater = null; + + private SharedPreferences prefs = null; + + // determines whether or not menuitem accelerators are bound + // otherwise they collide with an external keyboard's CTRL-char + private boolean hardKeyboard = false; + + protected Uri requested; + + protected ClipboardManager clipboard; + private RelativeLayout stringPromptGroup; + protected EditText stringPrompt; + private TextView stringPromptInstructions; + + private RelativeLayout booleanPromptGroup; + private TextView booleanPrompt; + private Button booleanYes, booleanNo; + + private RelativeLayout keyboardGroup; + private Runnable keyboardGroupHider; + + private TextView empty; + + private Animation slide_left_in, slide_left_out, slide_right_in, slide_right_out, fade_stay_hidden, fade_out_delayed; + + private Animation keyboard_fade_in, keyboard_fade_out; + private float lastX, lastY; + + private InputMethodManager inputManager; + + private MenuItem disconnect, copy, paste, portForward, resize, urlscan; + + protected TerminalBridge copySource = null; + private int lastTouchRow, lastTouchCol; + + private boolean forcedOrientation; + + private Handler handler = new Handler(); + + private ImageView mKeyboardButton; + + private ActionBarWrapper actionBar; + private boolean inActionBarMenu = false; + private boolean titleBarHide; + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = ((TerminalManager.TerminalBinder) service).getService(); + + // let manager know about our event handling services + bound.disconnectHandler = disconnectHandler; + + Log.d(TAG, String.format("Connected to TerminalManager and found bridges.size=%d", bound.bridges.size())); + + bound.setResizeAllowed(true); + + // clear out any existing bridges and record requested index + flip.removeAllViews(); + + final String requestedNickname = (requested != null) ? requested.getFragment() : null; + int requestedIndex = 0; + + TerminalBridge requestedBridge = bound.getConnectedBridge(requestedNickname); + + // If we didn't find the requested connection, try opening it + if (requestedNickname != null && requestedBridge == null) { + try { + Log.d(TAG, String.format("We couldnt find an existing bridge with URI=%s (nickname=%s), so creating one now", requested.toString(), requestedNickname)); + requestedBridge = bound.openConnection(requested); + } catch (Exception e) { + Log.e(TAG, "Problem while trying to create new requested bridge from URI", e); + } + } + + // create views for all bridges on this service + for (TerminalBridge bridge : bound.bridges) { + + final int currentIndex = addNewTerminalView(bridge); + + // check to see if this bridge was requested + if (bridge == requestedBridge) + requestedIndex = currentIndex; + } + + setDisplayedTerminal(requestedIndex); + } + + public void onServiceDisconnected(ComponentName className) { + // tell each bridge to forget about our prompt handler + synchronized (bound.bridges) { + for (TerminalBridge bridge : bound.bridges) + bridge.promptHelper.setHandler(null); + } + + flip.removeAllViews(); + updateEmptyVisible(); + bound = null; + } + }; + + protected Handler promptHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + // someone below us requested to display a prompt + updatePromptVisible(); + } + }; + + protected Handler disconnectHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "Someone sending HANDLE_DISCONNECT to parentHandler"); + + // someone below us requested to display a password dialog + // they are sending nickname and requested + TerminalBridge bridge = (TerminalBridge) msg.obj; + + if (bridge.isAwaitingClose()) + closeBridge(bridge); + } + }; + + /** + * @param bridge + */ + private void closeBridge(final TerminalBridge bridge) { + synchronized (flip) { + final int flipIndex = getFlipIndex(bridge); + + if (flipIndex >= 0) { + if (flip.getDisplayedChild() == flipIndex) { + shiftCurrentTerminal(SHIFT_LEFT); + } + flip.removeViewAt(flipIndex); + + /* TODO Remove this workaround when ViewFlipper is fixed to listen + * to view removals. Android Issue 1784 + */ + final int numChildren = flip.getChildCount(); + if (flip.getDisplayedChild() >= numChildren && + numChildren > 0) { + flip.setDisplayedChild(numChildren - 1); + } + + updateEmptyVisible(); + } + + // If we just closed the last bridge, go back to the previous activity. + if (flip.getChildCount() == 0) { + finish(); + } + } + } + + protected View findCurrentView(int id) { + View view = flip.getCurrentView(); + if (view == null) return null; + return view.findViewById(id); + } + + protected PromptHelper getCurrentPromptHelper() { + View view = findCurrentView(R.id.console_flip); + if (!(view instanceof TerminalView)) return null; + return ((TerminalView) view).bridge.promptHelper; + } + + protected void hideAllPrompts() { + stringPromptGroup.setVisibility(View.GONE); + booleanPromptGroup.setVisibility(View.GONE); + } + + private void showEmulatedKeys() { + keyboardGroup.startAnimation(keyboard_fade_in); + keyboardGroup.setVisibility(View.VISIBLE); + actionBar.show(); + + if (keyboardGroupHider != null) + handler.removeCallbacks(keyboardGroupHider); + keyboardGroupHider = new Runnable() { + public void run() { + if (keyboardGroup.getVisibility() == View.GONE || inActionBarMenu) + return; + + keyboardGroup.startAnimation(keyboard_fade_out); + keyboardGroup.setVisibility(View.GONE); + if (titleBarHide) { + actionBar.hide(); + } + keyboardGroupHider = null; + } + }; + handler.postDelayed(keyboardGroupHider, KEYBOARD_DISPLAY_TIME); + } + + private void hideEmulatedKeys() { + if (keyboardGroupHider != null) + handler.removeCallbacks(keyboardGroupHider); + keyboardGroup.setVisibility(View.GONE); + if (titleBarHide) { + actionBar.hide(); + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { + StrictModeSetup.run(); + } + + hardKeyboard = getResources().getConfiguration().keyboard == + Configuration.KEYBOARD_QWERTY; + + clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + prefs = PreferenceManager.getDefaultSharedPreferences(this); + + titleBarHide = prefs.getBoolean(PreferenceConstants.TITLEBARHIDE, false); + if (titleBarHide) { + getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); + } + + this.setContentView(R.layout.act_console); + + // hide status bar if requested by user + if (prefs.getBoolean(PreferenceConstants.FULLSCREEN, false)) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + // TODO find proper way to disable volume key beep if it exists. + setVolumeControlStream(AudioManager.STREAM_MUSIC); + + // handle requested console from incoming intent + requested = getIntent().getData(); + + inflater = LayoutInflater.from(this); + + flip = (ViewFlipper) findViewById(R.id.console_flip); + empty = (TextView) findViewById(android.R.id.empty); + + stringPromptGroup = (RelativeLayout) findViewById(R.id.console_password_group); + stringPromptInstructions = (TextView) findViewById(R.id.console_password_instructions); + stringPrompt = (EditText) findViewById(R.id.console_password); + stringPrompt.setOnKeyListener(new OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) return false; + if (keyCode != KeyEvent.KEYCODE_ENTER) return false; + + // pass collected password down to current terminal + String value = stringPrompt.getText().toString(); + + PromptHelper helper = getCurrentPromptHelper(); + if (helper == null) return false; + helper.setResponse(value); + + // finally clear password for next user + stringPrompt.setText(""); + updatePromptVisible(); + + return true; + } + }); + + booleanPromptGroup = (RelativeLayout) findViewById(R.id.console_boolean_group); + booleanPrompt = (TextView) findViewById(R.id.console_prompt); + + booleanYes = (Button) findViewById(R.id.console_prompt_yes); + booleanYes.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + PromptHelper helper = getCurrentPromptHelper(); + if (helper == null) return; + helper.setResponse(Boolean.TRUE); + updatePromptVisible(); + } + }); + + booleanNo = (Button) findViewById(R.id.console_prompt_no); + booleanNo.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + PromptHelper helper = getCurrentPromptHelper(); + if (helper == null) return; + helper.setResponse(Boolean.FALSE); + updatePromptVisible(); + } + }); + + // preload animations for terminal switching + slide_left_in = AnimationUtils.loadAnimation(this, R.anim.slide_left_in); + slide_left_out = AnimationUtils.loadAnimation(this, R.anim.slide_left_out); + slide_right_in = AnimationUtils.loadAnimation(this, R.anim.slide_right_in); + slide_right_out = AnimationUtils.loadAnimation(this, R.anim.slide_right_out); + + fade_out_delayed = AnimationUtils.loadAnimation(this, R.anim.fade_out_delayed); + fade_stay_hidden = AnimationUtils.loadAnimation(this, R.anim.fade_stay_hidden); + + // Preload animation for keyboard button + keyboard_fade_in = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_in); + keyboard_fade_out = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_out); + + inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + keyboardGroup = (RelativeLayout) findViewById(R.id.keyboard_group); + + mKeyboardButton = (ImageView) findViewById(R.id.button_keyboard); + mKeyboardButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + View flip = findCurrentView(R.id.console_flip); + if (flip == null) + return; + + inputManager.showSoftInput(flip, InputMethodManager.SHOW_FORCED); + hideEmulatedKeys(); + } + }); + + final ImageView ctrlButton = (ImageView) findViewById(R.id.button_ctrl); + ctrlButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + View flip = findCurrentView(R.id.console_flip); + if (flip == null) return; + TerminalView terminal = (TerminalView) flip; + + TerminalKeyListener handler = terminal.bridge.getKeyHandler(); + handler.metaPress(TerminalKeyListener.OUR_CTRL_ON, true); + hideEmulatedKeys(); + } + }); + + final ImageView escButton = (ImageView) findViewById(R.id.button_esc); + escButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + View flip = findCurrentView(R.id.console_flip); + if (flip == null) return; + TerminalView terminal = (TerminalView) flip; + + TerminalKeyListener handler = terminal.bridge.getKeyHandler(); + handler.sendEscape(); + hideEmulatedKeys(); + } + }); + + actionBar = ActionBarWrapper.getActionBar(this); + actionBar.setDisplayHomeAsUpEnabled(true); + if (titleBarHide) { + actionBar.hide(); + } + actionBar.addOnMenuVisibilityListener(new ActionBarWrapper.OnMenuVisibilityListener() { + public void onMenuVisibilityChanged(boolean isVisible) { + inActionBarMenu = isVisible; + if (isVisible == false) { + hideEmulatedKeys(); + } + } + }); + + // detect fling gestures to switch between terminals + final GestureDetector detect = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { + private float totalY = 0; + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + + final float distx = e2.getRawX() - e1.getRawX(); + final float disty = e2.getRawY() - e1.getRawY(); + final int goalwidth = flip.getWidth() / 2; + + // need to slide across half of display to trigger console change + // make sure user kept a steady hand horizontally + if (Math.abs(disty) < (flip.getHeight() / 4)) { + if (distx > goalwidth) { + shiftCurrentTerminal(SHIFT_RIGHT); + return true; + } + + if (distx < -goalwidth) { + shiftCurrentTerminal(SHIFT_LEFT); + return true; + } + + } + + return false; + } + + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + + // if copying, then ignore + if (copySource != null && copySource.isSelectingForCopy()) + return false; + + if (e1 == null || e2 == null) + return false; + + // if releasing then reset total scroll + if (e2.getAction() == MotionEvent.ACTION_UP) { + totalY = 0; + } + + // activate consider if within x tolerance + if (Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) { + + View flip = findCurrentView(R.id.console_flip); + if (flip == null) return false; + TerminalView terminal = (TerminalView) flip; + + // estimate how many rows we have scrolled through + // accumulate distance that doesn't trigger immediate scroll + totalY += distanceY; + final int moved = (int) (totalY / terminal.bridge.charHeight); + + // consume as scrollback only if towards right half of screen + if (e2.getX() > flip.getWidth() / 2) { + if (moved != 0) { + int base = terminal.bridge.buffer.getWindowBase(); + terminal.bridge.buffer.setWindowBase(base + moved); + totalY = 0; + return true; + } + } else { + // otherwise consume as pgup/pgdown for every 5 lines + if (moved > 5) { + ((vt320) terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0); + terminal.bridge.tryKeyVibrate(); + totalY = 0; + return true; + } else if (moved < -5) { + ((vt320) terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0); + terminal.bridge.tryKeyVibrate(); + totalY = 0; + return true; + } + + } + + } + + return false; + } + + + }); + + flip.setLongClickable(true); + flip.setOnTouchListener(new OnTouchListener() { + + public boolean onTouch(View v, MotionEvent event) { + + // when copying, highlight the area + if (copySource != null && copySource.isSelectingForCopy()) { + int row = (int) Math.floor(event.getY() / copySource.charHeight); + int col = (int) Math.floor(event.getX() / copySource.charWidth); + + SelectionArea area = copySource.getSelectionArea(); + + switch(event.getAction()) { + case MotionEvent.ACTION_DOWN: + // recording starting area + if (area.isSelectingOrigin()) { + area.setRow(row); + area.setColumn(col); + lastTouchRow = row; + lastTouchCol = col; + copySource.redraw(); + } + return true; + case MotionEvent.ACTION_MOVE: + /* ignore when user hasn't moved since last time so + * we can fine-tune with directional pad + */ + if (row == lastTouchRow && col == lastTouchCol) + return true; + + // if the user moves, start the selection for other corner + area.finishSelectingOrigin(); + + // update selected area + area.setRow(row); + area.setColumn(col); + lastTouchRow = row; + lastTouchCol = col; + copySource.redraw(); + return true; + case MotionEvent.ACTION_UP: + /* If they didn't move their finger, maybe they meant to + * select the rest of the text with the directional pad. + */ + if (area.getLeft() == area.getRight() && + area.getTop() == area.getBottom()) { + return true; + } + + // copy selected area to clipboard + String copiedText = area.copyFrom(copySource.buffer); + + clipboard.setText(copiedText); + Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, copiedText.length()), Toast.LENGTH_LONG).show(); + // fall through to clear state + + case MotionEvent.ACTION_CANCEL: + // make sure we clear any highlighted area + area.reset(); + copySource.setSelectingForCopy(false); + copySource.redraw(); + return true; + } + } + + Configuration config = getResources().getConfiguration(); + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + lastX = event.getX(); + lastY = event.getY(); + } else if (event.getAction() == MotionEvent.ACTION_UP + && keyboardGroup.getVisibility() == View.GONE + && event.getEventTime() - event.getDownTime() < CLICK_TIME + && Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE + && Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) { + showEmulatedKeys(); + } + + // pass any touch events back to detector + return detect.onTouchEvent(event); + } + + }); + + } + + /** + * + */ + private void configureOrientation() { + String rotateDefault; + if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS) + rotateDefault = PreferenceConstants.ROTATION_PORTRAIT; + else + rotateDefault = PreferenceConstants.ROTATION_LANDSCAPE; + + String rotate = prefs.getString(PreferenceConstants.ROTATION, rotateDefault); + if (PreferenceConstants.ROTATION_DEFAULT.equals(rotate)) + rotate = rotateDefault; + + // request a forced orientation if requested by user + if (PreferenceConstants.ROTATION_LANDSCAPE.equals(rotate)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + forcedOrientation = true; + } else if (PreferenceConstants.ROTATION_PORTRAIT.equals(rotate)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + forcedOrientation = true; + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + forcedOrientation = false; + } + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + View view = findCurrentView(R.id.console_flip); + final boolean activeTerminal = (view instanceof TerminalView); + boolean sessionOpen = false; + boolean disconnected = false; + boolean canForwardPorts = false; + + if (activeTerminal) { + TerminalBridge bridge = ((TerminalView) view).bridge; + sessionOpen = bridge.isSessionOpen(); + disconnected = bridge.isDisconnected(); + canForwardPorts = bridge.canFowardPorts(); + } + + menu.setQwertyMode(true); + + disconnect = menu.add(R.string.list_host_disconnect); + if (hardKeyboard) + disconnect.setAlphabeticShortcut('w'); + if (!sessionOpen && disconnected) + disconnect.setTitle(R.string.console_menu_close); + disconnect.setEnabled(activeTerminal); + disconnect.setIcon(android.R.drawable.ic_menu_close_clear_cancel); + disconnect.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // disconnect or close the currently visible session + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + TerminalBridge bridge = terminalView.bridge; + + bridge.dispatchDisconnect(true); + return true; + } + }); + + copy = menu.add(R.string.console_menu_copy); + if (hardKeyboard) + copy.setAlphabeticShortcut('c'); + copy.setIcon(android.R.drawable.ic_menu_set_as); + copy.setEnabled(activeTerminal); + copy.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // mark as copying and reset any previous bounds + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + copySource = terminalView.bridge; + + SelectionArea area = copySource.getSelectionArea(); + area.reset(); + area.setBounds(copySource.buffer.getColumns(), copySource.buffer.getRows()); + + copySource.setSelectingForCopy(true); + + // Make sure we show the initial selection + copySource.redraw(); + + Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG).show(); + return true; + } + }); + + paste = menu.add(R.string.console_menu_paste); + if (hardKeyboard) + paste.setAlphabeticShortcut('v'); + paste.setIcon(android.R.drawable.ic_menu_edit); + paste.setEnabled(clipboard.hasText() && sessionOpen); + paste.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // force insert of clipboard text into current console + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + TerminalBridge bridge = terminalView.bridge; + + // pull string from clipboard and generate all events to force down + String clip = clipboard.getText().toString(); + bridge.injectString(clip); + + return true; + } + }); + + portForward = menu.add(R.string.console_menu_portforwards); + if (hardKeyboard) + portForward.setAlphabeticShortcut('f'); + portForward.setIcon(android.R.drawable.ic_menu_manage); + portForward.setEnabled(sessionOpen && canForwardPorts); + portForward.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + TerminalBridge bridge = terminalView.bridge; + + Intent intent = new Intent(ConsoleActivity.this, PortForwardListActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, bridge.host.getId()); + ConsoleActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); + + urlscan = menu.add(R.string.console_menu_urlscan); + if (hardKeyboard) + urlscan.setAlphabeticShortcut('u'); + urlscan.setIcon(android.R.drawable.ic_menu_search); + urlscan.setEnabled(activeTerminal); + urlscan.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + + List<String> urls = terminalView.bridge.scanForURLs(); + + Dialog urlDialog = new Dialog(ConsoleActivity.this); + urlDialog.setTitle(R.string.console_menu_urlscan); + + ListView urlListView = new ListView(ConsoleActivity.this); + URLItemListener urlListener = new URLItemListener(ConsoleActivity.this); + urlListView.setOnItemClickListener(urlListener); + + urlListView.setAdapter(new ArrayAdapter<String>(ConsoleActivity.this, android.R.layout.simple_list_item_1, urls)); + urlDialog.setContentView(urlListView); + urlDialog.show(); + + return true; + } + }); + + resize = menu.add(R.string.console_menu_resize); + if (hardKeyboard) + resize.setAlphabeticShortcut('s'); + resize.setIcon(android.R.drawable.ic_menu_crop); + resize.setEnabled(sessionOpen); + resize.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + + final View resizeView = inflater.inflate(R.layout.dia_resize, null, false); + new AlertDialog.Builder(ConsoleActivity.this) + .setView(resizeView) + .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + int width, height; + try { + width = Integer.parseInt(((EditText) resizeView + .findViewById(R.id.width)) + .getText().toString()); + height = Integer.parseInt(((EditText) resizeView + .findViewById(R.id.height)) + .getText().toString()); + } catch (NumberFormatException nfe) { + // TODO change this to a real dialog where we can + // make the input boxes turn red to indicate an error. + return; + } + + terminalView.forceSize(width, height); + } + }).setNegativeButton(android.R.string.cancel, null).create().show(); + + return true; + } + }); + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + setVolumeControlStream(AudioManager.STREAM_NOTIFICATION); + + final View view = findCurrentView(R.id.console_flip); + boolean activeTerminal = (view instanceof TerminalView); + boolean sessionOpen = false; + boolean disconnected = false; + boolean canForwardPorts = false; + + if (activeTerminal) { + TerminalBridge bridge = ((TerminalView) view).bridge; + sessionOpen = bridge.isSessionOpen(); + disconnected = bridge.isDisconnected(); + canForwardPorts = bridge.canFowardPorts(); + } + + disconnect.setEnabled(activeTerminal); + if (sessionOpen || !disconnected) + disconnect.setTitle(R.string.list_host_disconnect); + else + disconnect.setTitle(R.string.console_menu_close); + copy.setEnabled(activeTerminal); + paste.setEnabled(clipboard.hasText() && sessionOpen); + portForward.setEnabled(sessionOpen && canForwardPorts); + urlscan.setEnabled(activeTerminal); + resize.setEnabled(sessionOpen); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + Intent intent = new Intent(this, HostListActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onOptionsMenuClosed(Menu menu) { + super.onOptionsMenuClosed(menu); + + setVolumeControlStream(AudioManager.STREAM_MUSIC); + } + + @Override + public void onStart() { + super.onStart(); + + // connect with manager service to find all bridges + // when connected it will insert all views + bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + } + + @Override + public void onPause() { + super.onPause(); + Log.d(TAG, "onPause called"); + + if (forcedOrientation && bound != null) + bound.setResizeAllowed(false); + } + + @Override + public void onResume() { + super.onResume(); + Log.d(TAG, "onResume called"); + + // Make sure we don't let the screen fall asleep. + // This also keeps the Wi-Fi chipset from disconnecting us. + if (prefs.getBoolean(PreferenceConstants.KEEP_ALIVE, true)) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + configureOrientation(); + + if (forcedOrientation && bound != null) + bound.setResizeAllowed(true); + } + + /* (non-Javadoc) + * @see android.app.Activity#onNewIntent(android.content.Intent) + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + Log.d(TAG, "onNewIntent called"); + + requested = intent.getData(); + + if (requested == null) { + Log.e(TAG, "Got null intent data in onNewIntent()"); + return; + } + + if (bound == null) { + Log.e(TAG, "We're not bound in onNewIntent()"); + return; + } + + TerminalBridge requestedBridge = bound.getConnectedBridge(requested.getFragment()); + int requestedIndex = 0; + + synchronized (flip) { + if (requestedBridge == null) { + // If we didn't find the requested connection, try opening it + + try { + Log.d(TAG, String.format("We couldnt find an existing bridge with URI=%s (nickname=%s)," + + "so creating one now", requested.toString(), requested.getFragment())); + requestedBridge = bound.openConnection(requested); + } catch (Exception e) { + Log.e(TAG, "Problem while trying to create new requested bridge from URI", e); + // TODO: We should display an error dialog here. + return; + } + + requestedIndex = addNewTerminalView(requestedBridge); + } else { + final int flipIndex = getFlipIndex(requestedBridge); + if (flipIndex > requestedIndex) { + requestedIndex = flipIndex; + } + } + + setDisplayedTerminal(requestedIndex); + } + } + + @Override + public void onStop() { + super.onStop(); + + unbindService(connection); + } + + protected void shiftCurrentTerminal(final int direction) { + View overlay; + synchronized (flip) { + boolean shouldAnimate = flip.getChildCount() > 1; + + // Only show animation if there is something else to go to. + if (shouldAnimate) { + // keep current overlay from popping up again + overlay = findCurrentView(R.id.terminal_overlay); + if (overlay != null) + overlay.startAnimation(fade_stay_hidden); + + if (direction == SHIFT_LEFT) { + flip.setInAnimation(slide_left_in); + flip.setOutAnimation(slide_left_out); + flip.showNext(); + } else if (direction == SHIFT_RIGHT) { + flip.setInAnimation(slide_right_in); + flip.setOutAnimation(slide_right_out); + flip.showPrevious(); + } + } + + ConsoleActivity.this.updateDefault(); + + if (shouldAnimate) { + // show overlay on new slide and start fade + overlay = findCurrentView(R.id.terminal_overlay); + if (overlay != null) + overlay.startAnimation(fade_out_delayed); + } + + updatePromptVisible(); + } + } + + /** + * Save the currently shown {@link TerminalView} as the default. This is + * saved back down into {@link TerminalManager} where we can read it again + * later. + */ + private void updateDefault() { + // update the current default terminal + View view = findCurrentView(R.id.console_flip); + if (!(view instanceof TerminalView)) return; + + TerminalView terminal = (TerminalView) view; + if (bound == null) return; + bound.defaultBridge = terminal.bridge; + } + + protected void updateEmptyVisible() { + // update visibility of empty status message + empty.setVisibility((flip.getChildCount() == 0) ? View.VISIBLE : View.GONE); + } + + /** + * Show any prompts requested by the currently visible {@link TerminalView}. + */ + protected void updatePromptVisible() { + // check if our currently-visible terminalbridge is requesting any prompt services + View view = findCurrentView(R.id.console_flip); + + // Hide all the prompts in case a prompt request was canceled + hideAllPrompts(); + + if (!(view instanceof TerminalView)) { + // we dont have an active view, so hide any prompts + return; + } + + PromptHelper prompt = ((TerminalView) view).bridge.promptHelper; + if (String.class.equals(prompt.promptRequested)) { + stringPromptGroup.setVisibility(View.VISIBLE); + + String instructions = prompt.promptInstructions; + if (instructions != null && instructions.length() > 0) { + stringPromptInstructions.setVisibility(View.VISIBLE); + stringPromptInstructions.setText(instructions); + } else + stringPromptInstructions.setVisibility(View.GONE); + stringPrompt.setText(""); + stringPrompt.setHint(prompt.promptHint); + stringPrompt.requestFocus(); + + } else if (Boolean.class.equals(prompt.promptRequested)) { + booleanPromptGroup.setVisibility(View.VISIBLE); + booleanPrompt.setText(prompt.promptHint); + booleanYes.requestFocus(); + + } else { + hideAllPrompts(); + view.requestFocus(); + } + } + + private class URLItemListener implements OnItemClickListener { + private WeakReference<Context> contextRef; + + URLItemListener(Context context) { + this.contextRef = new WeakReference<Context>(context); + } + + public void onItemClick(AdapterView<?> arg0, View view, int position, long id) { + Context context = contextRef.get(); + + if (context == null) + return; + + try { + TextView urlView = (TextView) view; + + String url = urlView.getText().toString(); + if (url.indexOf("://") < 0) + url = "http://" + url; + + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(intent); + } catch (Exception e) { + Log.e(TAG, "couldn't open URL", e); + // We should probably tell the user that we couldn't find a handler... + } + } + + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + Log.d(TAG, String.format("onConfigurationChanged; requestedOrientation=%d, newConfig.orientation=%d", getRequestedOrientation(), newConfig.orientation)); + if (bound != null) { + if (forcedOrientation && + (newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE && + getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) || + (newConfig.orientation != Configuration.ORIENTATION_PORTRAIT && + getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)) + bound.setResizeAllowed(false); + else + bound.setResizeAllowed(true); + + bound.hardKeyboardHidden = (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES); + + mKeyboardButton.setVisibility(bound.hardKeyboardHidden ? View.VISIBLE : View.GONE); + } + } + + /** + * Adds a new TerminalBridge to the current set of views in our ViewFlipper. + * + * @param bridge TerminalBridge to add to our ViewFlipper + * @return the child index of the new view in the ViewFlipper + */ + private int addNewTerminalView(TerminalBridge bridge) { + // let them know about our prompt handler services + bridge.promptHelper.setHandler(promptHandler); + + // inflate each terminal view + RelativeLayout view = (RelativeLayout) inflater.inflate(R.layout.item_terminal, flip, false); + + // set the terminal overlay text + TextView overlay = (TextView) view.findViewById(R.id.terminal_overlay); + overlay.setText(bridge.host.getNickname()); + + // and add our terminal view control, using index to place behind overlay + TerminalView terminal = new TerminalView(ConsoleActivity.this, bridge); + terminal.setId(R.id.console_flip); + view.addView(terminal, 0); + + synchronized (flip) { + // finally attach to the flipper + flip.addView(view); + return flip.getChildCount() - 1; + } + } + + private int getFlipIndex(TerminalBridge bridge) { + synchronized (flip) { + final int children = flip.getChildCount(); + for (int i = 0; i < children; i++) { + final View view = flip.getChildAt(i).findViewById(R.id.console_flip); + + if (view == null || !(view instanceof TerminalView)) { + // How did that happen? + continue; + } + + final TerminalView tv = (TerminalView) view; + + if (tv.bridge == bridge) { + return i; + } + } + } + + return -1; + } + + /** + * Displays the child in the ViewFlipper at the requestedIndex and updates the prompts. + * + * @param requestedIndex the index of the terminal view to display + */ + private void setDisplayedTerminal(int requestedIndex) { + synchronized (flip) { + try { + // show the requested bridge if found, also fade out overlay + flip.setDisplayedChild(requestedIndex); + flip.getCurrentView().findViewById(R.id.terminal_overlay) + .startAnimation(fade_out_delayed); + } catch (NullPointerException npe) { + Log.d(TAG, "View went away when we were about to display it", npe); + } + + updatePromptVisible(); + updateEmptyVisible(); + } + } +} diff --git a/app/src/main/java/org/connectbot/GeneratePubkeyActivity.java b/app/src/main/java/org/connectbot/GeneratePubkeyActivity.java new file mode 100644 index 0000000..d7664c6 --- /dev/null +++ b/app/src/main/java/org/connectbot/GeneratePubkeyActivity.java @@ -0,0 +1,360 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; + +import org.connectbot.bean.PubkeyBean; +import org.connectbot.util.EntropyDialog; +import org.connectbot.util.EntropyView; +import org.connectbot.util.OnEntropyGatheredListener; +import org.connectbot.util.PubkeyDatabase; +import org.connectbot.util.PubkeyUtils; + +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.RadioGroup.OnCheckedChangeListener; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +import com.trilead.ssh2.signature.ECDSASHA2Verify; + +public class GeneratePubkeyActivity extends Activity implements OnEntropyGatheredListener { + /** + * + */ + private static final int RSA_MINIMUM_BITS = 768; + + public final static String TAG = "ConnectBot.GeneratePubkeyActivity"; + + final static int DEFAULT_BITS = 1024; + + final static int[] ECDSA_SIZES = ECDSASHA2Verify.getCurveSizes(); + + final static int ECDSA_DEFAULT_BITS = ECDSA_SIZES[0]; + + private LayoutInflater inflater = null; + + private EditText nickname; + private RadioGroup keyTypeGroup; + private SeekBar bitsSlider; + private EditText bitsText; + private CheckBox unlockAtStartup; + private CheckBox confirmUse; + private Button save; + private Dialog entropyDialog; + private ProgressDialog progress; + + private EditText password1, password2; + + private String keyType = PubkeyDatabase.KEY_TYPE_RSA; + private int minBits = 768; + private int bits = DEFAULT_BITS; + + private byte[] entropy; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setContentView(R.layout.act_generatepubkey); + + nickname = (EditText) findViewById(R.id.nickname); + + keyTypeGroup = (RadioGroup) findViewById(R.id.key_type); + + bitsText = (EditText) findViewById(R.id.bits); + bitsSlider = (SeekBar) findViewById(R.id.bits_slider); + + password1 = (EditText) findViewById(R.id.password1); + password2 = (EditText) findViewById(R.id.password2); + + unlockAtStartup = (CheckBox) findViewById(R.id.unlock_at_startup); + + confirmUse = (CheckBox) findViewById(R.id.confirm_use); + + save = (Button) findViewById(R.id.save); + + inflater = LayoutInflater.from(this); + + nickname.addTextChangedListener(textChecker); + password1.addTextChangedListener(textChecker); + password2.addTextChangedListener(textChecker); + + // TODO add BC to provide EC for devices that don't have it. + if (Security.getProviders("KeyPairGenerator.EC") == null) { + ((RadioButton) findViewById(R.id.ec)).setEnabled(false); + } + + keyTypeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { + + public void onCheckedChanged(RadioGroup group, int checkedId) { + if (checkedId == R.id.rsa) { + minBits = RSA_MINIMUM_BITS; + + bitsSlider.setEnabled(true); + bitsSlider.setProgress(DEFAULT_BITS - minBits); + + bitsText.setText(String.valueOf(DEFAULT_BITS)); + bitsText.setEnabled(true); + + keyType = PubkeyDatabase.KEY_TYPE_RSA; + } else if (checkedId == R.id.dsa) { + // DSA keys can only be 1024 bits + + bitsSlider.setEnabled(false); + bitsSlider.setProgress(DEFAULT_BITS - minBits); + + bitsText.setText(String.valueOf(DEFAULT_BITS)); + bitsText.setEnabled(false); + + keyType = PubkeyDatabase.KEY_TYPE_DSA; + } else if (checkedId == R.id.ec) { + minBits = ECDSA_DEFAULT_BITS; + + bitsSlider.setEnabled(true); + bitsSlider.setProgress(ECDSA_DEFAULT_BITS - minBits); + + bitsText.setText(String.valueOf(ECDSA_DEFAULT_BITS)); + bitsText.setEnabled(true); + + keyType = PubkeyDatabase.KEY_TYPE_EC; + } + } + }); + + bitsSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromTouch) { + if (PubkeyDatabase.KEY_TYPE_EC.equals(keyType)) { + bits = getClosestFieldSize(progress + minBits); + seekBar.setProgress(bits - minBits); + } else { + // Stay evenly divisible by 8 because it looks nicer to have + // 2048 than 2043 bits. + final int ourProgress = progress - (progress % 8); + bits = minBits + ourProgress; + } + + bitsText.setText(String.valueOf(bits)); + } + + public void onStartTrackingTouch(SeekBar seekBar) { + // We don't care about the start. + } + + public void onStopTrackingTouch(SeekBar seekBar) { + // We don't care about the stop. + } + }); + + bitsText.setOnFocusChangeListener(new OnFocusChangeListener() { + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + final boolean isEc = PubkeyDatabase.KEY_TYPE_EC.equals(keyType); + try { + bits = Integer.parseInt(bitsText.getText().toString()); + if (bits < minBits) { + bits = minBits; + bitsText.setText(String.valueOf(bits)); + } + if (isEc) { + bits = getClosestFieldSize(bits); + } + } catch (NumberFormatException nfe) { + bits = isEc ? ECDSA_DEFAULT_BITS : DEFAULT_BITS; + bitsText.setText(String.valueOf(bits)); + } + + bitsSlider.setProgress(bits - minBits); + } + } + }); + + save.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + GeneratePubkeyActivity.this.save.setEnabled(false); + + GeneratePubkeyActivity.this.startEntropyGather(); + } + }); + + } + + private void checkEntries() { + boolean allowSave = true; + + if (!password1.getText().toString().equals(password2.getText().toString())) + allowSave = false; + + if (nickname.getText().length() == 0) + allowSave = false; + + save.setEnabled(allowSave); + } + + private void startEntropyGather() { + final View entropyView = inflater.inflate(R.layout.dia_gatherentropy, null, false); + ((EntropyView) entropyView.findViewById(R.id.entropy)).addOnEntropyGatheredListener(GeneratePubkeyActivity.this); + entropyDialog = new EntropyDialog(GeneratePubkeyActivity.this, entropyView); + entropyDialog.show(); + } + + public void onEntropyGathered(byte[] entropy) { + // For some reason the entropy dialog was aborted, exit activity + if (entropy == null) { + finish(); + return; + } + + this.entropy = entropy.clone(); + + int numSetBits = 0; + for (int i = 0; i < 20; i++) + numSetBits += measureNumberOfSetBits(this.entropy[i]); + + Log.d(TAG, "Entropy distribution=" + (int) (100.0 * numSetBits / 160.0) + "%"); + + Log.d(TAG, "entropy gathered; attemping to generate key..."); + startKeyGen(); + } + + private void startKeyGen() { + progress = new ProgressDialog(GeneratePubkeyActivity.this); + progress.setMessage(GeneratePubkeyActivity.this.getResources().getText(R.string.pubkey_generating)); + progress.setIndeterminate(true); + progress.setCancelable(false); + progress.show(); + + Thread keyGenThread = new Thread(mKeyGen); + keyGenThread.setName("KeyGen"); + keyGenThread.start(); + } + + final private Runnable mKeyGen = new Runnable() { + public void run() { + try { + boolean encrypted = false; + + SecureRandom random = new SecureRandom(); + + // Work around JVM bug + random.nextInt(); + random.setSeed(entropy); + + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(keyType); + + keyPairGen.initialize(bits, random); + + KeyPair pair = keyPairGen.generateKeyPair(); + PrivateKey priv = pair.getPrivate(); + PublicKey pub = pair.getPublic(); + + String secret = password1.getText().toString(); + if (secret.length() > 0) + encrypted = true; + + Log.d(TAG, "private: " + PubkeyUtils.formatKey(priv)); + Log.d(TAG, "public: " + PubkeyUtils.formatKey(pub)); + + PubkeyBean pubkey = new PubkeyBean(); + pubkey.setNickname(nickname.getText().toString()); + pubkey.setType(keyType); + pubkey.setPrivateKey(PubkeyUtils.getEncodedPrivate(priv, secret)); + pubkey.setPublicKey(pub.getEncoded()); + pubkey.setEncrypted(encrypted); + pubkey.setStartup(unlockAtStartup.isChecked()); + pubkey.setConfirmUse(confirmUse.isChecked()); + + PubkeyDatabase pubkeydb = new PubkeyDatabase(GeneratePubkeyActivity.this); + pubkeydb.savePubkey(pubkey); + pubkeydb.close(); + } catch (Exception e) { + Log.e(TAG, "Could not generate key pair"); + + e.printStackTrace(); + } + + GeneratePubkeyActivity.this.runOnUiThread(new Runnable() { + public void run() { + progress.dismiss(); + GeneratePubkeyActivity.this.finish(); + } + }); + } + + }; + + final private TextWatcher textChecker = new TextWatcher() { + public void afterTextChanged(Editable s) {} + + public void beforeTextChanged(CharSequence s, int start, int count, + int after) {} + + public void onTextChanged(CharSequence s, int start, int before, + int count) { + checkEntries(); + } + }; + + private int measureNumberOfSetBits(byte b) { + int numSetBits = 0; + + for (int i = 0; i < 8; i++) { + if ((b & 1) == 1) + numSetBits++; + b >>= 1; + } + + return numSetBits; + } + + private int getClosestFieldSize(int bits) { + int outBits = ECDSA_DEFAULT_BITS; + int distance = Math.abs(bits - ECDSA_DEFAULT_BITS); + + for (int i = 1; i < ECDSA_SIZES.length; i++) { + int thisDistance = Math.abs(bits - ECDSA_SIZES[i]); + if (thisDistance < distance) { + distance = thisDistance; + outBits = ECDSA_SIZES[i]; + } + } + return outBits; + } +} diff --git a/app/src/main/java/org/connectbot/HelpActivity.java b/app/src/main/java/org/connectbot/HelpActivity.java new file mode 100644 index 0000000..f56a1c0 --- /dev/null +++ b/app/src/main/java/org/connectbot/HelpActivity.java @@ -0,0 +1,77 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.io.IOException; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.AssetManager; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.LinearLayout; + +/** + * @author Kenny Root + * + */ +public class HelpActivity extends Activity { + public final static String TAG = "ConnectBot.HelpActivity"; + + public final static String HELPDIR = "help"; + public final static String SUFFIX = ".html"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_help); + + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_help))); + + AssetManager am = this.getAssets(); + LinearLayout content = (LinearLayout) findViewById(R.id.topics); + + try { + for (String name : am.list(HELPDIR)) { + if (name.endsWith(SUFFIX)) { + Button button = new Button(this); + final String topic = name.substring(0, name.length() - SUFFIX.length()); + button.setText(topic); + + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(HelpActivity.this, HelpTopicActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, topic); + HelpActivity.this.startActivity(intent); + } + }); + + content.addView(button); + } + } + } catch (IOException e) { + // TODO Auto-generated catch block + Log.e(TAG, "couldn't get list of help assets", e); + } + } +} diff --git a/app/src/main/java/org/connectbot/HelpTopicActivity.java b/app/src/main/java/org/connectbot/HelpTopicActivity.java new file mode 100644 index 0000000..a5fa8e0 --- /dev/null +++ b/app/src/main/java/org/connectbot/HelpTopicActivity.java @@ -0,0 +1,49 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import org.connectbot.util.HelpTopicView; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +/** + * @author Kenny Root + * + */ +public class HelpTopicActivity extends Activity { + public final static String TAG = "ConnectBot.HelpActivity"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_help_topic); + + String topic = getIntent().getStringExtra(Intent.EXTRA_TITLE); + + this.setTitle(String.format("%s: %s - %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_help), + topic)); + + HelpTopicView helpTopic = (HelpTopicView) findViewById(R.id.topic_text); + + helpTopic.setTopic(topic); + } +} diff --git a/app/src/main/java/org/connectbot/HostEditorActivity.java b/app/src/main/java/org/connectbot/HostEditorActivity.java new file mode 100644 index 0000000..d1e5487 --- /dev/null +++ b/app/src/main/java/org/connectbot/HostEditorActivity.java @@ -0,0 +1,433 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.connectbot.bean.HostBean; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalManager; +import org.connectbot.util.HostDatabase; +import org.connectbot.util.PubkeyDatabase; + +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.os.IBinder; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.util.Log; + +public class HostEditorActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { + public class CursorPreferenceHack implements SharedPreferences { + protected final String table; + protected final long id; + + protected Map<String, String> values = new HashMap<String, String>(); +// protected Map<String, String> pubkeys = new HashMap<String, String>(); + + public CursorPreferenceHack(String table, long id) { + this.table = table; + this.id = id; + + cacheValues(); + } + + protected final void cacheValues() { + // fill a cursor and cache the values locally + // this makes sure we dont have any floating cursor to dispose later + + SQLiteDatabase db = hostdb.getReadableDatabase(); + Cursor cursor = db.query(table, null, "_id = ?", + new String[] { String.valueOf(id) }, null, null, null); + + if (cursor.moveToFirst()) { + for (int i = 0; i < cursor.getColumnCount(); i++) { + String key = cursor.getColumnName(i); + if (key.equals(HostDatabase.FIELD_HOST_HOSTKEY)) continue; + String value = cursor.getString(i); + values.put(key, value); + } + } + cursor.close(); + db.close(); + +// db = pubkeydb.getReadableDatabase(); +// cursor = db.query(PubkeyDatabase.TABLE_PUBKEYS, +// new String[] { "_id", PubkeyDatabase.FIELD_PUBKEY_NICKNAME }, +// null, null, null, null, null); +// +// if (cursor.moveToFirst()) { +// do { +// String pubkeyid = String.valueOf(cursor.getLong(0)); +// String value = cursor.getString(1); +// pubkeys.put(pubkeyid, value); +// } while (cursor.moveToNext()); +// } +// +// cursor.close(); +// db.close(); + } + + public boolean contains(String key) { + return values.containsKey(key); + } + + public class Editor implements SharedPreferences.Editor { + + private ContentValues update = new ContentValues(); + + public SharedPreferences.Editor clear() { + Log.d(this.getClass().toString(), "clear()"); + update = new ContentValues(); + return this; + } + + public boolean commit() { + //Log.d(this.getClass().toString(), "commit() changes back to database"); + SQLiteDatabase db = hostdb.getWritableDatabase(); + db.update(table, update, "_id = ?", new String[] { String.valueOf(id) }); + db.close(); + + // make sure we refresh the parent cached values + cacheValues(); + + // and update any listeners + for (OnSharedPreferenceChangeListener listener : listeners) { + listener.onSharedPreferenceChanged(CursorPreferenceHack.this, null); + } + + return true; + } + + // Gingerbread compatibility + public void apply() { + commit(); + } + + public android.content.SharedPreferences.Editor putBoolean(String key, boolean value) { + return this.putString(key, Boolean.toString(value)); + } + + public android.content.SharedPreferences.Editor putFloat(String key, float value) { + return this.putString(key, Float.toString(value)); + } + + public android.content.SharedPreferences.Editor putInt(String key, int value) { + return this.putString(key, Integer.toString(value)); + } + + public android.content.SharedPreferences.Editor putLong(String key, long value) { + return this.putString(key, Long.toString(value)); + } + + public android.content.SharedPreferences.Editor putString(String key, String value) { + //Log.d(this.getClass().toString(), String.format("Editor.putString(key=%s, value=%s)", key, value)); + update.put(key, value); + return this; + } + + public android.content.SharedPreferences.Editor remove(String key) { + //Log.d(this.getClass().toString(), String.format("Editor.remove(key=%s)", key)); + update.remove(key); + return this; + } + + public android.content.SharedPreferences.Editor putStringSet(String key, Set<String> value) { + throw new UnsupportedOperationException("HostEditor Prefs do not support Set<String>"); + } + } + + + public Editor edit() { + //Log.d(this.getClass().toString(), "edit()"); + return new Editor(); + } + + public Map<String, ?> getAll() { + return values; + } + + public boolean getBoolean(String key, boolean defValue) { + return Boolean.valueOf(this.getString(key, Boolean.toString(defValue))); + } + + public float getFloat(String key, float defValue) { + return Float.valueOf(this.getString(key, Float.toString(defValue))); + } + + public int getInt(String key, int defValue) { + return Integer.valueOf(this.getString(key, Integer.toString(defValue))); + } + + public long getLong(String key, long defValue) { + return Long.valueOf(this.getString(key, Long.toString(defValue))); + } + + public String getString(String key, String defValue) { + //Log.d(this.getClass().toString(), String.format("getString(key=%s, defValue=%s)", key, defValue)); + + if (!values.containsKey(key)) return defValue; + return values.get(key); + } + + public Set<String> getStringSet(String key, Set<String> defValue) { + throw new ClassCastException("HostEditor Prefs do not support Set<String>"); + } + + protected List<OnSharedPreferenceChangeListener> listeners = new LinkedList<OnSharedPreferenceChangeListener>(); + + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + listeners.add(listener); + } + + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + listeners.remove(listener); + } + + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + //Log.d(this.getClass().toString(), String.format("getSharedPreferences(name=%s)", name)); + return this.pref; + } + + protected static final String TAG = "ConnectBot.HostEditorActivity"; + + protected HostDatabase hostdb = null; + private PubkeyDatabase pubkeydb = null; + + private CursorPreferenceHack pref; + private ServiceConnection connection; + + private HostBean host; + protected TerminalBridge hostBridge; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + long hostId = this.getIntent().getLongExtra(Intent.EXTRA_TITLE, -1); + + // TODO: we could pass through a specific ContentProvider uri here + //this.getPreferenceManager().setSharedPreferencesName(uri); + + this.hostdb = new HostDatabase(this); + this.pubkeydb = new PubkeyDatabase(this); + + host = hostdb.findHostById(hostId); + + connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + TerminalManager bound = ((TerminalManager.TerminalBinder) service).getService(); + + hostBridge = bound.getConnectedBridge(host); + } + + public void onServiceDisconnected(ComponentName name) { + hostBridge = null; + } + }; + + this.pref = new CursorPreferenceHack(HostDatabase.TABLE_HOSTS, hostId); + this.pref.registerOnSharedPreferenceChangeListener(this); + + this.addPreferencesFromResource(R.xml.host_prefs); + + // add all existing pubkeys to our listpreference for user to choose from + // TODO: may be an issue here when this activity is recycled after adding a new pubkey + // TODO: should consider moving into onStart, but we dont have a good way of resetting the listpref after filling once + ListPreference pubkeyPref = (ListPreference) findPreference(HostDatabase.FIELD_HOST_PUBKEYID); + + List<CharSequence> pubkeyNicks = new LinkedList<CharSequence>(Arrays.asList(pubkeyPref.getEntries())); + pubkeyNicks.addAll(pubkeydb.allValues(PubkeyDatabase.FIELD_PUBKEY_NICKNAME)); + pubkeyPref.setEntries(pubkeyNicks.toArray(new CharSequence[pubkeyNicks.size()])); + + List<CharSequence> pubkeyIds = new LinkedList<CharSequence>(Arrays.asList(pubkeyPref.getEntryValues())); + pubkeyIds.addAll(pubkeydb.allValues("_id")); + pubkeyPref.setEntryValues(pubkeyIds.toArray(new CharSequence[pubkeyIds.size()])); + + // Populate the character set encoding list with all available + final ListPreference charsetPref = (ListPreference) findPreference(HostDatabase.FIELD_HOST_ENCODING); + + if (CharsetHolder.isInitialized()) { + initCharsetPref(charsetPref); + } else { + String[] currentCharsetPref = new String[1]; + currentCharsetPref[0] = charsetPref.getValue(); + charsetPref.setEntryValues(currentCharsetPref); + charsetPref.setEntries(currentCharsetPref); + + new Thread(new Runnable() { + public void run() { + initCharsetPref(charsetPref); + } + }).start(); + } + + this.updateSummaries(); + } + + @Override + public void onStart() { + super.onStart(); + + bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (this.hostdb == null) + this.hostdb = new HostDatabase(this); + + if (this.pubkeydb == null) + this.pubkeydb = new PubkeyDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + + unbindService(connection); + + if (this.hostdb != null) { + this.hostdb.close(); + this.hostdb = null; + } + + if (this.pubkeydb != null) { + this.pubkeydb.close(); + this.pubkeydb = null; + } + } + + private void updateSummaries() { + // for all text preferences, set hint as current database value + for (String key : this.pref.values.keySet()) { + if (key.equals(HostDatabase.FIELD_HOST_POSTLOGIN)) continue; + Preference pref = this.findPreference(key); + if (pref == null) continue; + if (pref instanceof CheckBoxPreference) continue; + CharSequence value = this.pref.getString(key, ""); + + if (key.equals(HostDatabase.FIELD_HOST_PUBKEYID)) { + try { + int pubkeyId = Integer.parseInt((String) value); + if (pubkeyId >= 0) + pref.setSummary(pubkeydb.getNickname(pubkeyId)); + else if (pubkeyId == HostDatabase.PUBKEYID_ANY) + pref.setSummary(R.string.list_pubkeyids_any); + else if (pubkeyId == HostDatabase.PUBKEYID_NEVER) + pref.setSummary(R.string.list_pubkeyids_none); + continue; + } catch (NumberFormatException nfe) { + // Fall through. + } + } else if (pref instanceof ListPreference) { + ListPreference listPref = (ListPreference) pref; + int entryIndex = listPref.findIndexOfValue((String) value); + if (entryIndex >= 0) + value = listPref.getEntries()[entryIndex]; + } + + pref.setSummary(value); + } + + } + + private void initCharsetPref(final ListPreference charsetPref) { + charsetPref.setEntryValues(CharsetHolder.getCharsetIds()); + charsetPref.setEntries(CharsetHolder.getCharsetNames()); + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + // update values on changed preference + this.updateSummaries(); + + // Our CursorPreferenceHack always send null keys, so try to set charset anyway + if (hostBridge != null) + hostBridge.setCharset(sharedPreferences + .getString(HostDatabase.FIELD_HOST_ENCODING, HostDatabase.ENCODING_DEFAULT)); + } + + public static class CharsetHolder { + private static boolean initialized = false; + + private static CharSequence[] charsetIds; + private static CharSequence[] charsetNames; + + public static CharSequence[] getCharsetNames() { + if (charsetNames == null) + initialize(); + + return charsetNames; + } + + public static CharSequence[] getCharsetIds() { + if (charsetIds == null) + initialize(); + + return charsetIds; + } + + private synchronized static void initialize() { + if (initialized) + return; + + List<CharSequence> charsetIdsList = new LinkedList<CharSequence>(); + List<CharSequence> charsetNamesList = new LinkedList<CharSequence>(); + + for (Entry<String, Charset> entry : Charset.availableCharsets().entrySet()) { + Charset c = entry.getValue(); + if (c.canEncode() && c.isRegistered()) { + String key = entry.getKey(); + if (key.startsWith("cp")) { + // Custom CP437 charset changes + charsetIdsList.add("CP437"); + charsetNamesList.add("CP437"); + } + charsetIdsList.add(entry.getKey()); + charsetNamesList.add(c.displayName()); + } + } + + charsetIds = charsetIdsList.toArray(new CharSequence[charsetIdsList.size()]); + charsetNames = charsetNamesList.toArray(new CharSequence[charsetNamesList.size()]); + + initialized = true; + } + + public static boolean isInitialized() { + return initialized; + } + } +} diff --git a/app/src/main/java/org/connectbot/HostListActivity.java b/app/src/main/java/org/connectbot/HostListActivity.java new file mode 100644 index 0000000..331447a --- /dev/null +++ b/app/src/main/java/org/connectbot/HostListActivity.java @@ -0,0 +1,585 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.util.List; + +import org.connectbot.bean.HostBean; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalManager; +import org.connectbot.transport.TransportFactory; +import org.connectbot.util.HostDatabase; +import org.connectbot.util.PreferenceConstants; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.Intent.ShortcutIconResource; +import android.content.SharedPreferences.Editor; +import android.content.res.ColorStateList; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View.OnKeyListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +public class HostListActivity extends ListActivity { + public final static int REQUEST_EDIT = 1; + + public final static int REQUEST_EULA = 2; + + protected TerminalManager bound = null; + + protected HostDatabase hostdb; + private List<HostBean> hosts; + protected LayoutInflater inflater = null; + + protected boolean sortedByColor = false; + + private MenuItem sortcolor; + + private MenuItem sortlast; + + private Spinner transportSpinner; + private TextView quickconnect; + + private SharedPreferences prefs = null; + + protected boolean makingShortcut = false; + + protected Handler updateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + HostListActivity.this.updateList(); + } + }; + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = ((TerminalManager.TerminalBinder) service).getService(); + + // update our listview binder to find the service + HostListActivity.this.updateList(); + } + + public void onServiceDisconnected(ComponentName className) { + bound = null; + HostListActivity.this.updateList(); + } + }; + + @Override + public void onStart() { + super.onStart(); + + // start the terminal manager service + this.bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (this.hostdb == null) + this.hostdb = new HostDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + this.unbindService(connection); + + if (this.hostdb != null) { + this.hostdb.close(); + this.hostdb = null; + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_EULA) { + if (resultCode == Activity.RESULT_OK) { + // yay they agreed, so store that info + Editor edit = prefs.edit(); + edit.putBoolean(PreferenceConstants.EULA, true); + edit.commit(); + } else { + // user didnt agree, so close + this.finish(); + } + } else if (requestCode == REQUEST_EDIT) { + this.updateList(); + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_hostlist); + + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_hosts_list))); + + this.prefs = PreferenceManager.getDefaultSharedPreferences(this); + + // detect HTC Dream and apply special preferences + if (Build.MANUFACTURER.equals("HTC") && Build.DEVICE.equals("dream")) { + SharedPreferences.Editor editor = prefs.edit(); + boolean doCommit = false; + if (!prefs.contains(PreferenceConstants.SHIFT_FKEYS) && + !prefs.contains(PreferenceConstants.CTRL_FKEYS)) { + editor.putBoolean(PreferenceConstants.SHIFT_FKEYS, true); + editor.putBoolean(PreferenceConstants.CTRL_FKEYS, true); + doCommit = true; + } + if (!prefs.contains(PreferenceConstants.STICKY_MODIFIERS)) { + editor.putString(PreferenceConstants.STICKY_MODIFIERS, PreferenceConstants.YES); + doCommit = true; + } + if (!prefs.contains(PreferenceConstants.KEYMODE)) { + editor.putString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT); + doCommit = true; + } + if (doCommit) { + editor.commit(); + } + } + + // check for eula agreement + boolean agreed = prefs.getBoolean(PreferenceConstants.EULA, false); + if (!agreed) { + this.startActivityForResult(new Intent(this, WizardActivity.class), REQUEST_EULA); + } + + this.makingShortcut = Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction()) + || Intent.ACTION_PICK.equals(getIntent().getAction()); + + // connect with hosts database and populate list + this.hostdb = new HostDatabase(this); + ListView list = this.getListView(); + + this.sortedByColor = prefs.getBoolean(PreferenceConstants.SORT_BY_COLOR, false); + + //this.list.setSelector(R.drawable.highlight_disabled_pressed); + + list.setOnItemClickListener(new OnItemClickListener() { + + public synchronized void onItemClick(AdapterView<?> parent, View view, int position, long id) { + + // launch off to console details + HostBean host = (HostBean) parent.getAdapter().getItem(position); + Uri uri = host.getUri(); + + Intent contents = new Intent(Intent.ACTION_VIEW, uri); + contents.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + if (makingShortcut) { + // create shortcut if requested + ShortcutIconResource icon = Intent.ShortcutIconResource.fromContext(HostListActivity.this, R.drawable.icon); + + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, contents); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, host.getNickname()); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon); + + setResult(RESULT_OK, intent); + finish(); + + } else { + // otherwise just launch activity to show this host + HostListActivity.this.startActivity(contents); + } + } + }); + + this.registerForContextMenu(list); + + quickconnect = (TextView) this.findViewById(R.id.front_quickconnect); + quickconnect.setVisibility(makingShortcut ? View.GONE : View.VISIBLE); + quickconnect.setOnKeyListener(new OnKeyListener() { + + public boolean onKey(View v, int keyCode, KeyEvent event) { + + if (event.getAction() == KeyEvent.ACTION_UP) return false; + if (keyCode != KeyEvent.KEYCODE_ENTER) return false; + + return startConsoleActivity(); + } + }); + + transportSpinner = (Spinner) findViewById(R.id.transport_selection); + transportSpinner.setVisibility(makingShortcut ? View.GONE : View.VISIBLE); + ArrayAdapter<String> transportSelection = new ArrayAdapter<String>(this, + android.R.layout.simple_spinner_item, TransportFactory.getTransportNames()); + transportSelection.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + transportSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + public void onItemSelected(AdapterView<?> arg0, View view, int position, long id) { + String formatHint = TransportFactory.getFormatHint( + (String) transportSpinner.getSelectedItem(), + HostListActivity.this); + + quickconnect.setHint(formatHint); + quickconnect.setError(null); + quickconnect.requestFocus(); + } + public void onNothingSelected(AdapterView<?> arg0) { } + }); + transportSpinner.setAdapter(transportSelection); + + this.inflater = LayoutInflater.from(this); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + // don't offer menus when creating shortcut + if (makingShortcut) return true; + + sortcolor.setVisible(!sortedByColor); + sortlast.setVisible(sortedByColor); + + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + // don't offer menus when creating shortcut + if (makingShortcut) return true; + + // add host, ssh keys, about + sortcolor = menu.add(R.string.list_menu_sortcolor); + sortcolor.setIcon(android.R.drawable.ic_menu_share); + sortcolor.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + sortedByColor = true; + updateList(); + return true; + } + }); + + sortlast = menu.add(R.string.list_menu_sortname); + sortlast.setIcon(android.R.drawable.ic_menu_share); + sortlast.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + sortedByColor = false; + updateList(); + return true; + } + }); + + MenuItem keys = menu.add(R.string.list_menu_pubkeys); + keys.setIcon(android.R.drawable.ic_lock_lock); + keys.setIntent(new Intent(HostListActivity.this, PubkeyListActivity.class)); + + MenuItem colors = menu.add(R.string.title_colors); + colors.setIcon(android.R.drawable.ic_menu_slideshow); + colors.setIntent(new Intent(HostListActivity.this, ColorsActivity.class)); + + MenuItem settings = menu.add(R.string.list_menu_settings); + settings.setIcon(android.R.drawable.ic_menu_preferences); + settings.setIntent(new Intent(HostListActivity.this, SettingsActivity.class)); + + MenuItem help = menu.add(R.string.title_help); + help.setIcon(android.R.drawable.ic_menu_help); + help.setIntent(new Intent(HostListActivity.this, HelpActivity.class)); + + return true; + + } + + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + + // create menu to handle hosts + + // create menu to handle deleting and sharing lists + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final HostBean host = (HostBean) this.getListView().getItemAtPosition(info.position); + + menu.setHeaderTitle(host.getNickname()); + + // edit, disconnect, delete + MenuItem connect = menu.add(R.string.list_host_disconnect); + TerminalBridge bridge = null; + if (bound != null) { + bridge = bound.getConnectedBridge(host); + } + connect.setEnabled(bridge != null); + connect.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + bridge.dispatchDisconnect(true); + updateHandler.sendEmptyMessage(-1); + return true; + } + }); + + MenuItem edit = menu.add(R.string.list_host_edit); + edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(HostListActivity.this, HostEditorActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, host.getId()); + HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); + + MenuItem portForwards = menu.add(R.string.list_host_portforwards); + portForwards.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(HostListActivity.this, PortForwardListActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, host.getId()); + HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); + if (!TransportFactory.canForwardPorts(host.getProtocol())) + portForwards.setEnabled(false); + + MenuItem delete = menu.add(R.string.list_host_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(HostListActivity.this) + .setMessage(getString(R.string.delete_message, host.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // make sure we disconnect + if (bridge != null) + bridge.dispatchDisconnect(true); + + hostdb.deleteHost(host); + updateHandler.sendEmptyMessage(-1); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + + return true; + } + }); + } + + /** + * @param text + * @return + */ + private boolean startConsoleActivity() { + Uri uri = TransportFactory.getUri((String) transportSpinner + .getSelectedItem(), quickconnect.getText().toString()); + + if (uri == null) { + quickconnect.setError(getString(R.string.list_format_error, + TransportFactory.getFormatHint( + (String) transportSpinner.getSelectedItem(), + HostListActivity.this))); + return false; + } + + HostBean host = TransportFactory.findHost(hostdb, uri); + if (host == null) { + host = TransportFactory.getTransport(uri.getScheme()).createHost(uri); + host.setColor(HostDatabase.COLOR_GRAY); + host.setPubkeyId(HostDatabase.PUBKEYID_ANY); + hostdb.saveHost(host); + } + + Intent intent = new Intent(HostListActivity.this, ConsoleActivity.class); + intent.setData(uri); + startActivity(intent); + + return true; + } + + protected void updateList() { + if (prefs.getBoolean(PreferenceConstants.SORT_BY_COLOR, false) != sortedByColor) { + Editor edit = prefs.edit(); + edit.putBoolean(PreferenceConstants.SORT_BY_COLOR, sortedByColor); + edit.commit(); + } + + if (hostdb == null) + hostdb = new HostDatabase(this); + + hosts = hostdb.getHosts(sortedByColor); + + // Don't lose hosts that are connected via shortcuts but not in the database. + if (bound != null) { + for (TerminalBridge bridge : bound.bridges) { + if (!hosts.contains(bridge.host)) + hosts.add(0, bridge.host); + } + } + + HostAdapter adapter = new HostAdapter(this, hosts, bound); + + this.setListAdapter(adapter); + } + + class HostAdapter extends ArrayAdapter<HostBean> { + private List<HostBean> hosts; + private final TerminalManager manager; + private final ColorStateList red, green, blue; + + public final static int STATE_UNKNOWN = 1, STATE_CONNECTED = 2, STATE_DISCONNECTED = 3; + + class ViewHolder { + public TextView nickname; + public TextView caption; + public ImageView icon; + } + + public HostAdapter(Context context, List<HostBean> hosts, TerminalManager manager) { + super(context, R.layout.item_host, hosts); + + this.hosts = hosts; + this.manager = manager; + + red = context.getResources().getColorStateList(R.color.red); + green = context.getResources().getColorStateList(R.color.green); + blue = context.getResources().getColorStateList(R.color.blue); + } + + /** + * Check if we're connected to a terminal with the given host. + */ + private int getConnectedState(HostBean host) { + // always disconnected if we dont have backend service + if (this.manager == null) + return STATE_UNKNOWN; + + if (manager.getConnectedBridge(host) != null) + return STATE_CONNECTED; + + if (manager.disconnected.contains(host)) + return STATE_DISCONNECTED; + + return STATE_UNKNOWN; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = inflater.inflate(R.layout.item_host, null, false); + + holder = new ViewHolder(); + + holder.nickname = (TextView) convertView.findViewById(android.R.id.text1); + holder.caption = (TextView) convertView.findViewById(android.R.id.text2); + holder.icon = (ImageView) convertView.findViewById(android.R.id.icon); + + convertView.setTag(holder); + } else + holder = (ViewHolder) convertView.getTag(); + + HostBean host = hosts.get(position); + if (host == null) { + // Well, something bad happened. We can't continue. + Log.e("HostAdapter", "Host bean is null!"); + + holder.nickname.setText("Error during lookup"); + holder.caption.setText("see 'adb logcat' for more"); + return convertView; + } + + holder.nickname.setText(host.getNickname()); + + switch (this.getConnectedState(host)) { + case STATE_UNKNOWN: + holder.icon.setImageState(new int[] { }, true); + break; + case STATE_CONNECTED: + holder.icon.setImageState(new int[] { android.R.attr.state_checked }, true); + break; + case STATE_DISCONNECTED: + holder.icon.setImageState(new int[] { android.R.attr.state_expanded }, true); + break; + } + + ColorStateList chosen = null; + if (HostDatabase.COLOR_RED.equals(host.getColor())) + chosen = this.red; + else if (HostDatabase.COLOR_GREEN.equals(host.getColor())) + chosen = this.green; + else if (HostDatabase.COLOR_BLUE.equals(host.getColor())) + chosen = this.blue; + + Context context = convertView.getContext(); + + if (chosen != null) { + // set color normally if not selected + holder.nickname.setTextColor(chosen); + holder.caption.setTextColor(chosen); + } else { + // selected, so revert back to default black text + holder.nickname.setTextAppearance(context, android.R.attr.textAppearanceLarge); + holder.caption.setTextAppearance(context, android.R.attr.textAppearanceSmall); + } + + long now = System.currentTimeMillis() / 1000; + + String nice = context.getString(R.string.bind_never); + if (host.getLastConnect() > 0) { + int minutes = (int) ((now - host.getLastConnect()) / 60); + if (minutes >= 60) { + int hours = (minutes / 60); + if (hours >= 24) { + int days = (hours / 24); + nice = context.getString(R.string.bind_days, days); + } else + nice = context.getString(R.string.bind_hours, hours); + } else + nice = context.getString(R.string.bind_minutes, minutes); + } + + holder.caption.setText(nice); + + return convertView; + } + } +} diff --git a/app/src/main/java/org/connectbot/PortForwardListActivity.java b/app/src/main/java/org/connectbot/PortForwardListActivity.java new file mode 100644 index 0000000..ff4f7d2 --- /dev/null +++ b/app/src/main/java/org/connectbot/PortForwardListActivity.java @@ -0,0 +1,425 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.util.List; + +import org.connectbot.bean.HostBean; +import org.connectbot.bean.PortForwardBean; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalManager; +import org.connectbot.util.HostDatabase; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.res.Resources; +import android.database.SQLException; +import android.graphics.Paint; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.MenuItem.OnMenuItemClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; + +/** + * List all portForwards for a particular host and provide a way for users to add more portForwards, + * edit existing portForwards, and delete portForwards. + * + * @author Kenny Root + */ +public class PortForwardListActivity extends ListActivity { + public final static String TAG = "ConnectBot.PortForwardListActivity"; + + private static final int LISTENER_CYCLE_TIME = 500; + + protected HostDatabase hostdb; + + private List<PortForwardBean> portForwards; + + private ServiceConnection connection = null; + protected TerminalBridge hostBridge = null; + protected LayoutInflater inflater = null; + + private HostBean host; + + @Override + public void onStart() { + super.onStart(); + + this.bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (this.hostdb == null) + this.hostdb = new HostDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + + this.unbindService(connection); + + if (this.hostdb != null) { + this.hostdb.close(); + this.hostdb = null; + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + long hostId = this.getIntent().getLongExtra(Intent.EXTRA_TITLE, -1); + + setContentView(R.layout.act_portforwardlist); + + // connect with hosts database and populate list + this.hostdb = new HostDatabase(this); + host = hostdb.findHostById(hostId); + + { + final String nickname = host != null ? host.getNickname() : null; + final Resources resources = getResources(); + + if (nickname != null) { + this.setTitle(String.format("%s: %s (%s)", + resources.getText(R.string.app_name), + resources.getText(R.string.title_port_forwards_list), + nickname)); + } else { + this.setTitle(String.format("%s: %s", + resources.getText(R.string.app_name), + resources.getText(R.string.title_port_forwards_list))); + } + } + + connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + TerminalManager bound = ((TerminalManager.TerminalBinder) service).getService(); + + hostBridge = bound.getConnectedBridge(host); + updateHandler.sendEmptyMessage(-1); + } + + public void onServiceDisconnected(ComponentName name) { + hostBridge = null; + } + }; + + this.updateList(); + + this.registerForContextMenu(this.getListView()); + + this.getListView().setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { + ListView lv = PortForwardListActivity.this.getListView(); + PortForwardBean pfb = (PortForwardBean) lv.getItemAtPosition(position); + + if (hostBridge != null) { + if (pfb.isEnabled()) + hostBridge.disablePortForward(pfb); + else { + if (!hostBridge.enablePortForward(pfb)) + Toast.makeText(PortForwardListActivity.this, getString(R.string.portforward_problem), Toast.LENGTH_LONG).show(); + } + + updateHandler.sendEmptyMessage(-1); + } + } + }); + + this.inflater = LayoutInflater.from(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + MenuItem add = menu.add(R.string.portforward_menu_add); + add.setIcon(android.R.drawable.ic_menu_add); + add.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // build dialog to prompt user about updating + final View portForwardView = inflater.inflate(R.layout.dia_portforward, null, false); + final EditText destEdit = (EditText) portForwardView.findViewById(R.id.portforward_destination); + final Spinner typeSpinner = (Spinner) portForwardView.findViewById(R.id.portforward_type); + + typeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + public void onItemSelected(AdapterView<?> value, View view, + int position, long id) { + destEdit.setEnabled(position != 2); + } + public void onNothingSelected(AdapterView<?> arg0) { + } + }); + + new AlertDialog.Builder(PortForwardListActivity.this) + .setView(portForwardView) + .setPositiveButton(R.string.portforward_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + final EditText nicknameEdit = (EditText) portForwardView.findViewById(R.id.nickname); + final EditText sourcePortEdit = (EditText) portForwardView.findViewById(R.id.portforward_source); + + String type = HostDatabase.PORTFORWARD_LOCAL; + switch (typeSpinner.getSelectedItemPosition()) { + case 0: + type = HostDatabase.PORTFORWARD_LOCAL; + break; + case 1: + type = HostDatabase.PORTFORWARD_REMOTE; + break; + case 2: + type = HostDatabase.PORTFORWARD_DYNAMIC5; + break; + } + + PortForwardBean pfb = new PortForwardBean( + host != null ? host.getId() : -1, + nicknameEdit.getText().toString(), type, + sourcePortEdit.getText().toString(), + destEdit.getText().toString()); + + if (hostBridge != null) { + hostBridge.addPortForward(pfb); + hostBridge.enablePortForward(pfb); + } + + if (host != null && !hostdb.savePortForward(pfb)) + throw new SQLException("Could not save port forward"); + + updateHandler.sendEmptyMessage(-1); + } catch (Exception e) { + Log.e(TAG, "Could not update port forward", e); + // TODO Show failure dialog. + } + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + + return true; + } + }); + + return true; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // Create menu to handle deleting and editing port forward + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final PortForwardBean pfb = (PortForwardBean) this.getListView().getItemAtPosition(info.position); + + menu.setHeaderTitle(pfb.getNickname()); + + MenuItem edit = menu.add(R.string.portforward_edit); + edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final View editTunnelView = inflater.inflate(R.layout.dia_portforward, null, false); + + final Spinner typeSpinner = (Spinner) editTunnelView.findViewById(R.id.portforward_type); + if (HostDatabase.PORTFORWARD_LOCAL.equals(pfb.getType())) + typeSpinner.setSelection(0); + else if (HostDatabase.PORTFORWARD_REMOTE.equals(pfb.getType())) + typeSpinner.setSelection(1); + else + typeSpinner.setSelection(2); + + final EditText nicknameEdit = (EditText) editTunnelView.findViewById(R.id.nickname); + nicknameEdit.setText(pfb.getNickname()); + + final EditText sourcePortEdit = (EditText) editTunnelView.findViewById(R.id.portforward_source); + sourcePortEdit.setText(String.valueOf(pfb.getSourcePort())); + + final EditText destEdit = (EditText) editTunnelView.findViewById(R.id.portforward_destination); + if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(pfb.getType())) { + destEdit.setEnabled(false); + } else { + destEdit.setText(String.format("%s:%d", pfb.getDestAddr(), pfb.getDestPort())); + } + + typeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + public void onItemSelected(AdapterView<?> value, View view, + int position, long id) { + destEdit.setEnabled(position != 2); + } + public void onNothingSelected(AdapterView<?> arg0) { + } + }); + + new AlertDialog.Builder(PortForwardListActivity.this) + .setView(editTunnelView) + .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + if (hostBridge != null) + hostBridge.disablePortForward(pfb); + + pfb.setNickname(nicknameEdit.getText().toString()); + + switch (typeSpinner.getSelectedItemPosition()) { + case 0: + pfb.setType(HostDatabase.PORTFORWARD_LOCAL); + break; + case 1: + pfb.setType(HostDatabase.PORTFORWARD_REMOTE); + break; + case 2: + pfb.setType(HostDatabase.PORTFORWARD_DYNAMIC5); + break; + } + + pfb.setSourcePort(Integer.parseInt(sourcePortEdit.getText().toString())); + pfb.setDest(destEdit.getText().toString()); + + // Use the new settings for the existing connection. + if (hostBridge != null) + updateHandler.postDelayed(new Runnable() { + public void run() { + hostBridge.enablePortForward(pfb); + updateHandler.sendEmptyMessage(-1); + } + }, LISTENER_CYCLE_TIME); + + + if (!hostdb.savePortForward(pfb)) + throw new SQLException("Could not save port forward"); + + updateHandler.sendEmptyMessage(-1); + } catch (Exception e) { + Log.e(TAG, "Could not update port forward", e); + // TODO Show failure dialog. + } + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + + return true; + } + }); + + MenuItem delete = menu.add(R.string.portforward_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(PortForwardListActivity.this) + .setMessage(getString(R.string.delete_message, pfb.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + // Delete the port forward from the host if needed. + if (hostBridge != null) + hostBridge.removePortForward(pfb); + + hostdb.deletePortForward(pfb); + } catch (Exception e) { + Log.e(TAG, "Could not delete port forward", e); + } + + updateHandler.sendEmptyMessage(-1); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + + return true; + } + }); + } + + protected Handler updateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + PortForwardListActivity.this.updateList(); + } + }; + + protected void updateList() { + if (hostBridge != null) { + this.portForwards = hostBridge.getPortForwards(); + } else { + if (this.hostdb == null) return; + this.portForwards = this.hostdb.getPortForwardsForHost(host); + } + + PortForwardAdapter adapter = new PortForwardAdapter(this, portForwards); + + this.setListAdapter(adapter); + } + + class PortForwardAdapter extends ArrayAdapter<PortForwardBean> { + class ViewHolder { + public TextView nickname; + public TextView caption; + } + + private List<PortForwardBean> portForwards; + + public PortForwardAdapter(Context context, List<PortForwardBean> portForwards) { + super(context, R.layout.item_portforward, portForwards); + + this.portForwards = portForwards; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = inflater.inflate(R.layout.item_portforward, null, false); + + holder = new ViewHolder(); + holder.nickname = (TextView) convertView.findViewById(android.R.id.text1); + holder.caption = (TextView) convertView.findViewById(android.R.id.text2); + + convertView.setTag(holder); + } else + holder = (ViewHolder) convertView.getTag(); + + PortForwardBean pfb = portForwards.get(position); + holder.nickname.setText(pfb.getNickname()); + holder.caption.setText(pfb.getDescription()); + + if (hostBridge != null && !pfb.isEnabled()) { + holder.nickname.setPaintFlags(holder.nickname.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + holder.caption.setPaintFlags(holder.caption.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } + + return convertView; + } + } +} diff --git a/app/src/main/java/org/connectbot/PubkeyListActivity.java b/app/src/main/java/org/connectbot/PubkeyListActivity.java new file mode 100644 index 0000000..b3f7bd3 --- /dev/null +++ b/app/src/main/java/org/connectbot/PubkeyListActivity.java @@ -0,0 +1,673 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Collections; +import java.util.EventListener; +import java.util.LinkedList; +import java.util.List; + +import org.connectbot.bean.PubkeyBean; +import org.connectbot.service.TerminalManager; +import org.connectbot.util.PubkeyDatabase; +import org.connectbot.util.PubkeyUtils; +import org.openintents.intents.FileManagerIntents; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.IBinder; +import android.text.ClipboardManager; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TableRow; +import android.widget.TextView; +import android.widget.Toast; + +import com.trilead.ssh2.crypto.Base64; +import com.trilead.ssh2.crypto.PEMDecoder; +import com.trilead.ssh2.crypto.PEMStructure; + +/** + * List public keys in database by nickname and describe their properties. Allow users to import, + * generate, rename, and delete key pairs. + * + * @author Kenny Root + */ +public class PubkeyListActivity extends ListActivity implements EventListener { + public final static String TAG = "ConnectBot.PubkeyListActivity"; + + private static final int MAX_KEYFILE_SIZE = 8192; + private static final int REQUEST_CODE_PICK_FILE = 1; + + // Constants for AndExplorer's file picking intent + private static final String ANDEXPLORER_TITLE = "explorer_title"; + private static final String MIME_TYPE_ANDEXPLORER_FILE = "vnd.android.cursor.dir/lysesoft.andexplorer.file"; + + protected PubkeyDatabase pubkeydb; + private List<PubkeyBean> pubkeys; + + protected ClipboardManager clipboard; + + protected LayoutInflater inflater = null; + + protected TerminalManager bound = null; + + private MenuItem onstartToggle = null; + private MenuItem confirmUse = null; + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = ((TerminalManager.TerminalBinder) service).getService(); + + // update our listview binder to find the service + updateList(); + } + + public void onServiceDisconnected(ComponentName className) { + bound = null; + updateList(); + } + }; + + @Override + public void onStart() { + super.onStart(); + + bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (pubkeydb == null) + pubkeydb = new PubkeyDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + + unbindService(connection); + + if (pubkeydb != null) { + pubkeydb.close(); + pubkeydb = null; + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_pubkeylist); + + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_pubkey_list))); + + // connect with hosts database and populate list + pubkeydb = new PubkeyDatabase(this); + + updateList(); + + registerForContextMenu(getListView()); + + getListView().setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { + PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(position); + boolean loaded = bound != null && bound.isKeyLoaded(pubkey.getNickname()); + + // handle toggling key in-memory on/off + if (loaded) { + bound.removeKey(pubkey.getNickname()); + updateList(); + } else { + handleAddKey(pubkey); + } + + } + }); + + clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + + inflater = LayoutInflater.from(this); + } + + /** + * Read given file into memory as <code>byte[]</code>. + */ + protected static byte[] readRaw(File file) throws Exception { + InputStream is = new FileInputStream(file); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + int bytesRead; + byte[] buffer = new byte[1024]; + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + + os.flush(); + os.close(); + is.close(); + + return os.toByteArray(); + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + MenuItem generatekey = menu.add(R.string.pubkey_generate); + generatekey.setIcon(android.R.drawable.ic_menu_manage); + generatekey.setIntent(new Intent(PubkeyListActivity.this, GeneratePubkeyActivity.class)); + + MenuItem importkey = menu.add(R.string.pubkey_import); + importkey.setIcon(android.R.drawable.ic_menu_upload); + importkey.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Uri sdcard = Uri.fromFile(Environment.getExternalStorageDirectory()); + String pickerTitle = getString(R.string.pubkey_list_pick); + + // Try to use OpenIntent's file browser to pick a file + Intent intent = new Intent(FileManagerIntents.ACTION_PICK_FILE); + intent.setData(sdcard); + intent.putExtra(FileManagerIntents.EXTRA_TITLE, pickerTitle); + intent.putExtra(FileManagerIntents.EXTRA_BUTTON_TEXT, getString(android.R.string.ok)); + + try { + startActivityForResult(intent, REQUEST_CODE_PICK_FILE); + } catch (ActivityNotFoundException e) { + // If OI didn't work, try AndExplorer + intent = new Intent(Intent.ACTION_PICK); + intent.setDataAndType(sdcard, MIME_TYPE_ANDEXPLORER_FILE); + intent.putExtra(ANDEXPLORER_TITLE, pickerTitle); + + try { + startActivityForResult(intent, REQUEST_CODE_PICK_FILE); + } catch (ActivityNotFoundException e1) { + pickFileSimple(); + } + } + + return true; + } + }); + + return true; + } + + protected void handleAddKey(final PubkeyBean pubkey) { + if (pubkey.isEncrypted()) { + final View view = inflater.inflate(R.layout.dia_password, null); + final EditText passwordField = (EditText) view.findViewById(android.R.id.text1); + + new AlertDialog.Builder(PubkeyListActivity.this) + .setView(view) + .setPositiveButton(R.string.pubkey_unlock, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + handleAddKey(pubkey, passwordField.getText().toString()); + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + } else { + handleAddKey(pubkey, null); + } + } + + protected void handleAddKey(PubkeyBean keybean, String password) { + KeyPair pair = null; + if (PubkeyDatabase.KEY_TYPE_IMPORTED.equals(keybean.getType())) { + // load specific key using pem format + try { + pair = PEMDecoder.decode(new String(keybean.getPrivateKey()).toCharArray(), password); + } catch (Exception e) { + String message = getResources().getString(R.string.pubkey_failed_add, keybean.getNickname()); + Log.e(TAG, message, e); + Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG).show(); + } + } else { + // load using internal generated format + try { + PrivateKey privKey = PubkeyUtils.decodePrivate(keybean.getPrivateKey(), keybean.getType(), password); + PublicKey pubKey = PubkeyUtils.decodePublic(keybean.getPublicKey(), keybean.getType()); + Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey)); + + pair = new KeyPair(pubKey, privKey); + } catch (Exception e) { + String message = getResources().getString(R.string.pubkey_failed_add, keybean.getNickname()); + Log.e(TAG, message, e); + Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG).show(); + return; + } + } + + if (pair == null) { + return; + } + + Log.d(TAG, String.format("Unlocked key '%s'", keybean.getNickname())); + + // save this key in memory + bound.addKey(keybean, pair, true); + + updateList(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // Create menu to handle deleting and editing pubkey + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(info.position); + + menu.setHeaderTitle(pubkey.getNickname()); + + // TODO: option load/unload key from in-memory list + // prompt for password as needed for passworded keys + + // cant change password or clipboard imported keys + final boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); + final boolean loaded = bound != null && bound.isKeyLoaded(pubkey.getNickname()); + + MenuItem load = menu.add(loaded ? R.string.pubkey_memory_unload : R.string.pubkey_memory_load); + load.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + if (loaded) { + bound.removeKey(pubkey.getNickname()); + updateList(); + } else { + handleAddKey(pubkey); + //bound.addKey(nickname, trileadKey); + } + return true; + } + }); + + onstartToggle = menu.add(R.string.pubkey_load_on_start); + onstartToggle.setEnabled(!pubkey.isEncrypted()); + onstartToggle.setCheckable(true); + onstartToggle.setChecked(pubkey.isStartup()); + onstartToggle.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // toggle onstart status + pubkey.setStartup(!pubkey.isStartup()); + pubkeydb.savePubkey(pubkey); + updateList(); + return true; + } + }); + + MenuItem copyPublicToClipboard = menu.add(R.string.pubkey_copy_public); + copyPublicToClipboard.setEnabled(!imported); + copyPublicToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + try { + PublicKey pk = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType()); + String openSSHPubkey = PubkeyUtils.convertToOpenSSHFormat(pk, pubkey.getNickname()); + + clipboard.setText(openSSHPubkey); + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + }); + + MenuItem copyPrivateToClipboard = menu.add(R.string.pubkey_copy_private); + copyPrivateToClipboard.setEnabled(!pubkey.isEncrypted() || imported); + copyPrivateToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + try { + String data = null; + + if (imported) + data = new String(pubkey.getPrivateKey()); + else { + PrivateKey pk = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), pubkey.getType()); + data = PubkeyUtils.exportPEM(pk, null); + } + + clipboard.setText(data); + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + }); + + MenuItem changePassword = menu.add(R.string.pubkey_change_password); + changePassword.setEnabled(!imported); + changePassword.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final View changePasswordView = inflater.inflate(R.layout.dia_changepassword, null, false); + ((TableRow) changePasswordView.findViewById(R.id.old_password_prompt)) + .setVisibility(pubkey.isEncrypted() ? View.VISIBLE : View.GONE); + new AlertDialog.Builder(PubkeyListActivity.this) + .setView(changePasswordView) + .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String oldPassword = ((EditText) changePasswordView.findViewById(R.id.old_password)).getText().toString(); + String password1 = ((EditText) changePasswordView.findViewById(R.id.password1)).getText().toString(); + String password2 = ((EditText) changePasswordView.findViewById(R.id.password2)).getText().toString(); + + if (!password1.equals(password2)) { + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_passwords_do_not_match_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + return; + } + + try { + if (!pubkey.changePassword(oldPassword, password1)) + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_wrong_password_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + else { + pubkeydb.savePubkey(pubkey); + updateList(); + } + } catch (Exception e) { + Log.e(TAG, "Could not change private key password", e); + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_key_corrupted_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + } + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + + return true; + } + }); + + confirmUse = menu.add(R.string.pubkey_confirm_use); + confirmUse.setCheckable(true); + confirmUse.setChecked(pubkey.isConfirmUse()); + confirmUse.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // toggle confirm use + pubkey.setConfirmUse(!pubkey.isConfirmUse()); + pubkeydb.savePubkey(pubkey); + updateList(); + return true; + } + }); + + MenuItem delete = menu.add(R.string.pubkey_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(getString(R.string.delete_message, pubkey.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + + // dont forget to remove from in-memory + if (loaded) + bound.removeKey(pubkey.getNickname()); + + // delete from backend database and update gui + pubkeydb.deletePubkey(pubkey); + updateList(); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + + return true; + } + }); + + } + + protected void updateList() { + if (pubkeydb == null) return; + + pubkeys = pubkeydb.allPubkeys(); + PubkeyAdapter adapter = new PubkeyAdapter(this, pubkeys); + + this.setListAdapter(adapter); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + + switch (requestCode) { + case REQUEST_CODE_PICK_FILE: + if (resultCode == RESULT_OK && intent != null) { + Uri uri = intent.getData(); + try { + if (uri != null) { + readKeyFromFile(new File(URI.create(uri.toString()))); + } else { + String filename = intent.getDataString(); + if (filename != null) + readKeyFromFile(new File(URI.create(filename))); + } + } catch (IllegalArgumentException e) { + Log.e(TAG, "Couldn't read from picked file", e); + } + } + break; + } + } + + /** + * @param name + */ + private void readKeyFromFile(File file) { + PubkeyBean pubkey = new PubkeyBean(); + + // find the exact file selected + pubkey.setNickname(file.getName()); + + if (file.length() > MAX_KEYFILE_SIZE) { + Toast.makeText(PubkeyListActivity.this, + R.string.pubkey_import_parse_problem, + Toast.LENGTH_LONG).show(); + return; + } + + // parse the actual key once to check if its encrypted + // then save original file contents into our database + try { + byte[] raw = readRaw(file); + + String data = new String(raw); + if (data.startsWith(PubkeyUtils.PKCS8_START)) { + int start = data.indexOf(PubkeyUtils.PKCS8_START) + PubkeyUtils.PKCS8_START.length(); + int end = data.indexOf(PubkeyUtils.PKCS8_END); + + if (end > start) { + char[] encoded = data.substring(start, end - 1).toCharArray(); + Log.d(TAG, "encoded: " + new String(encoded)); + byte[] decoded = Base64.decode(encoded); + + KeyPair kp = PubkeyUtils.recoverKeyPair(decoded); + + pubkey.setType(kp.getPrivate().getAlgorithm()); + pubkey.setPrivateKey(kp.getPrivate().getEncoded()); + pubkey.setPublicKey(kp.getPublic().getEncoded()); + } else { + Log.e(TAG, "Problem parsing PKCS#8 file; corrupt?"); + Toast.makeText(PubkeyListActivity.this, + R.string.pubkey_import_parse_problem, + Toast.LENGTH_LONG).show(); + } + } else { + PEMStructure struct = PEMDecoder.parsePEM(new String(raw).toCharArray()); + pubkey.setEncrypted(PEMDecoder.isPEMEncrypted(struct)); + pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED); + pubkey.setPrivateKey(raw); + } + + // write new value into database + if (pubkeydb == null) + pubkeydb = new PubkeyDatabase(this); + pubkeydb.savePubkey(pubkey); + + updateList(); + } catch (Exception e) { + Log.e(TAG, "Problem parsing imported private key", e); + Toast.makeText(PubkeyListActivity.this, R.string.pubkey_import_parse_problem, Toast.LENGTH_LONG).show(); + } + } + + /** + * + */ + private void pickFileSimple() { + // build list of all files in sdcard root + final File sdcard = Environment.getExternalStorageDirectory(); + Log.d(TAG, sdcard.toString()); + + // Don't show a dialog if the SD card is completely absent. + final String state = Environment.getExternalStorageState(); + if (!Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) + && !Environment.MEDIA_MOUNTED.equals(state)) { + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_sdcard_absent) + .setNegativeButton(android.R.string.cancel, null).create().show(); + return; + } + + List<String> names = new LinkedList<String>(); + { + File[] files = sdcard.listFiles(); + if (files != null) { + for (File file : sdcard.listFiles()) { + if (file.isDirectory()) continue; + names.add(file.getName()); + } + } + } + Collections.sort(names); + + final String[] namesList = names.toArray(new String[] {}); + Log.d(TAG, names.toString()); + + // prompt user to select any file from the sdcard root + new AlertDialog.Builder(PubkeyListActivity.this) + .setTitle(R.string.pubkey_list_pick) + .setItems(namesList, new OnClickListener() { + public void onClick(DialogInterface arg0, int arg1) { + String name = namesList[arg1]; + + readKeyFromFile(new File(sdcard, name)); + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + } + + class PubkeyAdapter extends ArrayAdapter<PubkeyBean> { + private List<PubkeyBean> pubkeys; + + class ViewHolder { + public TextView nickname; + public TextView caption; + public ImageView icon; + } + + public PubkeyAdapter(Context context, List<PubkeyBean> pubkeys) { + super(context, R.layout.item_pubkey, pubkeys); + + this.pubkeys = pubkeys; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = inflater.inflate(R.layout.item_pubkey, null, false); + + holder = new ViewHolder(); + + holder.nickname = (TextView) convertView.findViewById(android.R.id.text1); + holder.caption = (TextView) convertView.findViewById(android.R.id.text2); + holder.icon = (ImageView) convertView.findViewById(android.R.id.icon1); + + convertView.setTag(holder); + } else + holder = (ViewHolder) convertView.getTag(); + + PubkeyBean pubkey = pubkeys.get(position); + holder.nickname.setText(pubkey.getNickname()); + + boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); + + if (imported) { + try { + PEMStructure struct = PEMDecoder.parsePEM(new String(pubkey.getPrivateKey()).toCharArray()); + String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : "DSA"; + holder.caption.setText(String.format("%s unknown-bit", type)); + } catch (IOException e) { + Log.e(TAG, "Error decoding IMPORTED public key at " + pubkey.getId(), e); + } + } else { + try { + holder.caption.setText(pubkey.getDescription()); + } catch (Exception e) { + Log.e(TAG, "Error decoding public key at " + pubkey.getId(), e); + holder.caption.setText(R.string.pubkey_unknown_format); + } + } + + if (bound == null) { + holder.icon.setVisibility(View.GONE); + } else { + holder.icon.setVisibility(View.VISIBLE); + + if (bound.isKeyLoaded(pubkey.getNickname())) + holder.icon.setImageState(new int[] { android.R.attr.state_checked }, true); + else + holder.icon.setImageState(new int[] { }, true); + } + + return convertView; + } + } +} diff --git a/app/src/main/java/org/connectbot/SettingsActivity.java b/app/src/main/java/org/connectbot/SettingsActivity.java new file mode 100644 index 0000000..460805d --- /dev/null +++ b/app/src/main/java/org/connectbot/SettingsActivity.java @@ -0,0 +1,60 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import org.connectbot.util.PreferenceConstants; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.util.Log; + +public class SettingsActivity extends PreferenceActivity { + private static final String TAG = "ConnectBot.Settings"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + try { + addPreferencesFromResource(R.xml.preferences); + } catch (ClassCastException e) { + Log.e(TAG, "Shared preferences are corrupt! Resetting to default values."); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Blow away all the preferences + SharedPreferences.Editor editor = preferences.edit(); + editor.clear(); + editor.commit(); + + PreferenceManager.setDefaultValues(this, R.xml.preferences, true); + + // Since they were able to get to the Settings activity, they already agreed to the EULA + editor = preferences.edit(); + editor.putBoolean(PreferenceConstants.EULA, true); + editor.commit(); + + addPreferencesFromResource(R.xml.preferences); + } + + // TODO: add parse checking here to make sure we have integer value for scrollback + + } + +} diff --git a/app/src/main/java/org/connectbot/StrictModeSetup.java b/app/src/main/java/org/connectbot/StrictModeSetup.java new file mode 100644 index 0000000..753a273 --- /dev/null +++ b/app/src/main/java/org/connectbot/StrictModeSetup.java @@ -0,0 +1,27 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.connectbot; + +import android.annotation.TargetApi; +import android.os.StrictMode; + +@TargetApi(9) +public class StrictModeSetup { + public static void run() { + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); + } +} diff --git a/app/src/main/java/org/connectbot/TerminalView.java b/app/src/main/java/org/connectbot/TerminalView.java new file mode 100644 index 0000000..88fa90d --- /dev/null +++ b/app/src/main/java/org/connectbot/TerminalView.java @@ -0,0 +1,451 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.connectbot.bean.SelectionArea; +import org.connectbot.service.FontSizeChangedListener; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalKeyListener; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.database.Cursor; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelXorXfermode; +import android.graphics.RectF; +import android.net.Uri; +import android.os.AsyncTask; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.widget.Toast; +import de.mud.terminal.VDUBuffer; + +/** + * User interface {@link View} for showing a TerminalBridge in an + * {@link android.app.Activity}. Handles drawing bitmap updates and passing keystrokes down + * to terminal. + * + * @author jsharkey + */ +public class TerminalView extends View implements FontSizeChangedListener { + + private final Context context; + public final TerminalBridge bridge; + private final Paint paint; + private final Paint cursorPaint; + private final Paint cursorStrokePaint; + + // Cursor paints to distinguish modes + private Path ctrlCursor, altCursor, shiftCursor; + private RectF tempSrc, tempDst; + private Matrix scaleMatrix; + private static final Matrix.ScaleToFit scaleType = Matrix.ScaleToFit.FILL; + + private Toast notification = null; + private String lastNotification = null; + private volatile boolean notifications = true; + + // Related to Accessibility Features + private boolean mAccessibilityInitialized = false; + private boolean mAccessibilityActive = true; + private Object[] mAccessibilityLock = new Object[0]; + private StringBuffer mAccessibilityBuffer; + private Pattern mControlCodes = null; + private Matcher mCodeMatcher = null; + private AccessibilityEventSender mEventSender = null; + + private static final String BACKSPACE_CODE = "\\x08\\x1b\\[K"; + private static final String CONTROL_CODE_PATTERN = "\\x1b\\[K[^m]+[m|:]"; + + private static final int ACCESSIBILITY_EVENT_THRESHOLD = 1000; + private static final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService"; + private static final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN"; + + public TerminalView(Context context, TerminalBridge bridge) { + super(context); + + this.context = context; + this.bridge = bridge; + paint = new Paint(); + + setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + setFocusable(true); + setFocusableInTouchMode(true); + + cursorPaint = new Paint(); + cursorPaint.setColor(bridge.color[bridge.defaultFg]); + cursorPaint.setXfermode(new PixelXorXfermode(bridge.color[bridge.defaultBg])); + cursorPaint.setAntiAlias(true); + + cursorStrokePaint = new Paint(cursorPaint); + cursorStrokePaint.setStrokeWidth(0.1f); + cursorStrokePaint.setStyle(Paint.Style.STROKE); + + /* + * Set up our cursor indicators on a 1x1 Path object which we can later + * transform to our character width and height + */ + // TODO make this into a resource somehow + shiftCursor = new Path(); + shiftCursor.lineTo(0.5f, 0.33f); + shiftCursor.lineTo(1.0f, 0.0f); + + altCursor = new Path(); + altCursor.moveTo(0.0f, 1.0f); + altCursor.lineTo(0.5f, 0.66f); + altCursor.lineTo(1.0f, 1.0f); + + ctrlCursor = new Path(); + ctrlCursor.moveTo(0.0f, 0.25f); + ctrlCursor.lineTo(1.0f, 0.5f); + ctrlCursor.lineTo(0.0f, 0.75f); + + // For creating the transform when the terminal resizes + tempSrc = new RectF(); + tempSrc.set(0.0f, 0.0f, 1.0f, 1.0f); + tempDst = new RectF(); + scaleMatrix = new Matrix(); + + bridge.addFontSizeChangedListener(this); + + // connect our view up to the bridge + setOnKeyListener(bridge.getKeyHandler()); + + mAccessibilityBuffer = new StringBuffer(); + + // Enable accessibility features if a screen reader is active. + new AccessibilityStateTester().execute((Void) null); + } + + public void destroy() { + // tell bridge to destroy its bitmap + bridge.parentDestroyed(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + bridge.parentChanged(this); + + scaleCursors(); + } + + public void onFontSizeChanged(float size) { + scaleCursors(); + } + + private void scaleCursors() { + // Create a scale matrix to scale our 1x1 representation of the cursor + tempDst.set(0.0f, 0.0f, bridge.charWidth, bridge.charHeight); + scaleMatrix.setRectToRect(tempSrc, tempDst, scaleType); + } + + @Override + public void onDraw(Canvas canvas) { + if (bridge.bitmap != null) { + // draw the bitmap + bridge.onDraw(); + + // draw the bridge bitmap if it exists + canvas.drawBitmap(bridge.bitmap, 0, 0, paint); + + // also draw cursor if visible + if (bridge.buffer.isCursorVisible()) { + int cursorColumn = bridge.buffer.getCursorColumn(); + final int cursorRow = bridge.buffer.getCursorRow(); + + final int columns = bridge.buffer.getColumns(); + + if (cursorColumn == columns) + cursorColumn = columns - 1; + + if (cursorColumn < 0 || cursorRow < 0) + return; + + int currentAttribute = bridge.buffer.getAttributes( + cursorColumn, cursorRow); + boolean onWideCharacter = (currentAttribute & VDUBuffer.FULLWIDTH) != 0; + + int x = cursorColumn * bridge.charWidth; + int y = (bridge.buffer.getCursorRow() + + bridge.buffer.screenBase - bridge.buffer.windowBase) + * bridge.charHeight; + + // Save the current clip and translation + canvas.save(); + + canvas.translate(x, y); + canvas.clipRect(0, 0, + bridge.charWidth * (onWideCharacter ? 2 : 1), + bridge.charHeight); + canvas.drawPaint(cursorPaint); + + final int deadKey = bridge.getKeyHandler().getDeadKey(); + if (deadKey != 0) { + canvas.drawText(new char[] { (char) deadKey }, 0, 1, 0, 0, cursorStrokePaint); + } + + // Make sure we scale our decorations to the correct size. + canvas.concat(scaleMatrix); + + int metaState = bridge.getKeyHandler().getMetaState(); + + if ((metaState & TerminalKeyListener.OUR_SHIFT_ON) != 0) + canvas.drawPath(shiftCursor, cursorStrokePaint); + else if ((metaState & TerminalKeyListener.OUR_SHIFT_LOCK) != 0) + canvas.drawPath(shiftCursor, cursorPaint); + + if ((metaState & TerminalKeyListener.OUR_ALT_ON) != 0) + canvas.drawPath(altCursor, cursorStrokePaint); + else if ((metaState & TerminalKeyListener.OUR_ALT_LOCK) != 0) + canvas.drawPath(altCursor, cursorPaint); + + if ((metaState & TerminalKeyListener.OUR_CTRL_ON) != 0) + canvas.drawPath(ctrlCursor, cursorStrokePaint); + else if ((metaState & TerminalKeyListener.OUR_CTRL_LOCK) != 0) + canvas.drawPath(ctrlCursor, cursorPaint); + + // Restore previous clip region + canvas.restore(); + } + + // draw any highlighted area + if (bridge.isSelectingForCopy()) { + SelectionArea area = bridge.getSelectionArea(); + canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect( + area.getLeft() * bridge.charWidth, + area.getTop() * bridge.charHeight, + (area.getRight() + 1) * bridge.charWidth, + (area.getBottom() + 1) * bridge.charHeight + ); + canvas.drawPaint(cursorPaint); + canvas.restore(); + } + } + } + + public void notifyUser(String message) { + if (!notifications) + return; + + if (notification != null) { + // Don't keep telling the user the same thing. + if (lastNotification != null && lastNotification.equals(message)) + return; + + notification.setText(message); + notification.show(); + } else { + notification = Toast.makeText(context, message, Toast.LENGTH_SHORT); + notification.show(); + } + + lastNotification = message; + } + + /** + * Ask the {@link TerminalBridge} we're connected to to resize to a specific size. + * @param width + * @param height + */ + public void forceSize(int width, int height) { + bridge.resizeComputed(width, height, getWidth(), getHeight()); + } + + /** + * Sets the ability for the TerminalView to display Toast notifications to the user. + * @param value whether to enable notifications or not + */ + public void setNotifications(boolean value) { + notifications = value; + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + outAttrs.imeOptions |= + EditorInfo.IME_FLAG_NO_EXTRACT_UI | + EditorInfo.IME_FLAG_NO_ENTER_ACTION | + EditorInfo.IME_ACTION_NONE; + outAttrs.inputType = EditorInfo.TYPE_NULL; + return new BaseInputConnection(this, false) { + @Override + public boolean deleteSurroundingText (int leftLength, int rightLength) { + if (rightLength == 0 && leftLength == 0) { + return this.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + } + for (int i = 0; i < leftLength; i++) { + this.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + } + // TODO: forward delete + return true; + } + }; + } + + public void propagateConsoleText(char[] rawText, int length) { + if (mAccessibilityActive) { + synchronized (mAccessibilityLock) { + mAccessibilityBuffer.append(rawText, 0, length); + } + + if (mAccessibilityInitialized) { + if (mEventSender != null) { + removeCallbacks(mEventSender); + } else { + mEventSender = new AccessibilityEventSender(); + } + + postDelayed(mEventSender, ACCESSIBILITY_EVENT_THRESHOLD); + } + } + } + + private class AccessibilityEventSender implements Runnable { + public void run() { + synchronized (mAccessibilityLock) { + if (mCodeMatcher == null) { + mCodeMatcher = mControlCodes.matcher(mAccessibilityBuffer); + } else { + mCodeMatcher.reset(mAccessibilityBuffer); + } + + // Strip all control codes out. + mAccessibilityBuffer = new StringBuffer(mCodeMatcher.replaceAll(" ")); + + // Apply Backspaces using backspace character sequence + int i = mAccessibilityBuffer.indexOf(BACKSPACE_CODE); + while (i != -1) { + mAccessibilityBuffer = mAccessibilityBuffer.replace(i == 0 ? 0 : i - 1, i + + BACKSPACE_CODE.length(), ""); + i = mAccessibilityBuffer.indexOf(BACKSPACE_CODE); + } + + if (mAccessibilityBuffer.length() > 0) { + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); + event.setFromIndex(0); + event.setAddedCount(mAccessibilityBuffer.length()); + event.getText().add(mAccessibilityBuffer); + + sendAccessibilityEventUnchecked(event); + mAccessibilityBuffer.setLength(0); + } + } + } + } + + private class AccessibilityStateTester extends AsyncTask<Void, Void, Boolean> { + @Override + protected Boolean doInBackground(Void... params) { + /* + * Presumably if the accessibility manager is not enabled, we don't + * need to send accessibility events. + */ + final AccessibilityManager accessibility = (AccessibilityManager) context + .getSystemService(Context.ACCESSIBILITY_SERVICE); + if (!accessibility.isEnabled()) { + return false; + } + + /* + * Restrict the set of intents to only accessibility services that + * have the category FEEDBACK_SPOKEN (aka, screen readers). + */ + final Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION); + screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY); + + final ContentResolver cr = context.getContentResolver(); + + final List<ResolveInfo> screenReaders = context.getPackageManager().queryIntentServices( + screenReaderIntent, 0); + + boolean foundScreenReader = false; + + final int N = screenReaders.size(); + for (int i = 0; i < N; i++) { + final ResolveInfo screenReader = screenReaders.get(i); + + /* + * All screen readers are expected to implement a content + * provider that responds to: + * content://<nameofpackage>.providers.StatusProvider + */ + final Cursor cursor = cr.query( + Uri.parse("content://" + screenReader.serviceInfo.packageName + + ".providers.StatusProvider"), null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + /* + * These content providers use a special cursor that only has + * one element, an integer that is 1 if the screen reader is + * running. + */ + final int status = cursor.getInt(0); + + cursor.close(); + + if (status == 1) { + foundScreenReader = true; + break; + } + } + } + + if (foundScreenReader) { + mControlCodes = Pattern.compile(CONTROL_CODE_PATTERN); + } + + return foundScreenReader; + } + + @Override + protected void onPostExecute(Boolean result) { + mAccessibilityActive = result; + + mAccessibilityInitialized = true; + + if (result) { + mEventSender = new AccessibilityEventSender(); + postDelayed(mEventSender, ACCESSIBILITY_EVENT_THRESHOLD); + } else { + mAccessibilityBuffer = null; + } + } + } +} diff --git a/app/src/main/java/org/connectbot/WizardActivity.java b/app/src/main/java/org/connectbot/WizardActivity.java new file mode 100644 index 0000000..4069c50 --- /dev/null +++ b/app/src/main/java/org/connectbot/WizardActivity.java @@ -0,0 +1,104 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import org.connectbot.util.HelpTopicView; + +import android.app.Activity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ViewFlipper; + +/** + * Show a series of wizard-like steps to the user, which might include an EULA, + * program credits, and helpful hints. + * + * @author jsharkey + */ +public class WizardActivity extends Activity { + protected ViewFlipper flipper = null; + private Button next, prev; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.act_wizard); + + this.flipper = (ViewFlipper) findViewById(R.id.wizard_flipper); + + // inflate the layout for EULA step + LayoutInflater inflater = LayoutInflater.from(this); + this.flipper.addView(inflater.inflate(R.layout.wiz_eula, this.flipper, false)); + + // Add a view for each help topic we want the user to see. + String[] topics = getResources().getStringArray(R.array.list_wizard_topics); + for (String topic : topics) { + flipper.addView(new HelpTopicView(this).setTopic(topic)); + } + + next = (Button) findViewById(R.id.action_next); + next.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (isLastDisplayed()) { + // user walked past end of wizard, so return okay + WizardActivity.this.setResult(Activity.RESULT_OK); + WizardActivity.this.finish(); + } else { + // show next step and update buttons + flipper.showNext(); + updateButtons(); + } + } + }); + + prev = (Button) findViewById(R.id.action_prev); + prev.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (isFirstDisplayed()) { + // user walked past beginning of wizard, so return that they cancelled + WizardActivity.this.setResult(Activity.RESULT_CANCELED); + WizardActivity.this.finish(); + } else { + // show previous step and update buttons + flipper.showPrevious(); + updateButtons(); + } + } + }); + + this.updateButtons(); + } + + protected boolean isFirstDisplayed() { + return (flipper.getDisplayedChild() == 0); + } + + protected boolean isLastDisplayed() { + return (flipper.getDisplayedChild() == flipper.getChildCount() - 1); + } + + protected void updateButtons() { + boolean eula = (flipper.getDisplayedChild() == 0); + + next.setText(eula ? getString(R.string.wizard_agree) : getString(R.string.wizard_next)); + prev.setText(eula ? getString(R.string.delete_neg) : getString(R.string.wizard_back)); + } +} diff --git a/app/src/main/java/org/connectbot/bean/AbstractBean.java b/app/src/main/java/org/connectbot/bean/AbstractBean.java new file mode 100644 index 0000000..7f55785 --- /dev/null +++ b/app/src/main/java/org/connectbot/bean/AbstractBean.java @@ -0,0 +1,49 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.bean; + +import java.util.Map.Entry; + +import org.connectbot.util.XmlBuilder; + +import android.content.ContentValues; + +/** + * @author Kenny Root + * + */ +abstract class AbstractBean { + public abstract ContentValues getValues(); + public abstract String getBeanName(); + + public String toXML() { + XmlBuilder xml = new XmlBuilder(); + + xml.append(String.format("<%s>", getBeanName())); + + ContentValues values = getValues(); + for (Entry<String, Object> entry : values.valueSet()) { + Object value = entry.getValue(); + if (value != null) + xml.append(entry.getKey(), value); + } + xml.append(String.format("</%s>", getBeanName())); + + return xml.toString(); + } +} diff --git a/app/src/main/java/org/connectbot/bean/HostBean.java b/app/src/main/java/org/connectbot/bean/HostBean.java new file mode 100644 index 0000000..7083e6d --- /dev/null +++ b/app/src/main/java/org/connectbot/bean/HostBean.java @@ -0,0 +1,327 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.bean; + +import org.connectbot.util.HostDatabase; + +import android.content.ContentValues; +import android.net.Uri; + +/** + * @author Kenny Root + * + */ +public class HostBean extends AbstractBean { + public static final String BEAN_NAME = "host"; + + /* Database fields */ + private long id = -1; + private String nickname = null; + private String username = null; + private String hostname = null; + private int port = 22; + private String protocol = "ssh"; + private String hostKeyAlgo = null; + private byte[] hostKey = null; + private long lastConnect = -1; + private String color; + private boolean useKeys = true; + private String useAuthAgent = HostDatabase.AUTHAGENT_NO; + private String postLogin = null; + private long pubkeyId = -1; + private boolean wantSession = true; + private String delKey = HostDatabase.DELKEY_DEL; + private int fontSize = -1; + private boolean compression = false; + private String encoding = HostDatabase.ENCODING_DEFAULT; + private boolean stayConnected = false; + private boolean quickDisconnect = false; + + public HostBean() { + + } + + @Override + public String getBeanName() { + return BEAN_NAME; + } + + public HostBean(String nickname, String protocol, String username, String hostname, int port) { + this.nickname = nickname; + this.protocol = protocol; + this.username = username; + this.hostname = hostname; + this.port = port; + } + + public void setId(long id) { + this.id = id; + } + public long getId() { + return id; + } + public void setNickname(String nickname) { + this.nickname = nickname; + } + public String getNickname() { + return nickname; + } + public void setUsername(String username) { + this.username = username; + } + public String getUsername() { + return username; + } + public void setHostname(String hostname) { + this.hostname = hostname; + } + public String getHostname() { + return hostname; + } + public void setPort(int port) { + this.port = port; + } + public int getPort() { + return port; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getProtocol() { + return protocol; + } + + public void setHostKeyAlgo(String hostKeyAlgo) { + this.hostKeyAlgo = hostKeyAlgo; + } + public String getHostKeyAlgo() { + return hostKeyAlgo; + } + public void setHostKey(byte[] hostKey) { + if (hostKey == null) + this.hostKey = null; + else + this.hostKey = hostKey.clone(); + } + public byte[] getHostKey() { + if (hostKey == null) + return null; + else + return hostKey.clone(); + } + public void setLastConnect(long lastConnect) { + this.lastConnect = lastConnect; + } + public long getLastConnect() { + return lastConnect; + } + public void setColor(String color) { + this.color = color; + } + public String getColor() { + return color; + } + public void setUseKeys(boolean useKeys) { + this.useKeys = useKeys; + } + public boolean getUseKeys() { + return useKeys; + } + public void setUseAuthAgent(String useAuthAgent) { + this.useAuthAgent = useAuthAgent; + } + public String getUseAuthAgent() { + return useAuthAgent; + } + public void setPostLogin(String postLogin) { + this.postLogin = postLogin; + } + public String getPostLogin() { + return postLogin; + } + public void setPubkeyId(long pubkeyId) { + this.pubkeyId = pubkeyId; + } + public long getPubkeyId() { + return pubkeyId; + } + public void setWantSession(boolean wantSession) { + this.wantSession = wantSession; + } + public boolean getWantSession() { + return wantSession; + } + public void setDelKey(String delKey) { + this.delKey = delKey; + } + public String getDelKey() { + return delKey; + } + public void setFontSize(int fontSize) { + this.fontSize = fontSize; + } + public int getFontSize() { + return fontSize; + } + public void setCompression(boolean compression) { + this.compression = compression; + } + public boolean getCompression() { + return compression; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public String getEncoding() { + return this.encoding; + } + + public void setStayConnected(boolean stayConnected) { + this.stayConnected = stayConnected; + } + + public boolean getStayConnected() { + return stayConnected; + } + + public void setQuickDisconnect(boolean quickDisconnect) { + this.quickDisconnect = quickDisconnect; + } + + public boolean getQuickDisconnect() { + return quickDisconnect; + } + + public String getDescription() { + String description = String.format("%s@%s", username, hostname); + + if (port != 22) + description += String.format(":%d", port); + + return description; + } + + @Override + public ContentValues getValues() { + ContentValues values = new ContentValues(); + + values.put(HostDatabase.FIELD_HOST_NICKNAME, nickname); + values.put(HostDatabase.FIELD_HOST_PROTOCOL, protocol); + values.put(HostDatabase.FIELD_HOST_USERNAME, username); + values.put(HostDatabase.FIELD_HOST_HOSTNAME, hostname); + values.put(HostDatabase.FIELD_HOST_PORT, port); + values.put(HostDatabase.FIELD_HOST_HOSTKEYALGO, hostKeyAlgo); + values.put(HostDatabase.FIELD_HOST_HOSTKEY, hostKey); + values.put(HostDatabase.FIELD_HOST_LASTCONNECT, lastConnect); + values.put(HostDatabase.FIELD_HOST_COLOR, color); + values.put(HostDatabase.FIELD_HOST_USEKEYS, Boolean.toString(useKeys)); + values.put(HostDatabase.FIELD_HOST_USEAUTHAGENT, useAuthAgent); + values.put(HostDatabase.FIELD_HOST_POSTLOGIN, postLogin); + values.put(HostDatabase.FIELD_HOST_PUBKEYID, pubkeyId); + values.put(HostDatabase.FIELD_HOST_WANTSESSION, Boolean.toString(wantSession)); + values.put(HostDatabase.FIELD_HOST_DELKEY, delKey); + values.put(HostDatabase.FIELD_HOST_FONTSIZE, fontSize); + values.put(HostDatabase.FIELD_HOST_COMPRESSION, Boolean.toString(compression)); + values.put(HostDatabase.FIELD_HOST_ENCODING, encoding); + values.put(HostDatabase.FIELD_HOST_STAYCONNECTED, stayConnected); + values.put(HostDatabase.FIELD_HOST_QUICKDISCONNECT, quickDisconnect); + + return values; + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof HostBean)) + return false; + + HostBean host = (HostBean) o; + + if (id != -1 && host.getId() != -1) + return host.getId() == id; + + if (nickname == null) { + if (host.getNickname() != null) + return false; + } else if (!nickname.equals(host.getNickname())) + return false; + + if (protocol == null) { + if (host.getProtocol() != null) + return false; + } else if (!protocol.equals(host.getProtocol())) + return false; + + if (username == null) { + if (host.getUsername() != null) + return false; + } else if (!username.equals(host.getUsername())) + return false; + + if (hostname == null) { + if (host.getHostname() != null) + return false; + } else if (!hostname.equals(host.getHostname())) + return false; + + if (port != host.getPort()) + return false; + + return true; + } + + @Override + public int hashCode() { + int hash = 7; + + if (id != -1) + return (int) id; + + hash = 31 * hash + (null == nickname ? 0 : nickname.hashCode()); + hash = 31 * hash + (null == protocol ? 0 : protocol.hashCode()); + hash = 31 * hash + (null == username ? 0 : username.hashCode()); + hash = 31 * hash + (null == hostname ? 0 : hostname.hashCode()); + hash = 31 * hash + port; + + return hash; + } + + /** + * @return URI identifying this HostBean + */ + public Uri getUri() { + StringBuilder sb = new StringBuilder(); + sb.append(protocol) + .append("://"); + + if (username != null) + sb.append(Uri.encode(username)) + .append('@'); + + sb.append(Uri.encode(hostname)) + .append(':') + .append(port) + .append("/#") + .append(nickname); + return Uri.parse(sb.toString()); + } + +} diff --git a/app/src/main/java/org/connectbot/bean/PortForwardBean.java b/app/src/main/java/org/connectbot/bean/PortForwardBean.java new file mode 100644 index 0000000..2bdaf20 --- /dev/null +++ b/app/src/main/java/org/connectbot/bean/PortForwardBean.java @@ -0,0 +1,239 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.bean; + +import org.connectbot.util.HostDatabase; + +import android.content.ContentValues; + + +/** + * @author Kenny Root + * + */ +public class PortForwardBean extends AbstractBean { + public static final String BEAN_NAME = "portforward"; + + /* Database fields */ + private long id = -1; + private long hostId = -1; + private String nickname = null; + private String type = null; + private int sourcePort = -1; + private String destAddr = null; + private int destPort = -1; + + /* Transient values */ + private boolean enabled = false; + private Object identifier = null; + + /** + * @param id database ID of port forward + * @param nickname Nickname to use to identify port forward + * @param type One of the port forward types from {@link HostDatabase} + * @param sourcePort Source port number + * @param destAddr Destination hostname or IP address + * @param destPort Destination port number + */ + public PortForwardBean(long id, long hostId, String nickname, String type, int sourcePort, String destAddr, int destPort) { + this.id = id; + this.hostId = hostId; + this.nickname = nickname; + this.type = type; + this.sourcePort = sourcePort; + this.destAddr = destAddr; + this.destPort = destPort; + } + + /** + * @param type One of the port forward types from {@link HostDatabase} + * @param source Source port number + * @param dest Destination is "host:port" format + */ + public PortForwardBean(long hostId, String nickname, String type, String source, String dest) { + this.hostId = hostId; + this.nickname = nickname; + this.type = type; + this.sourcePort = Integer.parseInt(source); + + setDest(dest); + } + + public String getBeanName() { + return BEAN_NAME; + } + + /** + * @param id the id to set + */ + public void setId(long id) { + this.id = id; + } + + /** + * @return the id + */ + public long getId() { + return id; + } + + /** + * @param nickname the nickname to set + */ + public void setNickname(String nickname) { + this.nickname = nickname; + } + + /** + * @return the nickname + */ + public String getNickname() { + return nickname; + } + + /** + * @param type the type to set + */ + public void setType(String type) { + this.type = type; + } + + /** + * @return the type + */ + public String getType() { + return type; + } + + /** + * @param sourcePort the sourcePort to set + */ + public void setSourcePort(int sourcePort) { + this.sourcePort = sourcePort; + } + + /** + * @return the sourcePort + */ + public int getSourcePort() { + return sourcePort; + } + + /** + * @param dest The destination in "host:port" format + */ + public final void setDest(String dest) { + String[] destSplit = dest.split(":"); + this.destAddr = destSplit[0]; + if (destSplit.length > 1) + this.destPort = Integer.parseInt(destSplit[1]); + } + + /** + * @param destAddr the destAddr to set + */ + public void setDestAddr(String destAddr) { + this.destAddr = destAddr; + } + + /** + * @return the destAddr + */ + public String getDestAddr() { + return destAddr; + } + + /** + * @param destPort the destPort to set + */ + public void setDestPort(int destPort) { + this.destPort = destPort; + } + + /** + * @return the destPort + */ + public int getDestPort() { + return destPort; + } + + /** + * @param enabled the enabled to set + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * @return the enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * @param identifier the identifier of this particular type to set + */ + public void setIdentifier(Object identifier) { + this.identifier = identifier; + } + + /** + * @return the identifier used by this particular type + */ + public Object getIdentifier() { + return identifier; + } + + /** + * @return human readable description of the port forward + */ + public CharSequence getDescription() { + String description = "Unknown type"; + + if (HostDatabase.PORTFORWARD_LOCAL.equals(type)) { + description = String.format("Local port %d to %s:%d", sourcePort, destAddr, destPort); + } else if (HostDatabase.PORTFORWARD_REMOTE.equals(type)) { + description = String.format("Remote port %d to %s:%d", sourcePort, destAddr, destPort); +/* I don't think we need the SOCKS4 type. + } else if (HostDatabase.PORTFORWARD_DYNAMIC4.equals(type)) { + description = String.format("Dynamic port %d (SOCKS4)", sourcePort); +*/ + } else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(type)) { + description = String.format("Dynamic port %d (SOCKS)", sourcePort); + } + + return description; + } + + /** + * @return + */ + public ContentValues getValues() { + ContentValues values = new ContentValues(); + + values.put(HostDatabase.FIELD_PORTFORWARD_HOSTID, hostId); + values.put(HostDatabase.FIELD_PORTFORWARD_NICKNAME, nickname); + values.put(HostDatabase.FIELD_PORTFORWARD_TYPE, type); + values.put(HostDatabase.FIELD_PORTFORWARD_SOURCEPORT, sourcePort); + values.put(HostDatabase.FIELD_PORTFORWARD_DESTADDR, destAddr); + values.put(HostDatabase.FIELD_PORTFORWARD_DESTPORT, destPort); + + return values; + } +} diff --git a/app/src/main/java/org/connectbot/bean/PubkeyBean.java b/app/src/main/java/org/connectbot/bean/PubkeyBean.java new file mode 100644 index 0000000..656c6af --- /dev/null +++ b/app/src/main/java/org/connectbot/bean/PubkeyBean.java @@ -0,0 +1,234 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.bean; + +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; + +import org.connectbot.util.PubkeyDatabase; +import org.connectbot.util.PubkeyUtils; + +import android.content.ContentValues; + +/** + * @author Kenny Root + * + */ +public class PubkeyBean extends AbstractBean { + public static final String BEAN_NAME = "pubkey"; + + private static final String KEY_TYPE_RSA = "RSA"; + + private static final String KEY_TYPE_DSA = "DSA"; + + private static final String KEY_TYPE_EC = "EC"; + + /* Database fields */ + private long id; + private String nickname; + private String type; + private byte[] privateKey; + private byte[] publicKey; + private boolean encrypted = false; + private boolean startup = false; + private boolean confirmUse = false; + private int lifetime = 0; + + /* Transient values */ + private transient boolean unlocked = false; + private transient Object unlockedPrivate = null; + private transient String description; + + @Override + public String getBeanName() { + return BEAN_NAME; + } + + public void setId(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + + public String getNickname() { + return nickname; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setPrivateKey(byte[] privateKey) { + if (privateKey == null) + this.privateKey = null; + else + this.privateKey = privateKey.clone(); + } + + public byte[] getPrivateKey() { + if (privateKey == null) + return null; + else + return privateKey.clone(); + } + + public void setPublicKey(byte[] encoded) { + if (encoded == null) + publicKey = null; + else + publicKey = encoded.clone(); + } + + public byte[] getPublicKey() { + if (publicKey == null) + return null; + else + return publicKey.clone(); + } + + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } + + public boolean isEncrypted() { + return encrypted; + } + + public void setStartup(boolean startup) { + this.startup = startup; + } + + public boolean isStartup() { + return startup; + } + + public void setConfirmUse(boolean confirmUse) { + this.confirmUse = confirmUse; + } + + public boolean isConfirmUse() { + return confirmUse; + } + + public void setLifetime(int lifetime) { + this.lifetime = lifetime; + } + + public int getLifetime() { + return lifetime; + } + + public void setUnlocked(boolean unlocked) { + this.unlocked = unlocked; + } + + public boolean isUnlocked() { + return unlocked; + } + + public void setUnlockedPrivate(Object unlockedPrivate) { + this.unlockedPrivate = unlockedPrivate; + } + + public Object getUnlockedPrivate() { + return unlockedPrivate; + } + + public String getDescription() { + if (description == null) { + final StringBuilder sb = new StringBuilder(); + try { + final PublicKey pubKey = PubkeyUtils.decodePublic(privateKey, type); + if (PubkeyDatabase.KEY_TYPE_RSA.equals(type)) { + int bits = ((RSAPublicKey) pubKey).getModulus().bitLength(); + sb.append("RSA "); + sb.append(bits); + sb.append("-bit"); + } else if (PubkeyDatabase.KEY_TYPE_DSA.equals(type)) { + sb.append("DSA 1024-bit"); + } else if (PubkeyDatabase.KEY_TYPE_EC.equals(type)) { + int bits = ((ECPublicKey) pubKey).getParams().getCurve().getField() + .getFieldSize(); + sb.append("EC "); + sb.append(bits); + sb.append("-bit"); + } else { + sb.append("Unknown Key Type"); + } + } catch (NoSuchAlgorithmException e) { + sb.append("Unknown Key Type"); + } catch (InvalidKeySpecException e) { + sb.append("Unknown Key Type"); + } + + if (encrypted) + sb.append(" (encrypted)"); + + description = sb.toString(); + } + return description; + } + + /* (non-Javadoc) + * @see org.connectbot.bean.AbstractBean#getValues() + */ + @Override + public ContentValues getValues() { + ContentValues values = new ContentValues(); + + values.put(PubkeyDatabase.FIELD_PUBKEY_NICKNAME, nickname); + values.put(PubkeyDatabase.FIELD_PUBKEY_TYPE, type); + values.put(PubkeyDatabase.FIELD_PUBKEY_PRIVATE, privateKey); + values.put(PubkeyDatabase.FIELD_PUBKEY_PUBLIC, publicKey); + values.put(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED, encrypted ? 1 : 0); + values.put(PubkeyDatabase.FIELD_PUBKEY_STARTUP, startup ? 1 : 0); + values.put(PubkeyDatabase.FIELD_PUBKEY_CONFIRMUSE, confirmUse ? 1 : 0); + values.put(PubkeyDatabase.FIELD_PUBKEY_LIFETIME, lifetime); + + return values; + } + + public boolean changePassword(String oldPassword, String newPassword) throws Exception { + PrivateKey priv; + + try { + priv = PubkeyUtils.decodePrivate(getPrivateKey(), getType(), oldPassword); + } catch (Exception e) { + return false; + } + + setPrivateKey(PubkeyUtils.getEncodedPrivate(priv, newPassword)); + setEncrypted(newPassword.length() > 0); + + return true; + } +} diff --git a/app/src/main/java/org/connectbot/bean/SelectionArea.java b/app/src/main/java/org/connectbot/bean/SelectionArea.java new file mode 100644 index 0000000..1bd78c1 --- /dev/null +++ b/app/src/main/java/org/connectbot/bean/SelectionArea.java @@ -0,0 +1,201 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.bean; + +import de.mud.terminal.VDUBuffer; + +/** + * @author Kenny Root + * Keep track of a selection area for the terminal copying mechanism. + * If the orientation is flipped one way, swap the bottom and top or + * left and right to keep it in the correct orientation. + */ +public class SelectionArea { + private int top; + private int bottom; + private int left; + private int right; + private int maxColumns; + private int maxRows; + private boolean selectingOrigin; + + public SelectionArea() { + reset(); + } + + public final void reset() { + top = left = bottom = right = 0; + selectingOrigin = true; + } + + /** + * @param columns + * @param rows + */ + public void setBounds(int columns, int rows) { + maxColumns = columns - 1; + maxRows = rows - 1; + } + + private int checkBounds(int value, int max) { + if (value < 0) + return 0; + else if (value > max) + return max; + else + return value; + } + + public boolean isSelectingOrigin() { + return selectingOrigin; + } + + public void finishSelectingOrigin() { + selectingOrigin = false; + } + + public void decrementRow() { + if (selectingOrigin) + setTop(top - 1); + else + setBottom(bottom - 1); + } + + public void incrementRow() { + if (selectingOrigin) + setTop(top + 1); + else + setBottom(bottom + 1); + } + + public void setRow(int row) { + if (selectingOrigin) + setTop(row); + else + setBottom(row); + } + + private void setTop(int top) { + this.top = bottom = checkBounds(top, maxRows); + } + + public int getTop() { + return Math.min(top, bottom); + } + + private void setBottom(int bottom) { + this.bottom = checkBounds(bottom, maxRows); + } + + public int getBottom() { + return Math.max(top, bottom); + } + + public void decrementColumn() { + if (selectingOrigin) + setLeft(left - 1); + else + setRight(right - 1); + } + + public void incrementColumn() { + if (selectingOrigin) + setLeft(left + 1); + else + setRight(right + 1); + } + + public void setColumn(int column) { + if (selectingOrigin) + setLeft(column); + else + setRight(column); + } + + private void setLeft(int left) { + this.left = right = checkBounds(left, maxColumns); + } + + public int getLeft() { + return Math.min(left, right); + } + + private void setRight(int right) { + this.right = checkBounds(right, maxColumns); + } + + public int getRight() { + return Math.max(left, right); + } + + public String copyFrom(VDUBuffer vb) { + int size = (getRight() - getLeft() + 1) * (getBottom() - getTop() + 1); + + StringBuffer buffer = new StringBuffer(size); + + for (int y = getTop(); y <= getBottom(); y++) { + int lastNonSpace = buffer.length(); + + for (int x = getLeft(); x <= getRight(); x++) { + // only copy printable chars + char c = vb.getChar(x, y); + + if (!Character.isDefined(c) || + (Character.isISOControl(c) && c != '\t')) + c = ' '; + + if (c != ' ') + lastNonSpace = buffer.length(); + + buffer.append(c); + } + + // Don't leave a bunch of spaces in our copy buffer. + if (buffer.length() > lastNonSpace) + buffer.delete(lastNonSpace + 1, buffer.length()); + + if (y != bottom) + buffer.append("\n"); + } + + return buffer.toString(); + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + + buffer.append("SelectionArea[top="); + buffer.append(top); + buffer.append(", bottom="); + buffer.append(bottom); + buffer.append(", left="); + buffer.append(left); + buffer.append(", right="); + buffer.append(right); + buffer.append(", maxColumns="); + buffer.append(maxColumns); + buffer.append(", maxRows="); + buffer.append(maxRows); + buffer.append(", isSelectingOrigin="); + buffer.append(isSelectingOrigin()); + buffer.append("]"); + + return buffer.toString(); + } +} diff --git a/app/src/main/java/org/connectbot/service/BackupAgent.java b/app/src/main/java/org/connectbot/service/BackupAgent.java new file mode 100644 index 0000000..312ae3b --- /dev/null +++ b/app/src/main/java/org/connectbot/service/BackupAgent.java @@ -0,0 +1,76 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +import java.io.IOException; + +import org.connectbot.util.HostDatabase; +import org.connectbot.util.PreferenceConstants; +import org.connectbot.util.PubkeyDatabase; + +import android.annotation.TargetApi; +import android.app.backup.BackupAgentHelper; +import android.app.backup.BackupDataInput; +import android.app.backup.BackupDataOutput; +import android.app.backup.FileBackupHelper; +import android.app.backup.SharedPreferencesBackupHelper; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +/** + * ConnectBot's backup agent. This is only loaded on API 8 and later by + * reading the AndroidManifest.xml, so it shouldn't affect any minimum + * SDK level. + */ +@TargetApi(8) +public class BackupAgent extends BackupAgentHelper { + @Override + public void onCreate() { + Log.d("ConnectBot.BackupAgent", "onCreate called"); + + SharedPreferencesBackupHelper prefs = new SharedPreferencesBackupHelper(this, getPackageName() + "_preferences"); + addHelper(PreferenceConstants.BACKUP_PREF_KEY, prefs); + + FileBackupHelper hosts = new FileBackupHelper(this, "../databases/" + HostDatabase.DB_NAME); + addHelper(HostDatabase.DB_NAME, hosts); + + FileBackupHelper pubkeys = new FileBackupHelper(this, "../databases/" + PubkeyDatabase.DB_NAME); + addHelper(PubkeyDatabase.DB_NAME, pubkeys); + + } + + @Override + public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) throws IOException { + synchronized (HostDatabase.dbLock) { + super.onBackup(oldState, data, newState); + } + } + + @Override + public void onRestore(BackupDataInput data, int appVersionCode, + ParcelFileDescriptor newState) throws IOException { + Log.d("ConnectBot.BackupAgent", "onRestore called"); + + synchronized (HostDatabase.dbLock) { + Log.d("ConnectBot.BackupAgent", "onRestore in-lock"); + + super.onRestore(data, appVersionCode, newState); + } + } +} diff --git a/app/src/main/java/org/connectbot/service/BackupWrapper.java b/app/src/main/java/org/connectbot/service/BackupWrapper.java new file mode 100644 index 0000000..11f1b52 --- /dev/null +++ b/app/src/main/java/org/connectbot/service/BackupWrapper.java @@ -0,0 +1,72 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +import org.connectbot.util.PreferenceConstants; + +import android.annotation.TargetApi; +import android.app.backup.BackupManager; +import android.content.Context; + +/** + * This is only invoked on Froyo and beyond. + */ +@TargetApi(8) +public abstract class BackupWrapper { + public static BackupWrapper getInstance() { + if (PreferenceConstants.PRE_FROYO) + return PreFroyo.Holder.sInstance; + else + return FroyoAndBeyond.Holder.sInstance; + } + + public abstract void onDataChanged(Context context); + + private static class PreFroyo extends BackupWrapper { + private static class Holder { + private static final PreFroyo sInstance = new PreFroyo(); + } + + @Override + public void onDataChanged(Context context) { + // do nothing for now + } + } + + private static class FroyoAndBeyond extends BackupWrapper { + private static class Holder { + private static final FroyoAndBeyond sInstance = new FroyoAndBeyond(); + } + + private static BackupManager mBackupManager; + + @Override + public void onDataChanged(Context context) { + checkBackupManager(context); + if (mBackupManager != null) { + mBackupManager.dataChanged(); + } + } + + private void checkBackupManager(Context context) { + if (mBackupManager == null) { + mBackupManager = new BackupManager(context); + } + } + } +} diff --git a/app/src/main/java/org/connectbot/service/BridgeDisconnectedListener.java b/app/src/main/java/org/connectbot/service/BridgeDisconnectedListener.java new file mode 100644 index 0000000..21c41d1 --- /dev/null +++ b/app/src/main/java/org/connectbot/service/BridgeDisconnectedListener.java @@ -0,0 +1,22 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +public interface BridgeDisconnectedListener { + public void onDisconnected(TerminalBridge bridge); +} diff --git a/app/src/main/java/org/connectbot/service/ConnectionNotifier.java b/app/src/main/java/org/connectbot/service/ConnectionNotifier.java new file mode 100644 index 0000000..e42525d --- /dev/null +++ b/app/src/main/java/org/connectbot/service/ConnectionNotifier.java @@ -0,0 +1,194 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.connectbot.ConsoleActivity; +import org.connectbot.R; +import org.connectbot.bean.HostBean; +import org.connectbot.util.HostDatabase; +import org.connectbot.util.PreferenceConstants; + +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Color; + +/** + * @author Kenny Root + * + * Based on the concept from jasta's blog post. + */ +public abstract class ConnectionNotifier { + private static final int ONLINE_NOTIFICATION = 1; + private static final int ACTIVITY_NOTIFICATION = 2; + + public static ConnectionNotifier getInstance() { + if (PreferenceConstants.PRE_ECLAIR) + return PreEclair.Holder.sInstance; + else + return EclairAndBeyond.Holder.sInstance; + } + + protected NotificationManager getNotificationManager(Context context) { + return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + + protected Notification newNotification(Context context) { + Notification notification = new Notification(); + notification.icon = R.drawable.notification_icon; + notification.when = System.currentTimeMillis(); + + return notification; + } + + protected Notification newActivityNotification(Context context, HostBean host) { + Notification notification = newNotification(context); + + Resources res = context.getResources(); + + String contentText = res.getString( + R.string.notification_text, host.getNickname()); + + Intent notificationIntent = new Intent(context, ConsoleActivity.class); + notificationIntent.setAction("android.intent.action.VIEW"); + notificationIntent.setData(host.getUri()); + + PendingIntent contentIntent = PendingIntent.getActivity(context, 0, + notificationIntent, 0); + + notification.setLatestEventInfo(context, res.getString(R.string.app_name), contentText, contentIntent); + + notification.flags = Notification.FLAG_AUTO_CANCEL; + + notification.flags |= Notification.DEFAULT_LIGHTS; + if (HostDatabase.COLOR_RED.equals(host.getColor())) + notification.ledARGB = Color.RED; + else if (HostDatabase.COLOR_GREEN.equals(host.getColor())) + notification.ledARGB = Color.GREEN; + else if (HostDatabase.COLOR_BLUE.equals(host.getColor())) + notification.ledARGB = Color.BLUE; + else + notification.ledARGB = Color.WHITE; + notification.ledOnMS = 300; + notification.ledOffMS = 1000; + notification.flags |= Notification.FLAG_SHOW_LIGHTS; + + return notification; + } + + protected Notification newRunningNotification(Context context) { + Notification notification = newNotification(context); + + notification.flags = Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR; + notification.when = 0; + + notification.contentIntent = PendingIntent.getActivity(context, + ONLINE_NOTIFICATION, + new Intent(context, ConsoleActivity.class), 0); + + Resources res = context.getResources(); + + notification.setLatestEventInfo(context, + res.getString(R.string.app_name), + res.getString(R.string.app_is_running), + notification.contentIntent); + + return notification; + } + + public void showActivityNotification(Service context, HostBean host) { + getNotificationManager(context).notify(ACTIVITY_NOTIFICATION, newActivityNotification(context, host)); + } + + public void hideActivityNotification(Service context) { + getNotificationManager(context).cancel(ACTIVITY_NOTIFICATION); + } + + public abstract void showRunningNotification(Service context); + public abstract void hideRunningNotification(Service context); + + private static class PreEclair extends ConnectionNotifier { + private static final Class<?>[] setForegroundSignature = new Class[] {boolean.class}; + private Method setForeground = null; + + private static class Holder { + private static final PreEclair sInstance = new PreEclair(); + } + + public PreEclair() { + try { + setForeground = Service.class.getMethod("setForeground", setForegroundSignature); + } catch (Exception e) { + } + } + + @Override + public void showRunningNotification(Service context) { + if (setForeground != null) { + Object[] setForegroundArgs = new Object[1]; + setForegroundArgs[0] = Boolean.TRUE; + try { + setForeground.invoke(context, setForegroundArgs); + } catch (InvocationTargetException e) { + } catch (IllegalAccessException e) { + } + getNotificationManager(context).notify(ONLINE_NOTIFICATION, newRunningNotification(context)); + } + } + + @Override + public void hideRunningNotification(Service context) { + if (setForeground != null) { + Object[] setForegroundArgs = new Object[1]; + setForegroundArgs[0] = Boolean.FALSE; + try { + setForeground.invoke(context, setForegroundArgs); + } catch (InvocationTargetException e) { + } catch (IllegalAccessException e) { + } + getNotificationManager(context).cancel(ONLINE_NOTIFICATION); + } + } + } + + @TargetApi(5) + private static class EclairAndBeyond extends ConnectionNotifier { + private static class Holder { + private static final EclairAndBeyond sInstance = new EclairAndBeyond(); + } + + @Override + public void showRunningNotification(Service context) { + context.startForeground(ONLINE_NOTIFICATION, newRunningNotification(context)); + } + + @Override + public void hideRunningNotification(Service context) { + context.stopForeground(true); + } + } +} diff --git a/app/src/main/java/org/connectbot/service/ConnectivityReceiver.java b/app/src/main/java/org/connectbot/service/ConnectivityReceiver.java new file mode 100644 index 0000000..1342661 --- /dev/null +++ b/app/src/main/java/org/connectbot/service/ConnectivityReceiver.java @@ -0,0 +1,154 @@ +/** + * + */ +package org.connectbot.service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiManager.WifiLock; +import android.util.Log; + +/** + * @author kroot + * + */ +public class ConnectivityReceiver extends BroadcastReceiver { + private static final String TAG = "ConnectBot.ConnectivityManager"; + + private boolean mIsConnected = false; + + final private TerminalManager mTerminalManager; + + final private WifiLock mWifiLock; + + private int mNetworkRef = 0; + + private boolean mLockingWifi; + + private Object[] mLock = new Object[0]; + + public ConnectivityReceiver(TerminalManager manager, boolean lockingWifi) { + mTerminalManager = manager; + + final ConnectivityManager cm = + (ConnectivityManager) manager.getSystemService(Context.CONNECTIVITY_SERVICE); + + final WifiManager wm = (WifiManager) manager.getSystemService(Context.WIFI_SERVICE); + mWifiLock = wm.createWifiLock(TAG); + + final NetworkInfo info = cm.getActiveNetworkInfo(); + if (info != null) { + mIsConnected = (info.getState() == State.CONNECTED); + } + + mLockingWifi = lockingWifi; + + final IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + manager.registerReceiver(this, filter); + } + + /* (non-Javadoc) + * @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent) + */ + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + + if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + Log.w(TAG, "onReceived() called: " + intent); + return; + } + + boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); + boolean isFailover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false); + + Log.d(TAG, "onReceived() called; noConnectivity? " + noConnectivity + "; isFailover? " + isFailover); + + if (noConnectivity && !isFailover && mIsConnected) { + mIsConnected = false; + mTerminalManager.onConnectivityLost(); + } else if (!mIsConnected) { + NetworkInfo info = (NetworkInfo) intent.getExtras() + .get(ConnectivityManager.EXTRA_NETWORK_INFO); + + if (mIsConnected = (info.getState() == State.CONNECTED)) { + mTerminalManager.onConnectivityRestored(); + } + } + } + + /** + * + */ + public void cleanup() { + if (mWifiLock.isHeld()) + mWifiLock.release(); + + mTerminalManager.unregisterReceiver(this); + } + + /** + * Increase the number of things using the network. Acquire a Wi-Fi lock + * if necessary. + */ + public void incRef() { + synchronized (mLock) { + mNetworkRef += 1; + + acquireWifiLockIfNecessaryLocked(); + } + } + + /** + * Decrease the number of things using the network. Release the Wi-Fi lock + * if necessary. + */ + public void decRef() { + synchronized (mLock) { + mNetworkRef -= 1; + + releaseWifiLockIfNecessaryLocked(); + } + } + + /** + * @param mLockingWifi + */ + public void setWantWifiLock(boolean lockingWifi) { + synchronized (mLock) { + mLockingWifi = lockingWifi; + + if (mLockingWifi) { + acquireWifiLockIfNecessaryLocked(); + } else { + releaseWifiLockIfNecessaryLocked(); + } + } + } + + private void acquireWifiLockIfNecessaryLocked() { + if (mLockingWifi && mNetworkRef > 0 && !mWifiLock.isHeld()) { + mWifiLock.acquire(); + } + } + + private void releaseWifiLockIfNecessaryLocked() { + if (mNetworkRef == 0 && mWifiLock.isHeld()) { + mWifiLock.release(); + } + } + + /** + * @return whether we're connected to a network + */ + public boolean isConnected() { + return mIsConnected; + } +} diff --git a/app/src/main/java/org/connectbot/service/FontSizeChangedListener.java b/app/src/main/java/org/connectbot/service/FontSizeChangedListener.java new file mode 100644 index 0000000..56b9971 --- /dev/null +++ b/app/src/main/java/org/connectbot/service/FontSizeChangedListener.java @@ -0,0 +1,31 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +/** + * @author Kenny Root + * + */ +public interface FontSizeChangedListener { + + /** + * @param sizeDp + * new font size in dp + */ + void onFontSizeChanged(float sizeDp); +} diff --git a/app/src/main/java/org/connectbot/service/KeyEventUtil.java b/app/src/main/java/org/connectbot/service/KeyEventUtil.java new file mode 100644 index 0000000..1362afa --- /dev/null +++ b/app/src/main/java/org/connectbot/service/KeyEventUtil.java @@ -0,0 +1,149 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2014 Torne Wuff + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.connectbot.service; + +import android.annotation.TargetApi; +import android.os.Build; +import android.view.KeyEvent; + +public class KeyEventUtil { + static final char CONTROL_LIMIT = ' '; + static final char PRINTABLE_LIMIT = '\u007e'; + static final char[] HEX_DIGITS = new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + static String printableRepresentation(String source) { + if (source == null) + return null; + + final StringBuilder sb = new StringBuilder(); + final int limit = source.length(); + char[] hexbuf = null; + int pointer = 0; + + sb.append('"'); + while (pointer < limit) { + int ch = source.charAt(pointer++); + switch (ch) { + case '\0': + sb.append("\\0"); + break; + case '\t': + sb.append("\\t"); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\"': + sb.append("\\\""); + break; + case '\\': + sb.append("\\\\"); + break; + default: + if (CONTROL_LIMIT <= ch && ch <= PRINTABLE_LIMIT) { + sb.append((char) ch); + } else { + sb.append("\\u"); + if (hexbuf == null) + hexbuf = new char[4]; + for (int offs = 4; offs > 0; ) { + hexbuf[--offs] = HEX_DIGITS[ch & 0xf]; + ch >>>= 4; + } + sb.append(hexbuf, 0, 4); + } + } + } + return sb.append('"').toString(); + } + + private static class ClassCompat { + private static final ClassCompat INSTANCE; + static { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { + INSTANCE = new HCMR2AndNewer(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { + INSTANCE = new HCMR1AndNewer(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { + INSTANCE = new GingerbreadAndNewer(); + } else { + INSTANCE = new ClassCompat(); + } + } + + private ClassCompat() { + } + + public static void appendExtras(StringBuilder d, int keyCode, KeyEvent event) { + INSTANCE.appendForApi(d, keyCode, event); + } + + protected void appendForApi(StringBuilder d, int keyCode, KeyEvent event) { + } + + @TargetApi(9) + private static class GingerbreadAndNewer extends ClassCompat { + @Override + protected void appendForApi(StringBuilder d, int keyCode, KeyEvent event) { + super.appendForApi(d, keyCode, event); + d.append(", source=").append(event.getSource()); + + } + } + @TargetApi(12) + private static class HCMR1AndNewer extends GingerbreadAndNewer { + protected void appendForApi(StringBuilder d, int keyCode, KeyEvent event) { + super.appendForApi(d, keyCode, event); + d.append(", keyCodeToString=").append(KeyEvent.keyCodeToString(keyCode)); + } + } + + @TargetApi(13) + private static class HCMR2AndNewer extends HCMR1AndNewer { + @Override + protected void appendForApi(StringBuilder d, int keyCode, KeyEvent event) { + super.appendForApi(d, keyCode, event); + d.append(", modifiers=").append(Integer.toHexString(event.getModifiers())); + } + } + } + + public static String describeKeyEvent(int keyCode, KeyEvent event) { + StringBuilder d = new StringBuilder(); + d.append("keyCode=").append(keyCode); + d.append(", event.toString=").append(event.toString()); + d.append(", action=").append(event.getAction()); + d.append(", characters=").append(printableRepresentation(event.getCharacters())); + d.append(", deviceId=").append(event.getDeviceId()); + d.append(", displayLabel=").append((int) event.getDisplayLabel()); + d.append(", flags=0x").append(Integer.toHexString(event.getFlags())); + d.append(", printingKey=").append(event.isPrintingKey()); + d.append(", keyCode=").append(event.getKeyCode()); + d.append(", metaState=0x").append(Integer.toHexString(event.getMetaState())); + d.append(", number=").append((int) event.getNumber()); + d.append(", scanCode=").append(event.getScanCode()); + d.append(", unicodeChar=").append(event.getUnicodeChar()); + ClassCompat.appendExtras(d, keyCode, event); + return d.toString(); + } +} diff --git a/app/src/main/java/org/connectbot/service/PromptHelper.java b/app/src/main/java/org/connectbot/service/PromptHelper.java new file mode 100644 index 0000000..9c34f1a --- /dev/null +++ b/app/src/main/java/org/connectbot/service/PromptHelper.java @@ -0,0 +1,159 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +import java.util.concurrent.Semaphore; + +import android.os.Handler; +import android.os.Message; + +/** + * Helps provide a relay for prompts and responses between a possible user + * interface and some underlying service. + * + * @author jsharkey + */ +public class PromptHelper { + private final Object tag; + + private Handler handler = null; + + private Semaphore promptToken; + private Semaphore promptResponse; + + public String promptInstructions = null; + public String promptHint = null; + public Object promptRequested = null; + + private Object response = null; + + public PromptHelper(Object tag) { + this.tag = tag; + + // Threads must acquire this before they can send a prompt. + promptToken = new Semaphore(1); + + // Responses will release this semaphore. + promptResponse = new Semaphore(0); + } + + + /** + * Register a user interface handler, if available. + */ + public void setHandler(Handler handler) { + this.handler = handler; + } + + /** + * Set an incoming value from an above user interface. Will automatically + * notify any waiting requests. + */ + public void setResponse(Object value) { + response = value; + promptRequested = null; + promptInstructions = null; + promptHint = null; + promptResponse.release(); + } + + /** + * Return the internal response value just before erasing and returning it. + */ + protected Object popResponse() { + Object value = response; + response = null; + return value; + } + + + /** + * Request a prompt response from parent. This is a blocking call until user + * interface returns a value. + * Only one thread can call this at a time. cancelPrompt() will force this to + * immediately return. + */ + private Object requestPrompt(String instructions, String hint, Object type) throws InterruptedException { + Object response = null; + + promptToken.acquire(); + + try { + promptInstructions = instructions; + promptHint = hint; + promptRequested = type; + + // notify any parent watching for live events + if (handler != null) + Message.obtain(handler, -1, tag).sendToTarget(); + + // acquire lock until user passes back value + promptResponse.acquire(); + + response = popResponse(); + } finally { + promptToken.release(); + } + + return response; + } + + /** + * Request a string response from parent. This is a blocking call until user + * interface returns a value. + * @param hint prompt hint for user to answer + * @return string user has entered + */ + public String requestStringPrompt(String instructions, String hint) { + String value = null; + try { + value = (String) requestPrompt(instructions, hint, String.class); + } catch (Exception e) { + } + return value; + } + + /** + * Request a boolean response from parent. This is a blocking call until user + * interface returns a value. + * @param hint prompt hint for user to answer + * @return choice user has made (yes/no) + */ + public Boolean requestBooleanPrompt(String instructions, String hint) { + Boolean value = null; + try { + value = (Boolean) requestPrompt(instructions, hint, Boolean.class); + } catch (Exception e) { + } + return value; + } + + /** + * Cancel an in-progress prompt. + */ + public void cancelPrompt() { + if (!promptToken.tryAcquire()) { + // A thread has the token, so try to interrupt it + response = null; + promptResponse.release(); + } else { + // No threads have acquired the token + promptToken.release(); + } + } +} diff --git a/app/src/main/java/org/connectbot/service/Relay.java b/app/src/main/java/org/connectbot/service/Relay.java new file mode 100644 index 0000000..36672ec --- /dev/null +++ b/app/src/main/java/org/connectbot/service/Relay.java @@ -0,0 +1,145 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; + +import org.apache.harmony.niochar.charset.additional.IBM437; +import org.connectbot.transport.AbsTransport; +import org.connectbot.util.EastAsianWidth; + +import android.util.Log; +import de.mud.terminal.vt320; + +/** + * @author Kenny Root + */ +public class Relay implements Runnable { + private static final String TAG = "ConnectBot.Relay"; + + private static final int BUFFER_SIZE = 4096; + + private TerminalBridge bridge; + + private Charset currentCharset; + private CharsetDecoder decoder; + + private AbsTransport transport; + + private vt320 buffer; + + private ByteBuffer byteBuffer; + private CharBuffer charBuffer; + + private byte[] byteArray; + private char[] charArray; + + public Relay(TerminalBridge bridge, AbsTransport transport, vt320 buffer, String encoding) { + setCharset(encoding); + this.bridge = bridge; + this.transport = transport; + this.buffer = buffer; + } + + public void setCharset(String encoding) { + Log.d("ConnectBot.Relay", "changing charset to " + encoding); + Charset charset; + if (encoding.equals("CP437")) + charset = new IBM437("IBM437", + new String[] { "IBM437", "CP437" }); + else + charset = Charset.forName(encoding); + + if (charset == currentCharset || charset == null) + return; + + CharsetDecoder newCd = charset.newDecoder(); + newCd.onUnmappableCharacter(CodingErrorAction.REPLACE); + newCd.onMalformedInput(CodingErrorAction.REPLACE); + + currentCharset = charset; + synchronized (this) { + decoder = newCd; + } + } + + public Charset getCharset() { + return currentCharset; + } + + public void run() { + byteBuffer = ByteBuffer.allocate(BUFFER_SIZE); + charBuffer = CharBuffer.allocate(BUFFER_SIZE); + + /* for East Asian character widths */ + byte[] wideAttribute = new byte[BUFFER_SIZE]; + + byteArray = byteBuffer.array(); + charArray = charBuffer.array(); + + CoderResult result; + + int bytesRead = 0; + byteBuffer.limit(0); + int bytesToRead; + int offset; + int charWidth; + + EastAsianWidth measurer = EastAsianWidth.getInstance(); + + try { + while (true) { + charWidth = bridge.charWidth; + bytesToRead = byteBuffer.capacity() - byteBuffer.limit(); + offset = byteBuffer.arrayOffset() + byteBuffer.limit(); + bytesRead = transport.read(byteArray, offset, bytesToRead); + + if (bytesRead > 0) { + byteBuffer.limit(byteBuffer.limit() + bytesRead); + + synchronized (this) { + result = decoder.decode(byteBuffer, charBuffer, false); + } + + if (result.isUnderflow() && + byteBuffer.limit() == byteBuffer.capacity()) { + byteBuffer.compact(); + byteBuffer.limit(byteBuffer.position()); + byteBuffer.position(0); + } + + offset = charBuffer.position(); + + measurer.measure(charArray, 0, offset, wideAttribute, bridge.defaultPaint, charWidth); + buffer.putString(charArray, wideAttribute, 0, charBuffer.position()); + bridge.propagateConsoleText(charArray, charBuffer.position()); + charBuffer.clear(); + bridge.redraw(); + } + } + } catch (IOException e) { + Log.e(TAG, "Problem while handling incoming data in relay thread", e); + } + } +} diff --git a/app/src/main/java/org/connectbot/service/TerminalBridge.java b/app/src/main/java/org/connectbot/service/TerminalBridge.java new file mode 100644 index 0000000..15b9d73 --- /dev/null +++ b/app/src/main/java/org/connectbot/service/TerminalBridge.java @@ -0,0 +1,1032 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.connectbot.R; +import org.connectbot.TerminalView; +import org.connectbot.bean.HostBean; +import org.connectbot.bean.PortForwardBean; +import org.connectbot.bean.SelectionArea; +import org.connectbot.transport.AbsTransport; +import org.connectbot.transport.TransportFactory; +import org.connectbot.util.HostDatabase; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.FontMetrics; +import android.graphics.Typeface; +import android.text.ClipboardManager; +import android.util.Log; +import de.mud.terminal.VDUBuffer; +import de.mud.terminal.VDUDisplay; +import de.mud.terminal.vt320; + + +/** + * Provides a bridge between a MUD terminal buffer and a possible TerminalView. + * This separation allows us to keep the TerminalBridge running in a background + * service. A TerminalView shares down a bitmap that we can use for rendering + * when available. + * + * This class also provides SSH hostkey verification prompting, and password + * prompting. + */ +@SuppressWarnings("deprecation") // for ClipboardManager +public class TerminalBridge implements VDUDisplay { + public final static String TAG = "ConnectBot.TerminalBridge"; + + public final static int DEFAULT_FONT_SIZE_DP = 10; + private final static int FONT_SIZE_STEP = 2; + private final float displayDensity; + + public Integer[] color; + + public int defaultFg = HostDatabase.DEFAULT_FG_COLOR; + public int defaultBg = HostDatabase.DEFAULT_BG_COLOR; + + protected final TerminalManager manager; + + public HostBean host; + + /* package */ AbsTransport transport; + + final Paint defaultPaint; + + private Relay relay; + + private final String emulation; + private final int scrollback; + + public Bitmap bitmap = null; + public VDUBuffer buffer = null; + + private TerminalView parent = null; + private final Canvas canvas = new Canvas(); + + private boolean disconnected = false; + private boolean awaitingClose = false; + + private boolean forcedSize = false; + private int columns; + private int rows; + + /* package */ final TerminalKeyListener keyListener; + + private boolean selectingForCopy = false; + private final SelectionArea selectionArea; + + // TODO add support for the new clipboard API + private ClipboardManager clipboard; + + public int charWidth = -1; + public int charHeight = -1; + private int charTop = -1; + + private float fontSizeDp = -1; + + private final List<FontSizeChangedListener> fontSizeChangedListeners; + + private final List<String> localOutput; + + /** + * Flag indicating if we should perform a full-screen redraw during our next + * rendering pass. + */ + private boolean fullRedraw = false; + + public PromptHelper promptHelper; + + protected BridgeDisconnectedListener disconnectListener = null; + + /** + * Create a new terminal bridge suitable for unit testing. + */ + public TerminalBridge() { + buffer = new vt320() { + @Override + public void write(byte[] b) {} + @Override + public void write(int b) {} + @Override + public void sendTelnetCommand(byte cmd) {} + @Override + public void setWindowSize(int c, int r) {} + @Override + public void debug(String s) {} + }; + + emulation = null; + manager = null; + + displayDensity = 1f; + + defaultPaint = new Paint(); + + selectionArea = new SelectionArea(); + scrollback = 1; + + localOutput = new LinkedList<String>(); + + fontSizeChangedListeners = new LinkedList<FontSizeChangedListener>(); + + transport = null; + + keyListener = new TerminalKeyListener(manager, this, buffer, null); + } + + /** + * Create new terminal bridge with following parameters. We will immediately + * launch thread to start SSH connection and handle any hostkey verification + * and password authentication. + */ + public TerminalBridge(final TerminalManager manager, final HostBean host) throws IOException { + this.manager = manager; + this.host = host; + + emulation = manager.getEmulation(); + scrollback = manager.getScrollback(); + + this.displayDensity = manager.getResources().getDisplayMetrics().density; + + // create prompt helper to relay password and hostkey requests up to gui + promptHelper = new PromptHelper(this); + + // create our default paint + defaultPaint = new Paint(); + defaultPaint.setAntiAlias(true); + defaultPaint.setTypeface(Typeface.MONOSPACE); + defaultPaint.setFakeBoldText(true); // more readable? + + localOutput = new LinkedList<String>(); + + fontSizeChangedListeners = new LinkedList<FontSizeChangedListener>(); + + int hostFontSizeDp = host.getFontSize(); + if (hostFontSizeDp <= 0) { + hostFontSizeDp = DEFAULT_FONT_SIZE_DP; + } + setFontSize(hostFontSizeDp); + + // create terminal buffer and handle outgoing data + // this is probably status reply information + buffer = new vt320() { + @Override + public void debug(String s) { + Log.d(TAG, s); + } + + @Override + public void write(byte[] b) { + try { + if (b != null && transport != null) + transport.write(b); + } catch (IOException e) { + Log.e(TAG, "Problem writing outgoing data in vt320() thread", e); + } + } + + @Override + public void write(int b) { + try { + if (transport != null) + transport.write(b); + } catch (IOException e) { + Log.e(TAG, "Problem writing outgoing data in vt320() thread", e); + } + } + + // We don't use telnet sequences. + @Override + public void sendTelnetCommand(byte cmd) { + } + + // We don't want remote to resize our window. + @Override + public void setWindowSize(int c, int r) { + } + + @Override + public void beep() { + if (parent.isShown()) + manager.playBeep(); + else + manager.sendActivityNotification(host); + } + }; + + // Don't keep any scrollback if a session is not being opened. + if (host.getWantSession()) + buffer.setBufferSize(scrollback); + else + buffer.setBufferSize(0); + + resetColors(); + buffer.setDisplay(this); + + selectionArea = new SelectionArea(); + + keyListener = new TerminalKeyListener(manager, this, buffer, host.getEncoding()); + } + + public PromptHelper getPromptHelper() { + return promptHelper; + } + + /** + * Spawn thread to open connection and start login process. + */ + protected void startConnection() { + transport = TransportFactory.getTransport(host.getProtocol()); + transport.setBridge(this); + transport.setManager(manager); + transport.setHost(host); + + // TODO make this more abstract so we don't litter on AbsTransport + transport.setCompression(host.getCompression()); + transport.setUseAuthAgent(host.getUseAuthAgent()); + transport.setEmulation(emulation); + + if (transport.canForwardPorts()) { + for (PortForwardBean portForward : manager.hostdb.getPortForwardsForHost(host)) + transport.addPortForward(portForward); + } + + outputLine(manager.res.getString(R.string.terminal_connecting, host.getHostname(), host.getPort(), host.getProtocol())); + + Thread connectionThread = new Thread(new Runnable() { + public void run() { + transport.connect(); + } + }); + connectionThread.setName("Connection"); + connectionThread.setDaemon(true); + connectionThread.start(); + } + + /** + * Handle challenges from keyboard-interactive authentication mode. + */ + public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) { + String[] responses = new String[numPrompts]; + for (int i = 0; i < numPrompts; i++) { + // request response from user for each prompt + responses[i] = promptHelper.requestStringPrompt(instruction, prompt[i]); + } + return responses; + } + + /** + * @return charset in use by bridge + */ + public Charset getCharset() { + return relay.getCharset(); + } + + /** + * Sets the encoding used by the terminal. If the connection is live, + * then the character set is changed for the next read. + * @param encoding the canonical name of the character encoding + */ + public void setCharset(String encoding) { + if (relay != null) + relay.setCharset(encoding); + keyListener.setCharset(encoding); + } + + /** + * Convenience method for writing a line into the underlying MUD buffer. + * Should never be called once the session is established. + */ + public final void outputLine(String line) { + if (transport != null && transport.isSessionOpen()) + Log.e(TAG, "Session established, cannot use outputLine!", new IOException("outputLine call traceback")); + + synchronized (localOutput) { + final String s = line + "\r\n"; + + localOutput.add(s); + + ((vt320) buffer).putString(s); + + // For accessibility + final char[] charArray = s.toCharArray(); + propagateConsoleText(charArray, charArray.length); + } + } + + /** + * Inject a specific string into this terminal. Used for post-login strings + * and pasting clipboard. + */ + public void injectString(final String string) { + if (string == null || string.length() == 0) + return; + + Thread injectStringThread = new Thread(new Runnable() { + public void run() { + try { + transport.write(string.getBytes(host.getEncoding())); + } catch (Exception e) { + Log.e(TAG, "Couldn't inject string to remote host: ", e); + } + } + }); + injectStringThread.setName("InjectString"); + injectStringThread.start(); + } + + /** + * Internal method to request actual PTY terminal once we've finished + * authentication. If called before authenticated, it will just fail. + */ + public void onConnected() { + disconnected = false; + + ((vt320) buffer).reset(); + + // We no longer need our local output. + localOutput.clear(); + + // previously tried vt100 and xterm for emulation modes + // "screen" works the best for color and escape codes + ((vt320) buffer).setAnswerBack(emulation); + + if (HostDatabase.DELKEY_BACKSPACE.equals(host.getDelKey())) + ((vt320) buffer).setBackspace(vt320.DELETE_IS_BACKSPACE); + else + ((vt320) buffer).setBackspace(vt320.DELETE_IS_DEL); + + // create thread to relay incoming connection data to buffer + relay = new Relay(this, transport, (vt320) buffer, host.getEncoding()); + Thread relayThread = new Thread(relay); + relayThread.setDaemon(true); + relayThread.setName("Relay"); + relayThread.start(); + + // force font-size to make sure we resizePTY as needed + setFontSize(fontSizeDp); + + // finally send any post-login string, if requested + injectString(host.getPostLogin()); + } + + /** + * @return whether a session is open or not + */ + public boolean isSessionOpen() { + if (transport != null) + return transport.isSessionOpen(); + return false; + } + + public void setOnDisconnectedListener(BridgeDisconnectedListener disconnectListener) { + this.disconnectListener = disconnectListener; + } + + /** + * Force disconnection of this terminal bridge. + */ + public void dispatchDisconnect(boolean immediate) { + // We don't need to do this multiple times. + synchronized (this) { + if (disconnected && !immediate) + return; + + disconnected = true; + } + + // Cancel any pending prompts. + promptHelper.cancelPrompt(); + + // disconnection request hangs if we havent really connected to a host yet + // temporary fix is to just spawn disconnection into a thread + Thread disconnectThread = new Thread(new Runnable() { + public void run() { + if (transport != null && transport.isConnected()) + transport.close(); + } + }); + disconnectThread.setName("Disconnect"); + disconnectThread.start(); + + if (immediate || (host.getQuickDisconnect() && !host.getStayConnected())) { + awaitingClose = true; + if (disconnectListener != null) + disconnectListener.onDisconnected(TerminalBridge.this); + } else { + { + final String line = manager.res.getString(R.string.alert_disconnect_msg); + ((vt320) buffer).putString("\r\n" + line + "\r\n"); + } + if (host.getStayConnected()) { + manager.requestReconnect(this); + return; + } + Thread disconnectPromptThread = new Thread(new Runnable() { + public void run() { + Boolean result = promptHelper.requestBooleanPrompt(null, + manager.res.getString(R.string.prompt_host_disconnected)); + if (result == null || result.booleanValue()) { + awaitingClose = true; + + // Tell the TerminalManager that we can be destroyed now. + if (disconnectListener != null) + disconnectListener.onDisconnected(TerminalBridge.this); + } + } + }); + disconnectPromptThread.setName("DisconnectPrompt"); + disconnectPromptThread.setDaemon(true); + disconnectPromptThread.start(); + } + } + + public void setSelectingForCopy(boolean selectingForCopy) { + this.selectingForCopy = selectingForCopy; + } + + public boolean isSelectingForCopy() { + return selectingForCopy; + } + + public SelectionArea getSelectionArea() { + return selectionArea; + } + + public synchronized void tryKeyVibrate() { + manager.tryKeyVibrate(); + } + + /** + * Request a different font size. Will make call to parentChanged() to make + * sure we resize PTY if needed. + * + * @param sizeDp Size of font in dp + */ + /* package */ final void setFontSize(float sizeDp) { + if (sizeDp <= 0.0) { + return; + } + + final int fontSizePx = (int) (sizeDp * this.displayDensity + 0.5f); + + defaultPaint.setTextSize(fontSizePx); + fontSizeDp = sizeDp; + + // read new metrics to get exact pixel dimensions + FontMetrics fm = defaultPaint.getFontMetrics(); + charTop = (int) Math.ceil(fm.top); + + float[] widths = new float[1]; + defaultPaint.getTextWidths("X", widths); + charWidth = (int) Math.ceil(widths[0]); + charHeight = (int) Math.ceil(fm.descent - fm.top); + + // refresh any bitmap with new font size + if (parent != null) { + parentChanged(parent); + } + + for (FontSizeChangedListener ofscl : fontSizeChangedListeners) { + ofscl.onFontSizeChanged(sizeDp); + } + + host.setFontSize((int) sizeDp); + manager.hostdb.updateFontSize(host); + + forcedSize = false; + } + + /** + * Add an {@link FontSizeChangedListener} to the list of listeners for this + * bridge. + * + * @param listener + * listener to add + */ + public void addFontSizeChangedListener(FontSizeChangedListener listener) { + fontSizeChangedListeners.add(listener); + } + + /** + * Remove an {@link FontSizeChangedListener} from the list of listeners for + * this bridge. + * + * @param listener + */ + public void removeFontSizeChangedListener(FontSizeChangedListener listener) { + fontSizeChangedListeners.remove(listener); + } + + /** + * Something changed in our parent {@link TerminalView}, maybe it's a new + * parent, or maybe it's an updated font size. We should recalculate + * terminal size information and request a PTY resize. + */ + public final synchronized void parentChanged(TerminalView parent) { + if (manager != null && !manager.isResizeAllowed()) { + Log.d(TAG, "Resize is not allowed now"); + return; + } + + this.parent = parent; + final int width = parent.getWidth(); + final int height = parent.getHeight(); + + // Something has gone wrong with our layout; we're 0 width or height! + if (width <= 0 || height <= 0) + return; + + clipboard = (ClipboardManager) parent.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + keyListener.setClipboardManager(clipboard); + + if (!forcedSize) { + // recalculate buffer size + int newColumns, newRows; + + newColumns = width / charWidth; + newRows = height / charHeight; + + // If nothing has changed in the terminal dimensions and not an intial + // draw then don't blow away scroll regions and such. + if (newColumns == columns && newRows == rows) + return; + + columns = newColumns; + rows = newRows; + } + + // reallocate new bitmap if needed + boolean newBitmap = (bitmap == null); + if (bitmap != null) + newBitmap = (bitmap.getWidth() != width || bitmap.getHeight() != height); + + if (newBitmap) { + discardBitmap(); + bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + canvas.setBitmap(bitmap); + } + + // clear out any old buffer information + defaultPaint.setColor(Color.BLACK); + canvas.drawPaint(defaultPaint); + + // Stroke the border of the terminal if the size is being forced; + if (forcedSize) { + int borderX = (columns * charWidth) + 1; + int borderY = (rows * charHeight) + 1; + + defaultPaint.setColor(Color.GRAY); + defaultPaint.setStrokeWidth(0.0f); + if (width >= borderX) + canvas.drawLine(borderX, 0, borderX, borderY + 1, defaultPaint); + if (height >= borderY) + canvas.drawLine(0, borderY, borderX + 1, borderY, defaultPaint); + } + + try { + // request a terminal pty resize + synchronized (buffer) { + buffer.setScreenSize(columns, rows, true); + } + + if (transport != null) + transport.setDimensions(columns, rows, width, height); + } catch (Exception e) { + Log.e(TAG, "Problem while trying to resize screen or PTY", e); + } + + // redraw local output if we don't have a sesson to receive our resize request + if (transport == null) { + synchronized (localOutput) { + ((vt320) buffer).reset(); + + for (String line : localOutput) + ((vt320) buffer).putString(line); + } + } + + // force full redraw with new buffer size + fullRedraw = true; + redraw(); + + parent.notifyUser(String.format("%d x %d", columns, rows)); + + Log.i(TAG, String.format("parentChanged() now width=%d, height=%d", columns, rows)); + } + + /** + * Somehow our parent {@link TerminalView} was destroyed. Now we don't need + * to redraw anywhere, and we can recycle our internal bitmap. + */ + public synchronized void parentDestroyed() { + parent = null; + discardBitmap(); + } + + private void discardBitmap() { + if (bitmap != null) + bitmap.recycle(); + bitmap = null; + } + + public void setVDUBuffer(VDUBuffer buffer) { + this.buffer = buffer; + } + + public VDUBuffer getVDUBuffer() { + return buffer; + } + + public void propagateConsoleText(char[] rawText, int length) { + if (parent != null) { + parent.propagateConsoleText(rawText, length); + } + } + + public void onDraw() { + int fg, bg; + synchronized (buffer) { + boolean entireDirty = buffer.update[0] || fullRedraw; + boolean isWideCharacter = false; + + // walk through all lines in the buffer + for (int l = 0; l < buffer.height; l++) { + + // check if this line is dirty and needs to be repainted + // also check for entire-buffer dirty flags + if (!entireDirty && !buffer.update[l + 1]) continue; + + // reset dirty flag for this line + buffer.update[l + 1] = false; + + // walk through all characters in this line + for (int c = 0; c < buffer.width; c++) { + int addr = 0; + int currAttr = buffer.charAttributes[buffer.windowBase + l][c]; + + { + int fgcolor = defaultFg; + + // check if foreground color attribute is set + if ((currAttr & VDUBuffer.COLOR_FG) != 0) + fgcolor = ((currAttr & VDUBuffer.COLOR_FG) >> VDUBuffer.COLOR_FG_SHIFT) - 1; + + if (fgcolor < 8 && (currAttr & VDUBuffer.BOLD) != 0) + fg = color[fgcolor + 8]; + else + fg = color[fgcolor]; + } + + // check if background color attribute is set + if ((currAttr & VDUBuffer.COLOR_BG) != 0) + bg = color[((currAttr & VDUBuffer.COLOR_BG) >> VDUBuffer.COLOR_BG_SHIFT) - 1]; + else + bg = color[defaultBg]; + + // support character inversion by swapping background and foreground color + if ((currAttr & VDUBuffer.INVERT) != 0) { + int swapc = bg; + bg = fg; + fg = swapc; + } + + // set underlined attributes if requested + defaultPaint.setUnderlineText((currAttr & VDUBuffer.UNDERLINE) != 0); + + isWideCharacter = (currAttr & VDUBuffer.FULLWIDTH) != 0; + + if (isWideCharacter) + addr++; + else { + // determine the amount of continuous characters with the same settings and print them all at once + while (c + addr < buffer.width + && buffer.charAttributes[buffer.windowBase + l][c + addr] == currAttr) { + addr++; + } + } + + // Save the current clip region + canvas.save(Canvas.CLIP_SAVE_FLAG); + + // clear this dirty area with background color + defaultPaint.setColor(bg); + if (isWideCharacter) { + canvas.clipRect(c * charWidth, + l * charHeight, + (c + 2) * charWidth, + (l + 1) * charHeight); + } else { + canvas.clipRect(c * charWidth, + l * charHeight, + (c + addr) * charWidth, + (l + 1) * charHeight); + } + canvas.drawPaint(defaultPaint); + + // write the text string starting at 'c' for 'addr' number of characters + defaultPaint.setColor(fg); + if ((currAttr & VDUBuffer.INVISIBLE) == 0) + canvas.drawText(buffer.charArray[buffer.windowBase + l], c, + addr, c * charWidth, (l * charHeight) - charTop, + defaultPaint); + + // Restore the previous clip region + canvas.restore(); + + // advance to the next text block with different characteristics + c += addr - 1; + if (isWideCharacter) + c++; + } + } + + // reset entire-buffer flags + buffer.update[0] = false; + } + fullRedraw = false; + } + + public void redraw() { + if (parent != null) + parent.postInvalidate(); + } + + // We don't have a scroll bar. + public void updateScrollBar() { + } + + /** + * Resize terminal to fit [rows]x[cols] in screen of size [width]x[height] + * + * @param rows desired number of text rows + * @param cols desired numbor of text colums + * @param width width of screen in pixels + * @param height height of screen in pixels + */ + public synchronized void resizeComputed(int cols, int rows, int width, int height) { + float sizeDp = 8.0f; + float step = 8.0f; + float limit = 0.125f; + + int direction; + + while ((direction = fontSizeCompare(sizeDp, cols, rows, width, height)) < 0) + sizeDp += step; + + if (direction == 0) { + Log.d("fontsize", String.format("Found match at %f", sizeDp)); + return; + } + + step /= 2.0f; + sizeDp -= step; + + while ((direction = fontSizeCompare(sizeDp, cols, rows, width, height)) != 0 + && step >= limit) { + step /= 2.0f; + if (direction > 0) { + sizeDp -= step; + } else { + sizeDp += step; + } + } + + if (direction > 0) + sizeDp -= step; + + this.columns = cols; + this.rows = rows; + setFontSize(sizeDp); + forcedSize = true; + } + + private int fontSizeCompare(float sizeDp, int cols, int rows, int width, int height) { + // read new metrics to get exact pixel dimensions + defaultPaint.setTextSize((int) (sizeDp * this.displayDensity + 0.5f)); + FontMetrics fm = defaultPaint.getFontMetrics(); + + float[] widths = new float[1]; + defaultPaint.getTextWidths("X", widths); + int termWidth = (int) widths[0] * cols; + int termHeight = (int) Math.ceil(fm.descent - fm.top) * rows; + + Log.d("fontsize", String.format("font size %fdp resulted in %d x %d", sizeDp, termWidth, termHeight)); + + // Check to see if it fits in resolution specified. + if (termWidth > width || termHeight > height) + return 1; + + if (termWidth == width || termHeight == height) + return 0; + + return -1; + } + + /** + * @return whether underlying transport can forward ports + */ + public boolean canFowardPorts() { + return transport.canForwardPorts(); + } + + /** + * Adds the {@link PortForwardBean} to the list. + * @param portForward the port forward bean to add + * @return true on successful addition + */ + public boolean addPortForward(PortForwardBean portForward) { + return transport.addPortForward(portForward); + } + + /** + * Removes the {@link PortForwardBean} from the list. + * @param portForward the port forward bean to remove + * @return true on successful removal + */ + public boolean removePortForward(PortForwardBean portForward) { + return transport.removePortForward(portForward); + } + + /** + * @return the list of port forwards + */ + public List<PortForwardBean> getPortForwards() { + return transport.getPortForwards(); + } + + /** + * Enables a port forward member. After calling this method, the port forward should + * be operational. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward setup + */ + public boolean enablePortForward(PortForwardBean portForward) { + if (!transport.isConnected()) { + Log.i(TAG, "Attempt to enable port forward while not connected"); + return false; + } + + return transport.enablePortForward(portForward); + } + + /** + * Disables a port forward member. After calling this method, the port forward should + * be non-functioning. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward tear-down + */ + public boolean disablePortForward(PortForwardBean portForward) { + if (!transport.isConnected()) { + Log.i(TAG, "Attempt to disable port forward while not connected"); + return false; + } + + return transport.disablePortForward(portForward); + } + + /** + * @return whether the TerminalBridge should close + */ + public boolean isAwaitingClose() { + return awaitingClose; + } + + /** + * @return whether this connection had started and subsequently disconnected + */ + public boolean isDisconnected() { + return disconnected; + } + + /* (non-Javadoc) + * @see de.mud.terminal.VDUDisplay#setColor(byte, byte, byte, byte) + */ + public void setColor(int index, int red, int green, int blue) { + // Don't allow the system colors to be overwritten for now. May violate specs. + if (index < color.length && index >= 16) + color[index] = 0xff000000 | red << 16 | green << 8 | blue; + } + + public final void resetColors() { + int[] defaults = manager.hostdb.getDefaultColorsForScheme(HostDatabase.DEFAULT_COLOR_SCHEME); + defaultFg = defaults[0]; + defaultBg = defaults[1]; + + color = manager.hostdb.getColorsForScheme(HostDatabase.DEFAULT_COLOR_SCHEME); + } + + private static Pattern urlPattern = null; + + /** + * @return + */ + public List<String> scanForURLs() { + List<String> urls = new LinkedList<String>(); + + if (urlPattern == null) { + // based on http://www.ietf.org/rfc/rfc2396.txt + String scheme = "[A-Za-z][-+.0-9A-Za-z]*"; + String unreserved = "[-._~0-9A-Za-z]"; + String pctEncoded = "%[0-9A-Fa-f]{2}"; + String subDelims = "[!$&'()*+,;:=]"; + String userinfo = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + "|:)*"; + String h16 = "[0-9A-Fa-f]{1,4}"; + String decOctet = "(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"; + String ipv4address = decOctet + "\\." + decOctet + "\\." + decOctet + "\\." + decOctet; + String ls32 = "(?:" + h16 + ":" + h16 + "|" + ipv4address + ")"; + String ipv6address = "(?:(?:" + h16 + "){6}" + ls32 + ")"; + String ipvfuture = "v[0-9A-Fa-f]+.(?:" + unreserved + "|" + subDelims + "|:)+"; + String ipLiteral = "\\[(?:" + ipv6address + "|" + ipvfuture + ")\\]"; + String regName = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + ")*"; + String host = "(?:" + ipLiteral + "|" + ipv4address + "|" + regName + ")"; + String port = "[0-9]*"; + String authority = "(?:" + userinfo + "@)?" + host + "(?::" + port + ")?"; + String pchar = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + "|@)"; + String segment = pchar + "*"; + String pathAbempty = "(?:/" + segment + ")*"; + String segmentNz = pchar + "+"; + String pathAbsolute = "/(?:" + segmentNz + "(?:/" + segment + ")*)?"; + String pathRootless = segmentNz + "(?:/" + segment + ")*"; + String hierPart = "(?://" + authority + pathAbempty + "|" + pathAbsolute + "|" + pathRootless + ")"; + String query = "(?:" + pchar + "|/|\\?)*"; + String fragment = "(?:" + pchar + "|/|\\?)*"; + String uriRegex = scheme + ":" + hierPart + "(?:" + query + ")?(?:#" + fragment + ")?"; + urlPattern = Pattern.compile(uriRegex); + } + + char[] visibleBuffer = new char[buffer.height * buffer.width]; + for (int l = 0; l < buffer.height; l++) + System.arraycopy(buffer.charArray[buffer.windowBase + l], 0, + visibleBuffer, l * buffer.width, buffer.width); + + Matcher urlMatcher = urlPattern.matcher(new String(visibleBuffer)); + while (urlMatcher.find()) + urls.add(urlMatcher.group()); + + return urls; + } + + /** + * @return + */ + public boolean isUsingNetwork() { + return transport.usesNetwork(); + } + + /** + * @return + */ + public TerminalKeyListener getKeyHandler() { + return keyListener; + } + + /** + * + */ + public void resetScrollPosition() { + // if we're in scrollback, scroll to bottom of window on input + if (buffer.windowBase != buffer.screenBase) + buffer.setWindowBase(buffer.screenBase); + } + + /** + * Convenience function to increase the font size by a given step. + */ + public void increaseFontSize() { + setFontSize(fontSizeDp + FONT_SIZE_STEP); + } + + /** + * Convenience function to decrease the font size by a given step. + */ + public void decreaseFontSize() { + setFontSize(fontSizeDp - FONT_SIZE_STEP); + } +} diff --git a/app/src/main/java/org/connectbot/service/TerminalKeyListener.java b/app/src/main/java/org/connectbot/service/TerminalKeyListener.java new file mode 100644 index 0000000..2a911a8 --- /dev/null +++ b/app/src/main/java/org/connectbot/service/TerminalKeyListener.java @@ -0,0 +1,616 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.connectbot.service; + +import java.io.IOException; + +import org.connectbot.bean.SelectionArea; +import org.connectbot.util.PreferenceConstants; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.res.Configuration; +import android.preference.PreferenceManager; +import android.text.ClipboardManager; +import android.util.Log; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnKeyListener; +import de.mud.terminal.VDUBuffer; +import de.mud.terminal.vt320; + +/** + * @author kenny + * + */ +@SuppressWarnings("deprecation") // for ClipboardManager +public class TerminalKeyListener implements OnKeyListener, OnSharedPreferenceChangeListener { + private static final String TAG = "ConnectBot.OnKeyListener"; + + // Constants for our private tracking of modifier state + public final static int OUR_CTRL_ON = 0x01; + public final static int OUR_CTRL_LOCK = 0x02; + public final static int OUR_ALT_ON = 0x04; + public final static int OUR_ALT_LOCK = 0x08; + public final static int OUR_SHIFT_ON = 0x10; + public final static int OUR_SHIFT_LOCK = 0x20; + private final static int OUR_SLASH = 0x40; + private final static int OUR_TAB = 0x80; + + // All the transient key codes + private final static int OUR_TRANSIENT = OUR_CTRL_ON | OUR_ALT_ON + | OUR_SHIFT_ON | OUR_SLASH | OUR_TAB; + + // The bit mask of momentary and lock states for each + private final static int OUR_CTRL_MASK = OUR_CTRL_ON | OUR_CTRL_LOCK; + private final static int OUR_ALT_MASK = OUR_ALT_ON | OUR_ALT_LOCK; + private final static int OUR_SHIFT_MASK = OUR_SHIFT_ON | OUR_SHIFT_LOCK; + + // backport constants from api level 11 + private final static int KEYCODE_ESCAPE = 111; + private final static int KEYCODE_CTRL_LEFT = 113; + private final static int KEYCODE_CTRL_RIGHT = 114; + private final static int KEYCODE_INSERT = 124; + private final static int KEYCODE_FORWARD_DEL = 112; + private final static int KEYCODE_MOVE_HOME = 122; + private final static int KEYCODE_MOVE_END = 123; + private final static int KEYCODE_PAGE_DOWN = 93; + private final static int KEYCODE_PAGE_UP = 92; + private final static int HC_META_CTRL_ON = 0x1000; + private final static int HC_META_CTRL_LEFT_ON = 0x2000; + private final static int HC_META_CTRL_RIGHT_ON = 0x4000; + private final static int HC_META_CTRL_MASK = HC_META_CTRL_ON | HC_META_CTRL_RIGHT_ON + | HC_META_CTRL_LEFT_ON; + private final static int HC_META_ALT_MASK = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON + | KeyEvent.META_ALT_RIGHT_ON; + + private final TerminalManager manager; + private final TerminalBridge bridge; + private final VDUBuffer buffer; + + private String keymode = null; + private final boolean deviceHasHardKeyboard; + private boolean shiftedNumbersAreFKeysOnHardKeyboard; + private boolean controlNumbersAreFKeysOnSoftKeyboard; + private boolean volumeKeysChangeFontSize; + private int stickyMetas; + + private int ourMetaState = 0; + + private int mDeadKey = 0; + + // TODO add support for the new API. + private ClipboardManager clipboard = null; + + private boolean selectingForCopy = false; + private final SelectionArea selectionArea; + + private String encoding; + + private final SharedPreferences prefs; + + public TerminalKeyListener(TerminalManager manager, + TerminalBridge bridge, + VDUBuffer buffer, + String encoding) { + this.manager = manager; + this.bridge = bridge; + this.buffer = buffer; + this.encoding = encoding; + + selectionArea = new SelectionArea(); + + prefs = PreferenceManager.getDefaultSharedPreferences(manager); + prefs.registerOnSharedPreferenceChangeListener(this); + + deviceHasHardKeyboard = (manager.res.getConfiguration().keyboard + == Configuration.KEYBOARD_QWERTY); + + updatePrefs(); + } + + /** + * Handle onKey() events coming down from a {@link org.connectbot.TerminalView} above us. + * Modify the keys to make more sense to a host then pass it to the transport. + */ + public boolean onKey(View v, int keyCode, KeyEvent event) { + try { + // skip keys if we aren't connected yet or have been disconnected + if (bridge.isDisconnected() || bridge.transport == null) + return false; + + final boolean interpretAsHardKeyboard = deviceHasHardKeyboard && + !manager.hardKeyboardHidden; + final boolean rightModifiersAreSlashAndTab = interpretAsHardKeyboard && + PreferenceConstants.KEYMODE_RIGHT.equals(keymode); + final boolean leftModifiersAreSlashAndTab = interpretAsHardKeyboard && + PreferenceConstants.KEYMODE_LEFT.equals(keymode); + final boolean shiftedNumbersAreFKeys = shiftedNumbersAreFKeysOnHardKeyboard && + interpretAsHardKeyboard; + final boolean controlNumbersAreFKeys = controlNumbersAreFKeysOnSoftKeyboard && + !interpretAsHardKeyboard; + + // Ignore all key-up events except for the special keys + if (event.getAction() == KeyEvent.ACTION_UP) { + if (rightModifiersAreSlashAndTab) { + if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT + && (ourMetaState & OUR_SLASH) != 0) { + ourMetaState &= ~OUR_TRANSIENT; + bridge.transport.write('/'); + return true; + } else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT + && (ourMetaState & OUR_TAB) != 0) { + ourMetaState &= ~OUR_TRANSIENT; + bridge.transport.write(0x09); + return true; + } + } else if (leftModifiersAreSlashAndTab) { + if (keyCode == KeyEvent.KEYCODE_ALT_LEFT + && (ourMetaState & OUR_SLASH) != 0) { + ourMetaState &= ~OUR_TRANSIENT; + bridge.transport.write('/'); + return true; + } else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + && (ourMetaState & OUR_TAB) != 0) { + ourMetaState &= ~OUR_TRANSIENT; + bridge.transport.write(0x09); + return true; + } + } + + return false; + } + + //Log.i("CBKeyDebug", KeyEventUtil.describeKeyEvent(keyCode, event)); + + if (volumeKeysChangeFontSize) { + if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + bridge.increaseFontSize(); + return true; + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + bridge.decreaseFontSize(); + return true; + } + } + + bridge.resetScrollPosition(); + + // Handle potentially multi-character IME input. + if (keyCode == KeyEvent.KEYCODE_UNKNOWN && + event.getAction() == KeyEvent.ACTION_MULTIPLE) { + byte[] input = event.getCharacters().getBytes(encoding); + bridge.transport.write(input); + return true; + } + + /// Handle alt and shift keys if they aren't repeating + if (event.getRepeatCount() == 0) { + if (rightModifiersAreSlashAndTab) { + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_RIGHT: + ourMetaState |= OUR_SLASH; + return true; + case KeyEvent.KEYCODE_SHIFT_RIGHT: + ourMetaState |= OUR_TAB; + return true; + case KeyEvent.KEYCODE_SHIFT_LEFT: + metaPress(OUR_SHIFT_ON); + return true; + case KeyEvent.KEYCODE_ALT_LEFT: + metaPress(OUR_ALT_ON); + return true; + } + } else if (leftModifiersAreSlashAndTab) { + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_LEFT: + ourMetaState |= OUR_SLASH; + return true; + case KeyEvent.KEYCODE_SHIFT_LEFT: + ourMetaState |= OUR_TAB; + return true; + case KeyEvent.KEYCODE_SHIFT_RIGHT: + metaPress(OUR_SHIFT_ON); + return true; + case KeyEvent.KEYCODE_ALT_RIGHT: + metaPress(OUR_ALT_ON); + return true; + } + } else { + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + metaPress(OUR_ALT_ON); + return true; + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + metaPress(OUR_SHIFT_ON); + return true; + } + } + if (keyCode == KEYCODE_CTRL_LEFT || keyCode == KEYCODE_CTRL_RIGHT) { + metaPress(OUR_CTRL_ON); + return true; + } + } + + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + if (selectingForCopy) { + if (selectionArea.isSelectingOrigin()) + selectionArea.finishSelectingOrigin(); + else { + if (clipboard != null) { + // copy selected area to clipboard + String copiedText = selectionArea.copyFrom(buffer); + clipboard.setText(copiedText); + // XXX STOPSHIP +// manager.notifyUser(manager.getString( +// R.string.console_copy_done, +// copiedText.length())); + selectingForCopy = false; + selectionArea.reset(); + } + } + } else { + if ((ourMetaState & OUR_CTRL_ON) != 0) { + sendEscape(); + ourMetaState &= ~OUR_CTRL_ON; + } else + metaPress(OUR_CTRL_ON, true); + } + bridge.redraw(); + return true; + } + + int derivedMetaState = event.getMetaState(); + if ((ourMetaState & OUR_SHIFT_MASK) != 0) + derivedMetaState |= KeyEvent.META_SHIFT_ON; + if ((ourMetaState & OUR_ALT_MASK) != 0) + derivedMetaState |= KeyEvent.META_ALT_ON; + if ((ourMetaState & OUR_CTRL_MASK) != 0) + derivedMetaState |= HC_META_CTRL_ON; + + if ((ourMetaState & OUR_TRANSIENT) != 0) { + ourMetaState &= ~OUR_TRANSIENT; + bridge.redraw(); + } + + // Test for modified numbers becoming function keys + if (shiftedNumbersAreFKeys && (derivedMetaState & KeyEvent.META_SHIFT_ON) != 0) { + if (sendFunctionKey(keyCode)) + return true; + } + if (controlNumbersAreFKeys && (derivedMetaState & HC_META_CTRL_ON) != 0) { + if (sendFunctionKey(keyCode)) + return true; + } + + // Ask the system to use the keymap to give us the unicode character for this key, + // with our derived modifier state applied. + int uchar = event.getUnicodeChar(derivedMetaState & ~HC_META_CTRL_MASK); + int ucharWithoutAlt = event.getUnicodeChar( + derivedMetaState & ~(HC_META_ALT_MASK | HC_META_CTRL_MASK)); + if (uchar == 0) { + // Keymap doesn't know the key with alt on it, so just go with the unmodified version + uchar = ucharWithoutAlt; + } else if (uchar != ucharWithoutAlt) { + // The alt key was used to modify the character returned; therefore, drop the alt + // modifier from the state so we don't end up sending alt+key. + derivedMetaState &= ~HC_META_ALT_MASK; + } + + // Remove shift from the modifier state as it has already been used by getUnicodeChar. + derivedMetaState &= ~KeyEvent.META_SHIFT_ON; + + if ((uchar & KeyCharacterMap.COMBINING_ACCENT) != 0) { + mDeadKey = uchar & KeyCharacterMap.COMBINING_ACCENT_MASK; + return true; + } + + if (mDeadKey != 0) { + uchar = KeyCharacterMap.getDeadChar(mDeadKey, keyCode); + mDeadKey = 0; + } + + // If we have a defined non-control character + if (uchar >= 0x20) { + if ((derivedMetaState & HC_META_CTRL_ON) != 0) + uchar = keyAsControl(uchar); + if ((derivedMetaState & KeyEvent.META_ALT_ON) != 0) + sendEscape(); + if (uchar < 0x80) + bridge.transport.write(uchar); + else + // TODO write encoding routine that doesn't allocate each time + bridge.transport.write(new String(Character.toChars(uchar)) + .getBytes(encoding)); + return true; + } + + // look for special chars + switch(keyCode) { + case KEYCODE_ESCAPE: + sendEscape(); + return true; + case KeyEvent.KEYCODE_TAB: + bridge.transport.write(0x09); + return true; + case KeyEvent.KEYCODE_CAMERA: + + // check to see which shortcut the camera button triggers + String camera = manager.prefs.getString( + PreferenceConstants.CAMERA, + PreferenceConstants.CAMERA_CTRLA_SPACE); + if (PreferenceConstants.CAMERA_CTRLA_SPACE.equals(camera)) { + bridge.transport.write(0x01); + bridge.transport.write(' '); + } else if (PreferenceConstants.CAMERA_CTRLA.equals(camera)) { + bridge.transport.write(0x01); + } else if (PreferenceConstants.CAMERA_ESC.equals(camera)) { + ((vt320) buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); + } else if (PreferenceConstants.CAMERA_ESC_A.equals(camera)) { + ((vt320) buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); + bridge.transport.write('a'); + } + + break; + + case KeyEvent.KEYCODE_DEL: + ((vt320) buffer).keyPressed(vt320.KEY_BACK_SPACE, ' ', + getStateForBuffer()); + return true; + case KeyEvent.KEYCODE_ENTER: + ((vt320) buffer).keyTyped(vt320.KEY_ENTER, ' ', 0); + return true; + + case KeyEvent.KEYCODE_DPAD_LEFT: + if (selectingForCopy) { + selectionArea.decrementColumn(); + bridge.redraw(); + } else { + ((vt320) buffer).keyPressed(vt320.KEY_LEFT, ' ', + getStateForBuffer()); + bridge.tryKeyVibrate(); + } + return true; + + case KeyEvent.KEYCODE_DPAD_UP: + if (selectingForCopy) { + selectionArea.decrementRow(); + bridge.redraw(); + } else { + ((vt320) buffer).keyPressed(vt320.KEY_UP, ' ', + getStateForBuffer()); + bridge.tryKeyVibrate(); + } + return true; + + case KeyEvent.KEYCODE_DPAD_DOWN: + if (selectingForCopy) { + selectionArea.incrementRow(); + bridge.redraw(); + } else { + ((vt320) buffer).keyPressed(vt320.KEY_DOWN, ' ', + getStateForBuffer()); + bridge.tryKeyVibrate(); + } + return true; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (selectingForCopy) { + selectionArea.incrementColumn(); + bridge.redraw(); + } else { + ((vt320) buffer).keyPressed(vt320.KEY_RIGHT, ' ', + getStateForBuffer()); + bridge.tryKeyVibrate(); + } + return true; + + case KEYCODE_INSERT: + ((vt320) buffer).keyPressed(vt320.KEY_INSERT, ' ', + getStateForBuffer()); + return true; + case KEYCODE_FORWARD_DEL: + ((vt320) buffer).keyPressed(vt320.KEY_DELETE, ' ', + getStateForBuffer()); + return true; + case KEYCODE_MOVE_HOME: + ((vt320) buffer).keyPressed(vt320.KEY_HOME, ' ', + getStateForBuffer()); + return true; + case KEYCODE_MOVE_END: + ((vt320) buffer).keyPressed(vt320.KEY_END, ' ', + getStateForBuffer()); + return true; + case KEYCODE_PAGE_UP: + ((vt320) buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', + getStateForBuffer()); + return true; + case KEYCODE_PAGE_DOWN: + ((vt320) buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', + getStateForBuffer()); + return true; + } + + } catch (IOException e) { + Log.e(TAG, "Problem while trying to handle an onKey() event", e); + try { + bridge.transport.flush(); + } catch (IOException ioe) { + Log.d(TAG, "Our transport was closed, dispatching disconnect event"); + bridge.dispatchDisconnect(false); + } + } catch (NullPointerException npe) { + Log.d(TAG, "Input before connection established ignored."); + return true; + } + + return false; + } + + public int keyAsControl(int key) { + // Support CTRL-a through CTRL-z + if (key >= 0x61 && key <= 0x7A) + key -= 0x60; + // Support CTRL-A through CTRL-_ + else if (key >= 0x41 && key <= 0x5F) + key -= 0x40; + // CTRL-space sends NULL + else if (key == 0x20) + key = 0x00; + // CTRL-? sends DEL + else if (key == 0x3F) + key = 0x7F; + return key; + } + + public void sendEscape() { + ((vt320) buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); + } + + /** + * @param key + * @return successful + */ + private boolean sendFunctionKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_1: + ((vt320) buffer).keyPressed(vt320.KEY_F1, ' ', 0); + return true; + case KeyEvent.KEYCODE_2: + ((vt320) buffer).keyPressed(vt320.KEY_F2, ' ', 0); + return true; + case KeyEvent.KEYCODE_3: + ((vt320) buffer).keyPressed(vt320.KEY_F3, ' ', 0); + return true; + case KeyEvent.KEYCODE_4: + ((vt320) buffer).keyPressed(vt320.KEY_F4, ' ', 0); + return true; + case KeyEvent.KEYCODE_5: + ((vt320) buffer).keyPressed(vt320.KEY_F5, ' ', 0); + return true; + case KeyEvent.KEYCODE_6: + ((vt320) buffer).keyPressed(vt320.KEY_F6, ' ', 0); + return true; + case KeyEvent.KEYCODE_7: + ((vt320) buffer).keyPressed(vt320.KEY_F7, ' ', 0); + return true; + case KeyEvent.KEYCODE_8: + ((vt320) buffer).keyPressed(vt320.KEY_F8, ' ', 0); + return true; + case KeyEvent.KEYCODE_9: + ((vt320) buffer).keyPressed(vt320.KEY_F9, ' ', 0); + return true; + case KeyEvent.KEYCODE_0: + ((vt320) buffer).keyPressed(vt320.KEY_F10, ' ', 0); + return true; + default: + return false; + } + } + + /** + * Handle meta key presses where the key can be locked on. + * <p> + * 1st press: next key to have meta state<br /> + * 2nd press: meta state is locked on<br /> + * 3rd press: disable meta state + * + * @param code + */ + public void metaPress(int code, boolean forceSticky) { + if ((ourMetaState & (code << 1)) != 0) { + ourMetaState &= ~(code << 1); + } else if ((ourMetaState & code) != 0) { + ourMetaState &= ~code; + ourMetaState |= code << 1; + } else if (forceSticky || (stickyMetas & code) != 0) { + ourMetaState |= code; + } else { + // skip redraw + return; + } + bridge.redraw(); + } + + public void metaPress(int code) { + metaPress(code, false); + } + + public void setTerminalKeyMode(String keymode) { + this.keymode = keymode; + } + + private int getStateForBuffer() { + int bufferState = 0; + + if ((ourMetaState & OUR_CTRL_MASK) != 0) + bufferState |= vt320.KEY_CONTROL; + if ((ourMetaState & OUR_SHIFT_MASK) != 0) + bufferState |= vt320.KEY_SHIFT; + if ((ourMetaState & OUR_ALT_MASK) != 0) + bufferState |= vt320.KEY_ALT; + + return bufferState; + } + + public int getMetaState() { + return ourMetaState; + } + + public int getDeadKey() { + return mDeadKey; + } + + public void setClipboardManager(ClipboardManager clipboard) { + this.clipboard = clipboard; + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (PreferenceConstants.KEYMODE.equals(key) || + PreferenceConstants.SHIFT_FKEYS.equals(key) || + PreferenceConstants.CTRL_FKEYS.equals(key) || + PreferenceConstants.VOLUME_FONT.equals(key) || + PreferenceConstants.STICKY_MODIFIERS.equals(key)) { + updatePrefs(); + } + } + + private void updatePrefs() { + keymode = prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_NONE); + shiftedNumbersAreFKeysOnHardKeyboard = + prefs.getBoolean(PreferenceConstants.SHIFT_FKEYS, false); + controlNumbersAreFKeysOnSoftKeyboard = + prefs.getBoolean(PreferenceConstants.CTRL_FKEYS, false); + volumeKeysChangeFontSize = prefs.getBoolean(PreferenceConstants.VOLUME_FONT, true); + String stickyModifiers = prefs.getString(PreferenceConstants.STICKY_MODIFIERS, + PreferenceConstants.NO); + if (PreferenceConstants.ALT.equals(stickyModifiers)) { + stickyMetas = OUR_ALT_ON; + } else if (PreferenceConstants.YES.equals(stickyModifiers)) { + stickyMetas = OUR_SHIFT_ON | OUR_CTRL_ON | OUR_ALT_ON; + } else { + stickyMetas = 0; + } + } + + public void setCharset(String encoding) { + this.encoding = encoding; + } +} diff --git a/app/src/main/java/org/connectbot/service/TerminalManager.java b/app/src/main/java/org/connectbot/service/TerminalManager.java new file mode 100644 index 0000000..928c5f5 --- /dev/null +++ b/app/src/main/java/org/connectbot/service/TerminalManager.java @@ -0,0 +1,714 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.service; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Timer; +import java.util.TimerTask; + +import org.connectbot.R; +import org.connectbot.bean.HostBean; +import org.connectbot.bean.PubkeyBean; +import org.connectbot.transport.TransportFactory; +import org.connectbot.util.HostDatabase; +import org.connectbot.util.PreferenceConstants; +import org.connectbot.util.PubkeyDatabase; +import org.connectbot.util.PubkeyUtils; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.res.AssetFileDescriptor; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.net.Uri; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Vibrator; +import android.preference.PreferenceManager; +import android.util.Log; + +/** + * Manager for SSH connections that runs as a service. This service holds a list + * of currently connected SSH bridges that are ready for connection up to a GUI + * if needed. + * + * @author jsharkey + */ +public class TerminalManager extends Service implements BridgeDisconnectedListener, OnSharedPreferenceChangeListener { + public final static String TAG = "ConnectBot.TerminalManager"; + + public List<TerminalBridge> bridges = new LinkedList<TerminalBridge>(); + public Map<HostBean, WeakReference<TerminalBridge>> mHostBridgeMap = + new HashMap<HostBean, WeakReference<TerminalBridge>>(); + public Map<String, WeakReference<TerminalBridge>> mNicknameBridgeMap = + new HashMap<String, WeakReference<TerminalBridge>>(); + + public TerminalBridge defaultBridge = null; + + public List<HostBean> disconnected = new LinkedList<HostBean>(); + + public Handler disconnectHandler = null; + + public Map<String, KeyHolder> loadedKeypairs = new HashMap<String, KeyHolder>(); + + public Resources res; + + public HostDatabase hostdb; + public PubkeyDatabase pubkeydb; + + protected SharedPreferences prefs; + + final private IBinder binder = new TerminalBinder(); + + private ConnectivityReceiver connectivityManager; + + private MediaPlayer mediaPlayer; + + private Timer pubkeyTimer; + + private Timer idleTimer; + private final long IDLE_TIMEOUT = 300000; // 5 minutes + + private Vibrator vibrator; + private volatile boolean wantKeyVibration; + public static final long VIBRATE_DURATION = 30; + + private boolean wantBellVibration; + + private boolean resizeAllowed = true; + + private boolean savingKeys; + + protected List<WeakReference<TerminalBridge>> mPendingReconnect + = new LinkedList<WeakReference<TerminalBridge>>(); + + public boolean hardKeyboardHidden; + + @Override + public void onCreate() { + Log.i(TAG, "Starting service"); + + prefs = PreferenceManager.getDefaultSharedPreferences(this); + prefs.registerOnSharedPreferenceChangeListener(this); + + res = getResources(); + + pubkeyTimer = new Timer("pubkeyTimer", true); + + hostdb = new HostDatabase(this); + pubkeydb = new PubkeyDatabase(this); + + // load all marked pubkeys into memory + updateSavingKeys(); + List<PubkeyBean> pubkeys = pubkeydb.getAllStartPubkeys(); + + for (PubkeyBean pubkey : pubkeys) { + try { + PrivateKey privKey = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), pubkey.getType()); + PublicKey pubKey = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType()); + KeyPair pair = new KeyPair(pubKey, privKey); + + addKey(pubkey, pair); + } catch (Exception e) { + Log.d(TAG, String.format("Problem adding key '%s' to in-memory cache", pubkey.getNickname()), e); + } + } + + vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + wantKeyVibration = prefs.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); + + wantBellVibration = prefs.getBoolean(PreferenceConstants.BELL_VIBRATE, true); + enableMediaPlayer(); + + hardKeyboardHidden = (res.getConfiguration().hardKeyboardHidden == + Configuration.HARDKEYBOARDHIDDEN_YES); + + final boolean lockingWifi = prefs.getBoolean(PreferenceConstants.WIFI_LOCK, true); + + connectivityManager = new ConnectivityReceiver(this, lockingWifi); + + } + + private void updateSavingKeys() { + savingKeys = prefs.getBoolean(PreferenceConstants.MEMKEYS, true); + } + + @Override + public void onDestroy() { + Log.i(TAG, "Destroying service"); + + disconnectAll(true); + + if (hostdb != null) { + hostdb.close(); + hostdb = null; + } + + if (pubkeydb != null) { + pubkeydb.close(); + pubkeydb = null; + } + + synchronized (this) { + if (idleTimer != null) + idleTimer.cancel(); + if (pubkeyTimer != null) + pubkeyTimer.cancel(); + } + + connectivityManager.cleanup(); + + ConnectionNotifier.getInstance().hideRunningNotification(this); + + disableMediaPlayer(); + } + + /** + * Disconnect all currently connected bridges. + */ + private void disconnectAll(final boolean immediate) { + TerminalBridge[] tmpBridges = null; + + synchronized (bridges) { + if (bridges.size() > 0) { + tmpBridges = bridges.toArray(new TerminalBridge[bridges.size()]); + } + } + + if (tmpBridges != null) { + // disconnect and dispose of any existing bridges + for (int i = 0; i < tmpBridges.length; i++) + tmpBridges[i].dispatchDisconnect(immediate); + } + } + + /** + * Open a new SSH session using the given parameters. + */ + private TerminalBridge openConnection(HostBean host) throws IllegalArgumentException, IOException { + // throw exception if terminal already open + if (getConnectedBridge(host) != null) { + throw new IllegalArgumentException("Connection already open for that nickname"); + } + + TerminalBridge bridge = new TerminalBridge(this, host); + bridge.setOnDisconnectedListener(this); + bridge.startConnection(); + + synchronized (bridges) { + bridges.add(bridge); + WeakReference<TerminalBridge> wr = new WeakReference<TerminalBridge>(bridge); + mHostBridgeMap.put(bridge.host, wr); + mNicknameBridgeMap.put(bridge.host.getNickname(), wr); + } + + synchronized (disconnected) { + disconnected.remove(bridge.host); + } + + if (bridge.isUsingNetwork()) { + connectivityManager.incRef(); + } + + if (prefs.getBoolean(PreferenceConstants.CONNECTION_PERSIST, true)) { + ConnectionNotifier.getInstance().showRunningNotification(this); + } + + // also update database with new connected time + touchHost(host); + + return bridge; + } + + public String getEmulation() { + return prefs.getString(PreferenceConstants.EMULATION, "screen"); + } + + public int getScrollback() { + int scrollback = 140; + try { + scrollback = Integer.parseInt(prefs.getString(PreferenceConstants.SCROLLBACK, "140")); + } catch (Exception e) { + } + return scrollback; + } + + /** + * Open a new connection by reading parameters from the given URI. Follows + * format specified by an individual transport. + */ + public TerminalBridge openConnection(Uri uri) throws Exception { + HostBean host = TransportFactory.findHost(hostdb, uri); + + if (host == null) + host = TransportFactory.getTransport(uri.getScheme()).createHost(uri); + + return openConnection(host); + } + + /** + * Update the last-connected value for the given nickname by passing through + * to {@link HostDatabase}. + */ + private void touchHost(HostBean host) { + hostdb.touchHost(host); + } + + /** + * Find a connected {@link TerminalBridge} with the given HostBean. + * + * @param host the HostBean to search for + * @return TerminalBridge that uses the HostBean + */ + public TerminalBridge getConnectedBridge(HostBean host) { + WeakReference<TerminalBridge> wr = mHostBridgeMap.get(host); + if (wr != null) { + return wr.get(); + } else { + return null; + } + } + + /** + * Find a connected {@link TerminalBridge} using its nickname. + * + * @param nickname + * @return TerminalBridge that matches nickname + */ + public TerminalBridge getConnectedBridge(final String nickname) { + if (nickname == null) { + return null; + } + WeakReference<TerminalBridge> wr = mNicknameBridgeMap.get(nickname); + if (wr != null) { + return wr.get(); + } else { + return null; + } + } + + /** + * Called by child bridge when somehow it's been disconnected. + */ + public void onDisconnected(TerminalBridge bridge) { + boolean shouldHideRunningNotification = false; + + synchronized (bridges) { + // remove this bridge from our list + bridges.remove(bridge); + + mHostBridgeMap.remove(bridge.host); + mNicknameBridgeMap.remove(bridge.host.getNickname()); + + if (bridge.isUsingNetwork()) { + connectivityManager.decRef(); + } + + if (bridges.size() == 0 && + mPendingReconnect.size() == 0) { + shouldHideRunningNotification = true; + } + } + + synchronized (disconnected) { + disconnected.add(bridge.host); + } + + if (shouldHideRunningNotification) { + ConnectionNotifier.getInstance().hideRunningNotification(this); + } + + // pass notification back up to gui + if (disconnectHandler != null) + Message.obtain(disconnectHandler, -1, bridge).sendToTarget(); + } + + public boolean isKeyLoaded(String nickname) { + return loadedKeypairs.containsKey(nickname); + } + + public void addKey(PubkeyBean pubkey, KeyPair pair) { + addKey(pubkey, pair, false); + } + + public void addKey(PubkeyBean pubkey, KeyPair pair, boolean force) { + if (!savingKeys && !force) + return; + + removeKey(pubkey.getNickname()); + + byte[] sshPubKey = PubkeyUtils.extractOpenSSHPublic(pair); + + KeyHolder keyHolder = new KeyHolder(); + keyHolder.bean = pubkey; + keyHolder.pair = pair; + keyHolder.openSSHPubkey = sshPubKey; + + loadedKeypairs.put(pubkey.getNickname(), keyHolder); + + if (pubkey.getLifetime() > 0) { + final String nickname = pubkey.getNickname(); + pubkeyTimer.schedule(new TimerTask() { + @Override + public void run() { + Log.d(TAG, "Unloading from memory key: " + nickname); + removeKey(nickname); + } + }, pubkey.getLifetime() * 1000); + } + + Log.d(TAG, String.format("Added key '%s' to in-memory cache", pubkey.getNickname())); + } + + public boolean removeKey(String nickname) { + Log.d(TAG, String.format("Removed key '%s' to in-memory cache", nickname)); + return loadedKeypairs.remove(nickname) != null; + } + + public boolean removeKey(byte[] publicKey) { + String nickname = null; + for (Entry<String, KeyHolder> entry : loadedKeypairs.entrySet()) { + if (Arrays.equals(entry.getValue().openSSHPubkey, publicKey)) { + nickname = entry.getKey(); + break; + } + } + + if (nickname != null) { + Log.d(TAG, String.format("Removed key '%s' to in-memory cache", nickname)); + return removeKey(nickname); + } else + return false; + } + + public KeyPair getKey(String nickname) { + if (loadedKeypairs.containsKey(nickname)) { + KeyHolder keyHolder = loadedKeypairs.get(nickname); + return keyHolder.pair; + } else + return null; + } + + public KeyPair getKey(byte[] publicKey) { + for (KeyHolder keyHolder : loadedKeypairs.values()) { + if (Arrays.equals(keyHolder.openSSHPubkey, publicKey)) + return keyHolder.pair; + } + return null; + } + + public String getKeyNickname(byte[] publicKey) { + for (Entry<String, KeyHolder> entry : loadedKeypairs.entrySet()) { + if (Arrays.equals(entry.getValue().openSSHPubkey, publicKey)) + return entry.getKey(); + } + return null; + } + + private void stopWithDelay() { + // TODO add in a way to check whether keys loaded are encrypted and only + // set timer when we have an encrypted key loaded + + if (loadedKeypairs.size() > 0) { + synchronized (this) { + if (idleTimer == null) + idleTimer = new Timer("idleTimer", true); + + idleTimer.schedule(new IdleTask(), IDLE_TIMEOUT); + } + } else { + Log.d(TAG, "Stopping service immediately"); + stopSelf(); + } + } + + protected void stopNow() { + if (bridges.size() == 0) { + stopSelf(); + } + } + + private synchronized void stopIdleTimer() { + if (idleTimer != null) { + idleTimer.cancel(); + idleTimer = null; + } + } + + public class TerminalBinder extends Binder { + public TerminalManager getService() { + return TerminalManager.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + Log.i(TAG, "Someone bound to TerminalManager"); + + setResizeAllowed(true); + + stopIdleTimer(); + + // Make sure we stay running to maintain the bridges + startService(new Intent(this, TerminalManager.class)); + + return binder; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + /* + * We want this service to continue running until it is explicitly + * stopped, so return sticky. + */ + return START_STICKY; + } + + @Override + public void onRebind(Intent intent) { + super.onRebind(intent); + + setResizeAllowed(true); + + Log.i(TAG, "Someone rebound to TerminalManager"); + + stopIdleTimer(); + } + + @Override + public boolean onUnbind(Intent intent) { + Log.i(TAG, "Someone unbound from TerminalManager"); + + setResizeAllowed(true); + + if (bridges.size() == 0) { + stopWithDelay(); + } + + return true; + } + + private class IdleTask extends TimerTask { + /* (non-Javadoc) + * @see java.util.TimerTask#run() + */ + @Override + public void run() { + Log.d(TAG, String.format("Stopping service after timeout of ~%d seconds", IDLE_TIMEOUT / 1000)); + TerminalManager.this.stopNow(); + } + } + + public void tryKeyVibrate() { + if (wantKeyVibration) + vibrate(); + } + + private void vibrate() { + if (vibrator != null) + vibrator.vibrate(VIBRATE_DURATION); + } + + private void enableMediaPlayer() { + mediaPlayer = new MediaPlayer(); + + float volume = prefs.getFloat(PreferenceConstants.BELL_VOLUME, + PreferenceConstants.DEFAULT_BELL_VOLUME); + + mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); + mediaPlayer.setOnCompletionListener(new BeepListener()); + + AssetFileDescriptor file = res.openRawResourceFd(R.raw.bell); + try { + mediaPlayer.setDataSource(file.getFileDescriptor(), file + .getStartOffset(), file.getLength()); + file.close(); + mediaPlayer.setVolume(volume, volume); + mediaPlayer.prepare(); + } catch (IOException e) { + Log.e(TAG, "Error setting up bell media player", e); + } + } + + private void disableMediaPlayer() { + if (mediaPlayer != null) { + mediaPlayer.release(); + mediaPlayer = null; + } + } + + public void playBeep() { + if (mediaPlayer != null) + mediaPlayer.start(); + + if (wantBellVibration) + vibrate(); + } + + private static class BeepListener implements OnCompletionListener { + public void onCompletion(MediaPlayer mp) { + mp.seekTo(0); + } + } + + /** + * Send system notification to user for a certain host. When user selects + * the notification, it will bring them directly to the ConsoleActivity + * displaying the host. + * + * @param host + */ + public void sendActivityNotification(HostBean host) { + if (!prefs.getBoolean(PreferenceConstants.BELL_NOTIFICATION, false)) + return; + + ConnectionNotifier.getInstance().showActivityNotification(this, host); + } + + /* (non-Javadoc) + * @see android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged(android.content.SharedPreferences, java.lang.String) + */ + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (PreferenceConstants.BELL.equals(key)) { + boolean wantAudible = sharedPreferences.getBoolean( + PreferenceConstants.BELL, true); + if (wantAudible && mediaPlayer == null) + enableMediaPlayer(); + else if (!wantAudible && mediaPlayer != null) + disableMediaPlayer(); + } else if (PreferenceConstants.BELL_VOLUME.equals(key)) { + if (mediaPlayer != null) { + float volume = sharedPreferences.getFloat( + PreferenceConstants.BELL_VOLUME, + PreferenceConstants.DEFAULT_BELL_VOLUME); + mediaPlayer.setVolume(volume, volume); + } + } else if (PreferenceConstants.BELL_VIBRATE.equals(key)) { + wantBellVibration = sharedPreferences.getBoolean( + PreferenceConstants.BELL_VIBRATE, true); + } else if (PreferenceConstants.BUMPY_ARROWS.equals(key)) { + wantKeyVibration = sharedPreferences.getBoolean( + PreferenceConstants.BUMPY_ARROWS, true); + } else if (PreferenceConstants.WIFI_LOCK.equals(key)) { + final boolean lockingWifi = prefs.getBoolean(PreferenceConstants.WIFI_LOCK, true); + connectivityManager.setWantWifiLock(lockingWifi); + } else if (PreferenceConstants.MEMKEYS.equals(key)) { + updateSavingKeys(); + } + } + + /** + * Allow {@link TerminalBridge} to resize when the parent has changed. + * @param resizeAllowed + */ + public void setResizeAllowed(boolean resizeAllowed) { + this.resizeAllowed = resizeAllowed; + } + + public boolean isResizeAllowed() { + return resizeAllowed; + } + + public static class KeyHolder { + public PubkeyBean bean; + public KeyPair pair; + public byte[] openSSHPubkey; + } + + /** + * Called when connectivity to the network is lost and it doesn't appear + * we'll be getting a different connection any time soon. + */ + public void onConnectivityLost() { + final Thread t = new Thread() { + @Override + public void run() { + disconnectAll(false); + } + }; + t.setName("Disconnector"); + t.start(); + } + + /** + * Called when connectivity to the network is restored. + */ + public void onConnectivityRestored() { + final Thread t = new Thread() { + @Override + public void run() { + reconnectPending(); + } + }; + t.setName("Reconnector"); + t.start(); + } + + /** + * Insert request into reconnect queue to be executed either immediately + * or later when connectivity is restored depending on whether we're + * currently connected. + * + * @param bridge the TerminalBridge to reconnect when possible + */ + public void requestReconnect(TerminalBridge bridge) { + synchronized (mPendingReconnect) { + mPendingReconnect.add(new WeakReference<TerminalBridge>(bridge)); + if (!bridge.isUsingNetwork() || + connectivityManager.isConnected()) { + reconnectPending(); + } + } + } + + /** + * Reconnect all bridges that were pending a reconnect when connectivity + * was lost. + */ + private void reconnectPending() { + synchronized (mPendingReconnect) { + for (WeakReference<TerminalBridge> ref : mPendingReconnect) { + TerminalBridge bridge = ref.get(); + if (bridge == null) { + continue; + } + bridge.startConnection(); + } + mPendingReconnect.clear(); + } + } +} diff --git a/app/src/main/java/org/connectbot/transport/AbsTransport.java b/app/src/main/java/org/connectbot/transport/AbsTransport.java new file mode 100644 index 0000000..bc19b92 --- /dev/null +++ b/app/src/main/java/org/connectbot/transport/AbsTransport.java @@ -0,0 +1,255 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.transport; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.connectbot.bean.HostBean; +import org.connectbot.bean.PortForwardBean; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalManager; + +import android.content.Context; +import android.net.Uri; + +/** + * @author Kenny Root + * + */ +public abstract class AbsTransport { + HostBean host; + TerminalBridge bridge; + TerminalManager manager; + + String emulation; + + public AbsTransport() { + } + + public AbsTransport(HostBean host, TerminalBridge bridge, TerminalManager manager) { + this.host = host; + this.bridge = bridge; + this.manager = manager; + } + + /** + * @return protocol part of the URI + */ + public static String getProtocolName() { + return "unknown"; + } + + /** + * Encode the current transport into a URI that can be passed via intent calls. + * @return URI to host + */ + public static Uri getUri(String input) { + return null; + } + + /** + * Causes transport to connect to the target host. After connecting but before a + * session is started, must call back to {@link TerminalBridge#onConnected()}. + * After that call a session may be opened. + */ + public abstract void connect(); + + /** + * Reads from the transport. Transport must support reading into a the byte array + * <code>buffer</code> at the start of <code>offset</code> and a maximum of + * <code>length</code> bytes. If the remote host disconnects, throw an + * {@link IOException}. + * @param buffer byte buffer to store read bytes into + * @param offset where to start writing in the buffer + * @param length maximum number of bytes to read + * @return number of bytes read + * @throws IOException when remote host disconnects + */ + public abstract int read(byte[] buffer, int offset, int length) throws IOException; + + /** + * Writes to the transport. If the host is not yet connected, simply return without + * doing anything. An {@link IOException} should be thrown if there is an error after + * connection. + * @param buffer bytes to write to transport + * @throws IOException when there is a problem writing after connection + */ + public abstract void write(byte[] buffer) throws IOException; + + /** + * Writes to the transport. See {@link #write(byte[])} for behavior details. + * @param c character to write to the transport + * @throws IOException when there is a problem writing after connection + */ + public abstract void write(int c) throws IOException; + + /** + * Flushes the write commands to the transport. + * @throws IOException when there is a problem writing after connection + */ + public abstract void flush() throws IOException; + + /** + * Closes the connection to the terminal. Note that the resulting failure to read + * should call {@link TerminalBridge#dispatchDisconnect(boolean)}. + */ + public abstract void close(); + + /** + * Tells the transport what dimensions the display is currently + * @param columns columns of text + * @param rows rows of text + * @param width width in pixels + * @param height height in pixels + */ + public abstract void setDimensions(int columns, int rows, int width, int height); + + public void setOptions(Map<String, String> options) { + // do nothing + } + + public Map<String, String> getOptions() { + return null; + } + + public void setCompression(boolean compression) { + // do nothing + } + + public void setUseAuthAgent(String useAuthAgent) { + // do nothing + } + + public void setEmulation(String emulation) { + this.emulation = emulation; + } + + public String getEmulation() { + return emulation; + } + + public void setHost(HostBean host) { + this.host = host; + } + + public void setBridge(TerminalBridge bridge) { + this.bridge = bridge; + } + + public void setManager(TerminalManager manager) { + this.manager = manager; + } + + /** + * Whether or not this transport type can forward ports. + * @return true on ability to forward ports + */ + public boolean canForwardPorts() { + return false; + } + + /** + * Adds the {@link PortForwardBean} to the list. + * @param portForward the port forward bean to add + * @return true on successful addition + */ + public boolean addPortForward(PortForwardBean portForward) { + return false; + } + + /** + * Enables a port forward member. After calling this method, the port forward should + * be operational iff it could be enabled by the transport. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward setup + */ + public boolean enablePortForward(PortForwardBean portForward) { + return false; + } + + /** + * Disables a port forward member. After calling this method, the port forward should + * be non-functioning iff it could be disabled by the transport. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward tear-down + */ + public boolean disablePortForward(PortForwardBean portForward) { + return false; + } + + /** + * Removes the {@link PortForwardBean} from the available port forwards. + * @param portForward the port forward bean to remove + * @return true on successful removal + */ + public boolean removePortForward(PortForwardBean portForward) { + return false; + } + + /** + * Gets a list of the {@link PortForwardBean} currently used by this transport. + * @return the list of port forwards + */ + public List<PortForwardBean> getPortForwards() { + return null; + } + + public abstract boolean isConnected(); + public abstract boolean isSessionOpen(); + + /** + * @return int default port for protocol + */ + public abstract int getDefaultPort(); + + /** + * @param username + * @param hostname + * @param port + * @return + */ + public abstract String getDefaultNickname(String username, String hostname, int port); + + /** + * @param uri + * @param selectionKeys + * @param selectionValues + */ + public abstract void getSelectionArgs(Uri uri, Map<String, String> selection); + + /** + * @param uri + * @return + */ + public abstract HostBean createHost(Uri uri); + + /** + * @param context context containing the correct resources + * @return string that hints at the format for connection + */ + public static String getFormatHint(Context context) { + return "???"; + } + + /** + * @return + */ + public abstract boolean usesNetwork(); +} diff --git a/app/src/main/java/org/connectbot/transport/Local.java b/app/src/main/java/org/connectbot/transport/Local.java new file mode 100644 index 0000000..f7bbf11 --- /dev/null +++ b/app/src/main/java/org/connectbot/transport/Local.java @@ -0,0 +1,224 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.transport; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Map; + +import org.connectbot.R; +import org.connectbot.bean.HostBean; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalManager; +import org.connectbot.util.HostDatabase; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.google.ase.Exec; + +/** + * @author Kenny Root + * + */ +public class Local extends AbsTransport { + private static final String TAG = "ConnectBot.Local"; + private static final String PROTOCOL = "local"; + + private static final String DEFAULT_URI = "local:#Local"; + + private FileDescriptor shellFd; + + private FileInputStream is; + private FileOutputStream os; + + /** + * + */ + public Local() { + } + + /** + * @param host + * @param bridge + * @param manager + */ + public Local(HostBean host, TerminalBridge bridge, TerminalManager manager) { + super(host, bridge, manager); + } + + public static String getProtocolName() { + return PROTOCOL; + } + + @Override + public void close() { + try { + if (os != null) { + os.close(); + os = null; + } + if (is != null) { + is.close(); + is = null; + } + } catch (IOException e) { + Log.e(TAG, "Couldn't close shell", e); + } + } + + @Override + public void connect() { + int[] pids = new int[1]; + + try { + shellFd = Exec.createSubprocess("/system/bin/sh", "-", null, pids); + } catch (Exception e) { + bridge.outputLine(manager.res.getString(R.string.local_shell_unavailable)); + Log.e(TAG, "Cannot start local shell", e); + return; + } + + final int shellPid = pids[0]; + Runnable exitWatcher = new Runnable() { + public void run() { + Exec.waitFor(shellPid); + + bridge.dispatchDisconnect(false); + } + }; + + Thread exitWatcherThread = new Thread(exitWatcher); + exitWatcherThread.setName("LocalExitWatcher"); + exitWatcherThread.setDaemon(true); + exitWatcherThread.start(); + + is = new FileInputStream(shellFd); + os = new FileOutputStream(shellFd); + + bridge.onConnected(); + } + + @Override + public void flush() throws IOException { + os.flush(); + } + + @Override + public String getDefaultNickname(String username, String hostname, int port) { + return DEFAULT_URI; + } + + @Override + public int getDefaultPort() { + return 0; + } + + @Override + public boolean isConnected() { + return is != null && os != null; + } + + @Override + public boolean isSessionOpen() { + return is != null && os != null; + } + + @Override + public int read(byte[] buffer, int start, int len) throws IOException { + if (is == null) { + bridge.dispatchDisconnect(false); + throw new IOException("session closed"); + } + return is.read(buffer, start, len); + } + + @Override + public void setDimensions(int columns, int rows, int width, int height) { + // We are not connected yet. + if (shellFd == null) { + return; + } + + try { + Exec.setPtyWindowSize(shellFd, rows, columns, width, height); + } catch (Exception e) { + Log.e(TAG, "Couldn't resize pty", e); + } + } + + @Override + public void write(byte[] buffer) throws IOException { + if (os != null) + os.write(buffer); + } + + @Override + public void write(int c) throws IOException { + if (os != null) + os.write(c); + } + + public static Uri getUri(String input) { + Uri uri = Uri.parse(DEFAULT_URI); + + if (input != null && input.length() > 0) { + uri = uri.buildUpon().fragment(input).build(); + } + + return uri; + } + + @Override + public HostBean createHost(Uri uri) { + HostBean host = new HostBean(); + + host.setProtocol(PROTOCOL); + + String nickname = uri.getFragment(); + if (nickname == null || nickname.length() == 0) { + host.setNickname(getDefaultNickname(host.getUsername(), + host.getHostname(), host.getPort())); + } else { + host.setNickname(uri.getFragment()); + } + + return host; + } + + @Override + public void getSelectionArgs(Uri uri, Map<String, String> selection) { + selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL); + selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment()); + } + + public static String getFormatHint(Context context) { + return context.getString(R.string.hostpref_nickname_title); + } + + /* (non-Javadoc) + * @see org.connectbot.transport.AbsTransport#usesNetwork() + */ + @Override + public boolean usesNetwork() { + return false; + } +} diff --git a/app/src/main/java/org/connectbot/transport/SSH.java b/app/src/main/java/org/connectbot/transport/SSH.java new file mode 100644 index 0000000..9a97596 --- /dev/null +++ b/app/src/main/java/org/connectbot/transport/SSH.java @@ -0,0 +1,958 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.connectbot.R; +import org.connectbot.bean.HostBean; +import org.connectbot.bean.PortForwardBean; +import org.connectbot.bean.PubkeyBean; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalManager; +import org.connectbot.service.TerminalManager.KeyHolder; +import org.connectbot.util.HostDatabase; +import org.connectbot.util.PubkeyDatabase; +import org.connectbot.util.PubkeyUtils; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.trilead.ssh2.AuthAgentCallback; +import com.trilead.ssh2.ChannelCondition; +import com.trilead.ssh2.Connection; +import com.trilead.ssh2.ConnectionInfo; +import com.trilead.ssh2.ConnectionMonitor; +import com.trilead.ssh2.DynamicPortForwarder; +import com.trilead.ssh2.InteractiveCallback; +import com.trilead.ssh2.KnownHosts; +import com.trilead.ssh2.LocalPortForwarder; +import com.trilead.ssh2.ServerHostKeyVerifier; +import com.trilead.ssh2.Session; +import com.trilead.ssh2.crypto.PEMDecoder; +import com.trilead.ssh2.signature.DSASHA1Verify; +import com.trilead.ssh2.signature.RSASHA1Verify; + +/** + * @author Kenny Root + * + */ +public class SSH extends AbsTransport implements ConnectionMonitor, InteractiveCallback, AuthAgentCallback { + public SSH() { + super(); + } + + /** + * @param bridge + * @param db + */ + public SSH(HostBean host, TerminalBridge bridge, TerminalManager manager) { + super(host, bridge, manager); + } + + private static final String PROTOCOL = "ssh"; + private static final String TAG = "ConnectBot.SSH"; + private static final int DEFAULT_PORT = 22; + + private static final String AUTH_PUBLICKEY = "publickey", + AUTH_PASSWORD = "password", + AUTH_KEYBOARDINTERACTIVE = "keyboard-interactive"; + + private final static int AUTH_TRIES = 20; + + static final Pattern hostmask; + static { + hostmask = Pattern.compile("^(.+)@([0-9a-z.-]+)(:(\\d+))?$", Pattern.CASE_INSENSITIVE); + } + + private boolean compression = false; + private volatile boolean authenticated = false; + private volatile boolean connected = false; + private volatile boolean sessionOpen = false; + + private boolean pubkeysExhausted = false; + private boolean interactiveCanContinue = true; + + private Connection connection; + private Session session; + private ConnectionInfo connectionInfo; + + private OutputStream stdin; + private InputStream stdout; + private InputStream stderr; + + private static final int conditions = ChannelCondition.STDOUT_DATA + | ChannelCondition.STDERR_DATA + | ChannelCondition.CLOSED + | ChannelCondition.EOF; + + private List<PortForwardBean> portForwards = new LinkedList<PortForwardBean>(); + + private int columns; + private int rows; + + private int width; + private int height; + + private String useAuthAgent = HostDatabase.AUTHAGENT_NO; + private String agentLockPassphrase; + + public class HostKeyVerifier implements ServerHostKeyVerifier { + public boolean verifyServerHostKey(String hostname, int port, + String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { + + // read in all known hosts from hostdb + KnownHosts hosts = manager.hostdb.getKnownHosts(); + Boolean result; + + String matchName = String.format(Locale.US, "%s:%d", hostname, port); + + String fingerprint = KnownHosts.createHexFingerprint(serverHostKeyAlgorithm, serverHostKey); + + String algorithmName; + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) + algorithmName = "RSA"; + else if ("ssh-dss".equals(serverHostKeyAlgorithm)) + algorithmName = "DSA"; + else if (serverHostKeyAlgorithm.startsWith("ecdsa-")) + algorithmName = "EC"; + else + algorithmName = serverHostKeyAlgorithm; + + switch(hosts.verifyHostkey(matchName, serverHostKeyAlgorithm, serverHostKey)) { + case KnownHosts.HOSTKEY_IS_OK: + bridge.outputLine(manager.res.getString(R.string.terminal_sucess, algorithmName, fingerprint)); + return true; + + case KnownHosts.HOSTKEY_IS_NEW: + // prompt user + bridge.outputLine(manager.res.getString(R.string.host_authenticity_warning, hostname)); + bridge.outputLine(manager.res.getString(R.string.host_fingerprint, algorithmName, fingerprint)); + + result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_continue_connecting)); + if (result == null) return false; + if (result.booleanValue()) { + // save this key in known database + manager.hostdb.saveKnownHost(hostname, port, serverHostKeyAlgorithm, serverHostKey); + } + return result.booleanValue(); + + case KnownHosts.HOSTKEY_HAS_CHANGED: + String header = String.format("@ %s @", + manager.res.getString(R.string.host_verification_failure_warning_header)); + + char[] atsigns = new char[header.length()]; + Arrays.fill(atsigns, '@'); + String border = new String(atsigns); + + bridge.outputLine(border); + bridge.outputLine(manager.res.getString(R.string.host_verification_failure_warning)); + bridge.outputLine(border); + + bridge.outputLine(String.format(manager.res.getString(R.string.host_fingerprint), + algorithmName, fingerprint)); + + // Users have no way to delete keys, so we'll prompt them for now. + result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_continue_connecting)); + if (result == null) return false; + if (result.booleanValue()) { + // save this key in known database + manager.hostdb.saveKnownHost(hostname, port, serverHostKeyAlgorithm, serverHostKey); + } + return result.booleanValue(); + + default: + return false; + } + } + + } + + private void authenticate() { + try { + if (connection.authenticateWithNone(host.getUsername())) { + finishConnection(); + return; + } + } catch (Exception e) { + Log.d(TAG, "Host does not support 'none' authentication."); + } + + bridge.outputLine(manager.res.getString(R.string.terminal_auth)); + + try { + long pubkeyId = host.getPubkeyId(); + + if (!pubkeysExhausted && + pubkeyId != HostDatabase.PUBKEYID_NEVER && + connection.isAuthMethodAvailable(host.getUsername(), AUTH_PUBLICKEY)) { + + // if explicit pubkey defined for this host, then prompt for password as needed + // otherwise just try all in-memory keys held in terminalmanager + + if (pubkeyId == HostDatabase.PUBKEYID_ANY) { + // try each of the in-memory keys + bridge.outputLine(manager.res + .getString(R.string.terminal_auth_pubkey_any)); + for (Entry<String, KeyHolder> entry : manager.loadedKeypairs.entrySet()) { + if (entry.getValue().bean.isConfirmUse() + && !promptForPubkeyUse(entry.getKey())) + continue; + + if (this.tryPublicKey(host.getUsername(), entry.getKey(), + entry.getValue().pair)) { + finishConnection(); + break; + } + } + } else { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_specific)); + // use a specific key for this host, as requested + PubkeyBean pubkey = manager.pubkeydb.findPubkeyById(pubkeyId); + + if (pubkey == null) + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_invalid)); + else + if (tryPublicKey(pubkey)) + finishConnection(); + } + + pubkeysExhausted = true; + } else if (interactiveCanContinue && + connection.isAuthMethodAvailable(host.getUsername(), AUTH_KEYBOARDINTERACTIVE)) { + // this auth method will talk with us using InteractiveCallback interface + // it blocks until authentication finishes + bridge.outputLine(manager.res.getString(R.string.terminal_auth_ki)); + interactiveCanContinue = false; + if (connection.authenticateWithKeyboardInteractive(host.getUsername(), this)) { + finishConnection(); + } else { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_ki_fail)); + } + } else if (connection.isAuthMethodAvailable(host.getUsername(), AUTH_PASSWORD)) { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pass)); + String password = bridge.getPromptHelper().requestStringPrompt(null, + manager.res.getString(R.string.prompt_password)); + if (password != null + && connection.authenticateWithPassword(host.getUsername(), password)) { + finishConnection(); + } else { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pass_fail)); + } + } else { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_fail)); + } + } catch (IllegalStateException e) { + Log.e(TAG, "Connection went away while we were trying to authenticate", e); + return; + } catch (Exception e) { + Log.e(TAG, "Problem during handleAuthentication()", e); + } + } + + /** + * Attempt connection with database row pointed to by cursor. + * @param cursor + * @return true for successful authentication + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws IOException + */ + private boolean tryPublicKey(PubkeyBean pubkey) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + KeyPair pair = null; + + if (manager.isKeyLoaded(pubkey.getNickname())) { + // load this key from memory if its already there + Log.d(TAG, String.format("Found unlocked key '%s' already in-memory", pubkey.getNickname())); + + if (pubkey.isConfirmUse()) { + if (!promptForPubkeyUse(pubkey.getNickname())) + return false; + } + + pair = manager.getKey(pubkey.getNickname()); + } else { + // otherwise load key from database and prompt for password as needed + String password = null; + if (pubkey.isEncrypted()) { + password = bridge.getPromptHelper().requestStringPrompt(null, + manager.res.getString(R.string.prompt_pubkey_password, pubkey.getNickname())); + + // Something must have interrupted the prompt. + if (password == null) + return false; + } + + if (PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType())) { + // load specific key using pem format + pair = PEMDecoder.decode(new String(pubkey.getPrivateKey()).toCharArray(), password); + } else { + // load using internal generated format + PrivateKey privKey; + try { + privKey = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), + pubkey.getType(), password); + } catch (Exception e) { + String message = String.format("Bad password for key '%s'. Authentication failed.", pubkey.getNickname()); + Log.e(TAG, message, e); + bridge.outputLine(message); + return false; + } + + PublicKey pubKey = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType()); + + // convert key to trilead format + pair = new KeyPair(pubKey, privKey); + Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey)); + } + + Log.d(TAG, String.format("Unlocked key '%s'", pubkey.getNickname())); + + // save this key in memory + manager.addKey(pubkey, pair); + } + + return tryPublicKey(host.getUsername(), pubkey.getNickname(), pair); + } + + private boolean tryPublicKey(String username, String keyNickname, KeyPair pair) throws IOException { + //bridge.outputLine(String.format("Attempting 'publickey' with key '%s' [%s]...", keyNickname, trileadKey.toString())); + boolean success = connection.authenticateWithPublicKey(username, pair); + if (!success) + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_fail, keyNickname)); + return success; + } + + /** + * Internal method to request actual PTY terminal once we've finished + * authentication. If called before authenticated, it will just fail. + */ + private void finishConnection() { + authenticated = true; + + for (PortForwardBean portForward : portForwards) { + try { + enablePortForward(portForward); + bridge.outputLine(manager.res.getString(R.string.terminal_enable_portfoward, portForward.getDescription())); + } catch (Exception e) { + Log.e(TAG, "Error setting up port forward during connect", e); + } + } + + if (!host.getWantSession()) { + bridge.outputLine(manager.res.getString(R.string.terminal_no_session)); + bridge.onConnected(); + return; + } + + try { + session = connection.openSession(); + + if (!useAuthAgent.equals(HostDatabase.AUTHAGENT_NO)) + session.requestAuthAgentForwarding(this); + + session.requestPTY(getEmulation(), columns, rows, width, height, null); + session.startShell(); + + stdin = session.getStdin(); + stdout = session.getStdout(); + stderr = session.getStderr(); + + sessionOpen = true; + + bridge.onConnected(); + } catch (IOException e1) { + Log.e(TAG, "Problem while trying to create PTY in finishConnection()", e1); + } + + } + + @Override + public void connect() { + connection = new Connection(host.getHostname(), host.getPort()); + connection.addConnectionMonitor(this); + + try { + connection.setCompression(compression); + } catch (IOException e) { + Log.e(TAG, "Could not enable compression!", e); + } + + try { + /* Uncomment when debugging SSH protocol: + DebugLogger logger = new DebugLogger() { + + public void log(int level, String className, String message) { + Log.d("SSH", message); + } + + }; + Logger.enabled = true; + Logger.logger = logger; + */ + connectionInfo = connection.connect(new HostKeyVerifier()); + connected = true; + + if (connectionInfo.clientToServerCryptoAlgorithm + .equals(connectionInfo.serverToClientCryptoAlgorithm) + && connectionInfo.clientToServerMACAlgorithm + .equals(connectionInfo.serverToClientMACAlgorithm)) { + bridge.outputLine(manager.res.getString(R.string.terminal_using_algorithm, + connectionInfo.clientToServerCryptoAlgorithm, + connectionInfo.clientToServerMACAlgorithm)); + } else { + bridge.outputLine(manager.res.getString( + R.string.terminal_using_c2s_algorithm, + connectionInfo.clientToServerCryptoAlgorithm, + connectionInfo.clientToServerMACAlgorithm)); + + bridge.outputLine(manager.res.getString( + R.string.terminal_using_s2c_algorithm, + connectionInfo.serverToClientCryptoAlgorithm, + connectionInfo.serverToClientMACAlgorithm)); + } + } catch (IOException e) { + Log.e(TAG, "Problem in SSH connection thread during authentication", e); + + // Display the reason in the text. + bridge.outputLine(e.getCause().getMessage()); + + onDisconnect(); + return; + } + + try { + // enter a loop to keep trying until authentication + int tries = 0; + while (connected && !connection.isAuthenticationComplete() && tries++ < AUTH_TRIES) { + authenticate(); + + // sleep to make sure we dont kill system + Thread.sleep(1000); + } + } catch (Exception e) { + Log.e(TAG, "Problem in SSH connection thread during authentication", e); + } + } + + @Override + public void close() { + connected = false; + + if (session != null) { + session.close(); + session = null; + } + + if (connection != null) { + connection.close(); + connection = null; + } + } + + private void onDisconnect() { + close(); + + bridge.dispatchDisconnect(false); + } + + @Override + public void flush() throws IOException { + if (stdin != null) + stdin.flush(); + } + + @Override + public int read(byte[] buffer, int start, int len) throws IOException { + int bytesRead = 0; + + if (session == null) + return 0; + + int newConditions = session.waitForCondition(conditions, 0); + + if ((newConditions & ChannelCondition.STDOUT_DATA) != 0) { + bytesRead = stdout.read(buffer, start, len); + } + + if ((newConditions & ChannelCondition.STDERR_DATA) != 0) { + byte discard[] = new byte[256]; + while (stderr.available() > 0) { + stderr.read(discard); + } + } + + if ((newConditions & ChannelCondition.EOF) != 0) { + onDisconnect(); + throw new IOException("Remote end closed connection"); + } + + return bytesRead; + } + + @Override + public void write(byte[] buffer) throws IOException { + if (stdin != null) + stdin.write(buffer); + } + + @Override + public void write(int c) throws IOException { + if (stdin != null) + stdin.write(c); + } + + @Override + public Map<String, String> getOptions() { + Map<String, String> options = new HashMap<String, String>(); + + options.put("compression", Boolean.toString(compression)); + + return options; + } + + @Override + public void setOptions(Map<String, String> options) { + if (options.containsKey("compression")) + compression = Boolean.parseBoolean(options.get("compression")); + } + + public static String getProtocolName() { + return PROTOCOL; + } + + @Override + public boolean isSessionOpen() { + return sessionOpen; + } + + @Override + public boolean isConnected() { + return connected; + } + + public void connectionLost(Throwable reason) { + onDisconnect(); + } + + @Override + public boolean canForwardPorts() { + return true; + } + + @Override + public List<PortForwardBean> getPortForwards() { + return portForwards; + } + + @Override + public boolean addPortForward(PortForwardBean portForward) { + return portForwards.add(portForward); + } + + @Override + public boolean removePortForward(PortForwardBean portForward) { + // Make sure we don't have a phantom forwarder. + disablePortForward(portForward); + + return portForwards.remove(portForward); + } + + @Override + public boolean enablePortForward(PortForwardBean portForward) { + if (!portForwards.contains(portForward)) { + Log.e(TAG, "Attempt to enable port forward not in list"); + return false; + } + + if (!authenticated) + return false; + + if (HostDatabase.PORTFORWARD_LOCAL.equals(portForward.getType())) { + LocalPortForwarder lpf = null; + try { + lpf = connection.createLocalPortForwarder( + new InetSocketAddress(InetAddress.getLocalHost(), portForward.getSourcePort()), + portForward.getDestAddr(), portForward.getDestPort()); + } catch (Exception e) { + Log.e(TAG, "Could not create local port forward", e); + return false; + } + + if (lpf == null) { + Log.e(TAG, "returned LocalPortForwarder object is null"); + return false; + } + + portForward.setIdentifier(lpf); + portForward.setEnabled(true); + return true; + } else if (HostDatabase.PORTFORWARD_REMOTE.equals(portForward.getType())) { + try { + connection.requestRemotePortForwarding("", portForward.getSourcePort(), portForward.getDestAddr(), portForward.getDestPort()); + } catch (Exception e) { + Log.e(TAG, "Could not create remote port forward", e); + return false; + } + + portForward.setEnabled(true); + return true; + } else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(portForward.getType())) { + DynamicPortForwarder dpf = null; + + try { + dpf = connection.createDynamicPortForwarder( + new InetSocketAddress(InetAddress.getLocalHost(), portForward.getSourcePort())); + } catch (Exception e) { + Log.e(TAG, "Could not create dynamic port forward", e); + return false; + } + + portForward.setIdentifier(dpf); + portForward.setEnabled(true); + return true; + } else { + // Unsupported type + Log.e(TAG, String.format("attempt to forward unknown type %s", portForward.getType())); + return false; + } + } + + @Override + public boolean disablePortForward(PortForwardBean portForward) { + if (!portForwards.contains(portForward)) { + Log.e(TAG, "Attempt to disable port forward not in list"); + return false; + } + + if (!authenticated) + return false; + + if (HostDatabase.PORTFORWARD_LOCAL.equals(portForward.getType())) { + LocalPortForwarder lpf = null; + lpf = (LocalPortForwarder) portForward.getIdentifier(); + + if (!portForward.isEnabled() || lpf == null) { + Log.d(TAG, String.format("Could not disable %s; it appears to be not enabled or have no handler", portForward.getNickname())); + return false; + } + + portForward.setEnabled(false); + + try { + lpf.close(); + } catch (IOException e) { + Log.e(TAG, "Could not stop local port forwarder, setting enabled to false", e); + return false; + } + + return true; + } else if (HostDatabase.PORTFORWARD_REMOTE.equals(portForward.getType())) { + portForward.setEnabled(false); + + try { + connection.cancelRemotePortForwarding(portForward.getSourcePort()); + } catch (IOException e) { + Log.e(TAG, "Could not stop remote port forwarding, setting enabled to false", e); + return false; + } + + return true; + } else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(portForward.getType())) { + DynamicPortForwarder dpf = null; + dpf = (DynamicPortForwarder) portForward.getIdentifier(); + + if (!portForward.isEnabled() || dpf == null) { + Log.d(TAG, String.format("Could not disable %s; it appears to be not enabled or have no handler", portForward.getNickname())); + return false; + } + + portForward.setEnabled(false); + + try { + dpf.close(); + } catch (IOException e) { + Log.e(TAG, "Could not stop dynamic port forwarder, setting enabled to false", e); + return false; + } + + return true; + } else { + // Unsupported type + Log.e(TAG, String.format("attempt to forward unknown type %s", portForward.getType())); + return false; + } + } + + @Override + public void setDimensions(int columns, int rows, int width, int height) { + this.columns = columns; + this.rows = rows; + + if (sessionOpen) { + try { + session.resizePTY(columns, rows, width, height); + } catch (IOException e) { + Log.e(TAG, "Couldn't send resize PTY packet", e); + } + } + } + + @Override + public int getDefaultPort() { + return DEFAULT_PORT; + } + + @Override + public String getDefaultNickname(String username, String hostname, int port) { + if (port == DEFAULT_PORT) { + return String.format(Locale.US, "%s@%s", username, hostname); + } else { + return String.format(Locale.US, "%s@%s:%d", username, hostname, port); + } + } + + public static Uri getUri(String input) { + Matcher matcher = hostmask.matcher(input); + + if (!matcher.matches()) + return null; + + StringBuilder sb = new StringBuilder(); + + sb.append(PROTOCOL) + .append("://") + .append(Uri.encode(matcher.group(1))) + .append('@') + .append(matcher.group(2)); + + String portString = matcher.group(4); + int port = DEFAULT_PORT; + if (portString != null) { + try { + port = Integer.parseInt(portString); + if (port < 1 || port > 65535) { + port = DEFAULT_PORT; + } + } catch (NumberFormatException nfe) { + // Keep the default port + } + } + + if (port != DEFAULT_PORT) { + sb.append(':') + .append(port); + } + + sb.append("/#") + .append(Uri.encode(input)); + + Uri uri = Uri.parse(sb.toString()); + + return uri; + } + + /** + * Handle challenges from keyboard-interactive authentication mode. + */ + public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) { + interactiveCanContinue = true; + String[] responses = new String[numPrompts]; + for (int i = 0; i < numPrompts; i++) { + // request response from user for each prompt + responses[i] = bridge.promptHelper.requestStringPrompt(instruction, prompt[i]); + } + return responses; + } + + @Override + public HostBean createHost(Uri uri) { + HostBean host = new HostBean(); + + host.setProtocol(PROTOCOL); + + host.setHostname(uri.getHost()); + + int port = uri.getPort(); + if (port < 0) + port = DEFAULT_PORT; + host.setPort(port); + + host.setUsername(uri.getUserInfo()); + + String nickname = uri.getFragment(); + if (nickname == null || nickname.length() == 0) { + host.setNickname(getDefaultNickname(host.getUsername(), + host.getHostname(), host.getPort())); + } else { + host.setNickname(uri.getFragment()); + } + + return host; + } + + @Override + public void getSelectionArgs(Uri uri, Map<String, String> selection) { + selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL); + selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment()); + selection.put(HostDatabase.FIELD_HOST_HOSTNAME, uri.getHost()); + + int port = uri.getPort(); + if (port < 0) + port = DEFAULT_PORT; + selection.put(HostDatabase.FIELD_HOST_PORT, Integer.toString(port)); + selection.put(HostDatabase.FIELD_HOST_USERNAME, uri.getUserInfo()); + } + + @Override + public void setCompression(boolean compression) { + this.compression = compression; + } + + public static String getFormatHint(Context context) { + return String.format("%s@%s:%s", + context.getString(R.string.format_username), + context.getString(R.string.format_hostname), + context.getString(R.string.format_port)); + } + + @Override + public void setUseAuthAgent(String useAuthAgent) { + this.useAuthAgent = useAuthAgent; + } + + public Map<String, byte[]> retrieveIdentities() { + Map<String, byte[]> pubKeys = new HashMap<String, byte[]>(manager.loadedKeypairs.size()); + + for (Entry<String, KeyHolder> entry : manager.loadedKeypairs.entrySet()) { + KeyPair pair = entry.getValue().pair; + + try { + PrivateKey privKey = pair.getPrivate(); + if (privKey instanceof RSAPrivateKey) { + RSAPublicKey pubkey = (RSAPublicKey) pair.getPublic(); + pubKeys.put(entry.getKey(), RSASHA1Verify.encodeSSHRSAPublicKey(pubkey)); + } else if (privKey instanceof DSAPrivateKey) { + DSAPublicKey pubkey = (DSAPublicKey) pair.getPublic(); + pubKeys.put(entry.getKey(), DSASHA1Verify.encodeSSHDSAPublicKey(pubkey)); + } else + continue; + } catch (IOException e) { + continue; + } + } + + return pubKeys; + } + + public KeyPair getKeyPair(byte[] publicKey) { + String nickname = manager.getKeyNickname(publicKey); + + if (nickname == null) + return null; + + if (useAuthAgent.equals(HostDatabase.AUTHAGENT_NO)) { + Log.e(TAG, ""); + return null; + } else if (useAuthAgent.equals(HostDatabase.AUTHAGENT_CONFIRM) || + manager.loadedKeypairs.get(nickname).bean.isConfirmUse()) { + if (!promptForPubkeyUse(nickname)) + return null; + } + return manager.getKey(nickname); + } + + private boolean promptForPubkeyUse(String nickname) { + Boolean result = bridge.promptHelper.requestBooleanPrompt(null, + manager.res.getString(R.string.prompt_allow_agent_to_use_key, + nickname)); + return result; + } + + public boolean addIdentity(KeyPair pair, String comment, boolean confirmUse, int lifetime) { + PubkeyBean pubkey = new PubkeyBean(); +// pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED); + pubkey.setNickname(comment); + pubkey.setConfirmUse(confirmUse); + pubkey.setLifetime(lifetime); + manager.addKey(pubkey, pair); + return true; + } + + public boolean removeAllIdentities() { + manager.loadedKeypairs.clear(); + return true; + } + + public boolean removeIdentity(byte[] publicKey) { + return manager.removeKey(publicKey); + } + + public boolean isAgentLocked() { + return agentLockPassphrase != null; + } + + public boolean requestAgentUnlock(String unlockPassphrase) { + if (agentLockPassphrase == null) + return false; + + if (agentLockPassphrase.equals(unlockPassphrase)) + agentLockPassphrase = null; + + return agentLockPassphrase == null; + } + + public boolean setAgentLock(String lockPassphrase) { + if (agentLockPassphrase != null) + return false; + + agentLockPassphrase = lockPassphrase; + return true; + } + + /* (non-Javadoc) + * @see org.connectbot.transport.AbsTransport#usesNetwork() + */ + @Override + public boolean usesNetwork() { + return true; + } +} diff --git a/app/src/main/java/org/connectbot/transport/Telnet.java b/app/src/main/java/org/connectbot/transport/Telnet.java new file mode 100644 index 0000000..5fde2f6 --- /dev/null +++ b/app/src/main/java/org/connectbot/transport/Telnet.java @@ -0,0 +1,330 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.charset.Charset; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.connectbot.R; +import org.connectbot.bean.HostBean; +import org.connectbot.service.TerminalBridge; +import org.connectbot.service.TerminalManager; +import org.connectbot.util.HostDatabase; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; +import de.mud.telnet.TelnetProtocolHandler; + +/** + * Telnet transport implementation.<br/> + * Original idea from the JTA telnet package (de.mud.telnet) + * + * @author Kenny Root + * + */ +public class Telnet extends AbsTransport { + private static final String TAG = "ConnectBot.Telnet"; + private static final String PROTOCOL = "telnet"; + + private static final int DEFAULT_PORT = 23; + + private TelnetProtocolHandler handler; + private Socket socket; + + private InputStream is; + private OutputStream os; + private int width; + private int height; + + private boolean connected = false; + + static final Pattern hostmask; + static { + hostmask = Pattern.compile("^([0-9a-z.-]+)(:(\\d+))?$", Pattern.CASE_INSENSITIVE); + } + + public Telnet() { + handler = new TelnetProtocolHandler() { + /** get the current terminal type */ + @Override + public String getTerminalType() { + return getEmulation(); + } + + /** get the current window size */ + @Override + public int[] getWindowSize() { + return new int[] { width, height }; + } + + /** notify about local echo */ + @Override + public void setLocalEcho(boolean echo) { + /* EMPTY */ + } + + /** write data to our back end */ + @Override + public void write(byte[] b) throws IOException { + if (os != null) + os.write(b); + } + + /** sent on IAC EOR (prompt terminator for remote access systems). */ + @Override + public void notifyEndOfRecord() { + } + + @Override + protected String getCharsetName() { + Charset charset = bridge.getCharset(); + if (charset != null) + return charset.name(); + else + return ""; + } + }; + } + + /** + * @param host + * @param bridge + * @param manager + */ + public Telnet(HostBean host, TerminalBridge bridge, TerminalManager manager) { + super(host, bridge, manager); + } + + public static String getProtocolName() { + return PROTOCOL; + } + + @Override + public void connect() { + try { + socket = new Socket(host.getHostname(), host.getPort()); + + connected = true; + + is = socket.getInputStream(); + os = socket.getOutputStream(); + + bridge.onConnected(); + } catch (UnknownHostException e) { + Log.d(TAG, "IO Exception connecting to host", e); + } catch (IOException e) { + Log.d(TAG, "IO Exception connecting to host", e); + } + } + + @Override + public void close() { + connected = false; + if (socket != null) + try { + socket.close(); + socket = null; + } catch (IOException e) { + Log.d(TAG, "Error closing telnet socket.", e); + } + } + + @Override + public void flush() throws IOException { + os.flush(); + } + + @Override + public int getDefaultPort() { + return DEFAULT_PORT; + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public boolean isSessionOpen() { + return connected; + } + + @Override + public int read(byte[] buffer, int start, int len) throws IOException { + /* process all already read bytes */ + int n = 0; + + do { + n = handler.negotiate(buffer, start); + if (n > 0) + return n; + } while (n == 0); + + while (n <= 0) { + do { + n = handler.negotiate(buffer, start); + if (n > 0) + return n; + } while (n == 0); + n = is.read(buffer, start, len); + if (n < 0) { + bridge.dispatchDisconnect(false); + throw new IOException("Remote end closed connection."); + } + + handler.inputfeed(buffer, start, n); + n = handler.negotiate(buffer, start); + } + return n; + } + + @Override + public void write(byte[] buffer) throws IOException { + try { + if (os != null) + os.write(buffer); + } catch (SocketException e) { + bridge.dispatchDisconnect(false); + } + } + + @Override + public void write(int c) throws IOException { + try { + if (os != null) + os.write(c); + } catch (SocketException e) { + bridge.dispatchDisconnect(false); + } + } + + @Override + public void setDimensions(int columns, int rows, int width, int height) { + try { + handler.setWindowSize(columns, rows); + } catch (IOException e) { + Log.e(TAG, "Couldn't resize remote terminal", e); + } + } + + @Override + public String getDefaultNickname(String username, String hostname, int port) { + if (port == DEFAULT_PORT) { + return String.format("%s", hostname); + } else { + return String.format("%s:%d", hostname, port); + } + } + + public static Uri getUri(String input) { + Matcher matcher = hostmask.matcher(input); + + if (!matcher.matches()) + return null; + + StringBuilder sb = new StringBuilder(); + + sb.append(PROTOCOL) + .append("://") + .append(matcher.group(1)); + + String portString = matcher.group(3); + int port = DEFAULT_PORT; + if (portString != null) { + try { + port = Integer.parseInt(portString); + if (port < 1 || port > 65535) { + port = DEFAULT_PORT; + } + } catch (NumberFormatException nfe) { + // Keep the default port + } + } + + if (port != DEFAULT_PORT) { + sb.append(':'); + sb.append(port); + } + + sb.append("/#") + .append(Uri.encode(input)); + + Uri uri = Uri.parse(sb.toString()); + + return uri; + } + + @Override + public HostBean createHost(Uri uri) { + HostBean host = new HostBean(); + + host.setProtocol(PROTOCOL); + + host.setHostname(uri.getHost()); + + int port = uri.getPort(); + if (port < 0) + port = DEFAULT_PORT; + host.setPort(port); + + String nickname = uri.getFragment(); + if (nickname == null || nickname.length() == 0) { + host.setNickname(getDefaultNickname(host.getUsername(), + host.getHostname(), host.getPort())); + } else { + host.setNickname(uri.getFragment()); + } + + return host; + } + + @Override + public void getSelectionArgs(Uri uri, Map<String, String> selection) { + selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL); + selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment()); + selection.put(HostDatabase.FIELD_HOST_HOSTNAME, uri.getHost()); + + int port = uri.getPort(); + if (port < 0) + port = DEFAULT_PORT; + selection.put(HostDatabase.FIELD_HOST_PORT, Integer.toString(port)); + } + + public static String getFormatHint(Context context) { + return String.format("%s:%s", + context.getString(R.string.format_hostname), + context.getString(R.string.format_port)); + } + + /* (non-Javadoc) + * @see org.connectbot.transport.AbsTransport#usesNetwork() + */ + @Override + public boolean usesNetwork() { + return true; + } +} diff --git a/app/src/main/java/org/connectbot/transport/TransportFactory.java b/app/src/main/java/org/connectbot/transport/TransportFactory.java new file mode 100644 index 0000000..72e5e08 --- /dev/null +++ b/app/src/main/java/org/connectbot/transport/TransportFactory.java @@ -0,0 +1,132 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.transport; + +import java.util.HashMap; +import java.util.Map; + +import org.connectbot.bean.HostBean; +import org.connectbot.util.HostDatabase; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + + +/** + * @author Kenny Root + * + */ +public class TransportFactory { + private static final String TAG = "ConnectBot.TransportFactory"; + + private static String[] transportNames = { + SSH.getProtocolName(), + Telnet.getProtocolName(), + Local.getProtocolName(), + }; + + /** + * @param protocol + * @return + */ + public static AbsTransport getTransport(String protocol) { + if (SSH.getProtocolName().equals(protocol)) { + return new SSH(); + } else if (Telnet.getProtocolName().equals(protocol)) { + return new Telnet(); + } else if (Local.getProtocolName().equals(protocol)) { + return new Local(); + } else { + return null; + } + } + + public static Uri getUri(String scheme, String input) { + Log.d("TransportFactory", String.format( + "Attempting to discover URI for scheme=%s on input=%s", scheme, + input)); + if (SSH.getProtocolName().equals(scheme)) + return SSH.getUri(input); + else if (Telnet.getProtocolName().equals(scheme)) + return Telnet.getUri(input); + else if (Local.getProtocolName().equals(scheme)) { + Log.d("TransportFactory", "Got to the local parsing area"); + return Local.getUri(input); + } else + return null; + } + + public static String[] getTransportNames() { + return transportNames; + } + + public static boolean isSameTransportType(AbsTransport a, AbsTransport b) { + if (a == null || b == null) + return false; + + return a.getClass().equals(b.getClass()); + } + + public static boolean canForwardPorts(String protocol) { + // TODO uh, make this have less knowledge about its children + if (SSH.getProtocolName().equals(protocol)) { + return true; + } else { + return false; + } + } + + /** + * @param protocol text name of protocol + * @param context + * @return expanded format hint + */ + public static String getFormatHint(String protocol, Context context) { + if (SSH.getProtocolName().equals(protocol)) { + return SSH.getFormatHint(context); + } else if (Telnet.getProtocolName().equals(protocol)) { + return Telnet.getFormatHint(context); + } else if (Local.getProtocolName().equals(protocol)) { + return Local.getFormatHint(context); + } else { + return AbsTransport.getFormatHint(context); + } + } + + /** + * @param hostdb Handle to HostDatabase + * @param uri URI to target server + * @param host HostBean in which to put the results + * @return true when host was found + */ + public static HostBean findHost(HostDatabase hostdb, Uri uri) { + AbsTransport transport = getTransport(uri.getScheme()); + + Map<String, String> selection = new HashMap<String, String>(); + + transport.getSelectionArgs(uri, selection); + if (selection.size() == 0) { + Log.e(TAG, String.format("Transport %s failed to do something useful with URI=%s", + uri.getScheme(), uri.toString())); + throw new IllegalStateException("Failed to get needed selection arguments"); + } + + return hostdb.findHost(selection); + } +} diff --git a/app/src/main/java/org/connectbot/util/Colors.java b/app/src/main/java/org/connectbot/util/Colors.java new file mode 100644 index 0000000..ff88d68 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/Colors.java @@ -0,0 +1,91 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +/** + * @author Kenny Root + * + */ +public class Colors { + public final static Integer[] defaults = new Integer[] { + 0xff000000, // black + 0xffcc0000, // red + 0xff00cc00, // green + 0xffcccc00, // brown + 0xff0000cc, // blue + 0xffcc00cc, // purple + 0xff00cccc, // cyan + 0xffcccccc, // light grey + 0xff444444, // dark grey + 0xffff4444, // light red + 0xff44ff44, // light green + 0xffffff44, // yellow + 0xff4444ff, // light blue + 0xffff44ff, // light purple + 0xff44ffff, // light cyan + 0xffffffff, // white + 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, + 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, + 0xff005fd7, 0xff005fff, 0xff008700, 0xff00875f, 0xff008787, + 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, + 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff, 0xff00d700, + 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, + 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, + 0xff00ffff, 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, + 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, + 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff, 0xff5f8700, 0xff5f875f, + 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, + 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff, + 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, + 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, + 0xff5fffd7, 0xff5fffff, 0xff870000, 0xff87005f, 0xff870087, + 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, + 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff, 0xff878700, + 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, + 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, + 0xff87afff, 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, + 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, + 0xff87ffaf, 0xff87ffd7, 0xff87ffff, 0xffaf0000, 0xffaf005f, + 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, + 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff, + 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, + 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, + 0xffafafd7, 0xffafafff, 0xffafd700, 0xffafd75f, 0xffafd787, + 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, + 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff, 0xffd70000, + 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, + 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, + 0xffd75fff, 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, + 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, + 0xffd7afaf, 0xffd7afd7, 0xffd7afff, 0xffd7d700, 0xffd7d75f, + 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, + 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff, + 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, + 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, + 0xffff5fd7, 0xffff5fff, 0xffff8700, 0xffff875f, 0xffff8787, + 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, + 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff, 0xffffd700, + 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, + 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, + 0xffffffff, 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, + 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, + 0xff626262, 0xff6c6c6c, 0xff767676, 0xff808080, 0xff8a8a8a, + 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, + 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee, + }; +} diff --git a/app/src/main/java/org/connectbot/util/EastAsianWidth.java b/app/src/main/java/org/connectbot/util/EastAsianWidth.java new file mode 100644 index 0000000..ea80cdd --- /dev/null +++ b/app/src/main/java/org/connectbot/util/EastAsianWidth.java @@ -0,0 +1,77 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import android.annotation.TargetApi; +import android.graphics.Paint; +import android.text.AndroidCharacter; + +/** + * @author Kenny Root + * + */ +public abstract class EastAsianWidth { + public static EastAsianWidth getInstance() { + if (PreferenceConstants.PRE_FROYO) + return PreFroyo.Holder.sInstance; + else + return FroyoAndBeyond.Holder.sInstance; + } + + /** + * @param charArray + * @param i + * @param position + * @param wideAttribute + */ + public abstract void measure(char[] charArray, int start, int end, + byte[] wideAttribute, Paint paint, int charWidth); + + private static class PreFroyo extends EastAsianWidth { + private static final int BUFFER_SIZE = 4096; + private float[] mWidths = new float[BUFFER_SIZE]; + + private static class Holder { + private static final PreFroyo sInstance = new PreFroyo(); + } + + @Override + public void measure(char[] charArray, int start, int end, + byte[] wideAttribute, Paint paint, int charWidth) { + paint.getTextWidths(charArray, start, end, mWidths); + final int N = end - start; + for (int i = 0; i < N; i++) + wideAttribute[i] = (byte) (((int) mWidths[i] != charWidth) ? + AndroidCharacter.EAST_ASIAN_WIDTH_WIDE : + AndroidCharacter.EAST_ASIAN_WIDTH_NARROW); + } + } + + @TargetApi(8) + private static class FroyoAndBeyond extends EastAsianWidth { + private static class Holder { + private static final FroyoAndBeyond sInstance = new FroyoAndBeyond(); + } + + @Override + public void measure(char[] charArray, int start, int end, + byte[] wideAttribute, Paint paint, int charWidth) { + AndroidCharacter.getEastAsianWidths(charArray, start, end - start, wideAttribute); + } + } +} diff --git a/app/src/main/java/org/connectbot/util/Encryptor.java b/app/src/main/java/org/connectbot/util/Encryptor.java new file mode 100644 index 0000000..11a71d6 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/Encryptor.java @@ -0,0 +1,205 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +/** + * This class is from: + * + * Encryptor.java + * Copyright 2008 Zach Scrivena + * zachscrivena@gmail.com + * http://zs.freeshell.org/ + */ + +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + + +/** + * Perform AES-128 encryption. + */ +public final class Encryptor +{ + /** name of the character set to use for converting between characters and bytes */ + private static final String CHARSET_NAME = "UTF-8"; + + /** random number generator algorithm */ + private static final String RNG_ALGORITHM = "SHA1PRNG"; + + /** message digest algorithm (must be sufficiently long to provide the key and initialization vector) */ + private static final String DIGEST_ALGORITHM = "SHA-256"; + + /** key algorithm (must be compatible with CIPHER_ALGORITHM) */ + private static final String KEY_ALGORITHM = "AES"; + + /** cipher algorithm (must be compatible with KEY_ALGORITHM) */ + private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; + + + /** + * Private constructor that should never be called. + */ + private Encryptor() { + } + + + /** + * Encrypt the specified cleartext using the given password. + * With the correct salt, number of iterations, and password, the decrypt() method reverses + * the effect of this method. + * This method generates and uses a random salt, and the user-specified number of iterations + * and password to create a 16-byte secret key and 16-byte initialization vector. + * The secret key and initialization vector are then used in the AES-128 cipher to encrypt + * the given cleartext. + * + * @param salt + * salt that was used in the encryption (to be populated) + * @param iterations + * number of iterations to use in salting + * @param password + * password to be used for encryption + * @param cleartext + * cleartext to be encrypted + * @return + * ciphertext + * @throws Exception + * on any error encountered in encryption + */ + public static byte[] encrypt( + final byte[] salt, + final int iterations, + final String password, + final byte[] cleartext) + throws Exception + { + /* generate salt randomly */ + SecureRandom.getInstance(RNG_ALGORITHM).nextBytes(salt); + + /* compute key and initialization vector */ + final MessageDigest shaDigest = MessageDigest.getInstance(DIGEST_ALGORITHM); + byte[] pw = password.getBytes(CHARSET_NAME); + + for (int i = 0; i < iterations; i++) + { + /* add salt */ + final byte[] salted = new byte[pw.length + salt.length]; + System.arraycopy(pw, 0, salted, 0, pw.length); + System.arraycopy(salt, 0, salted, pw.length, salt.length); + Arrays.fill(pw, (byte) 0x00); + + /* compute SHA-256 digest */ + shaDigest.reset(); + pw = shaDigest.digest(salted); + Arrays.fill(salted, (byte) 0x00); + } + + /* extract the 16-byte key and initialization vector from the SHA-256 digest */ + final byte[] key = new byte[16]; + final byte[] iv = new byte[16]; + System.arraycopy(pw, 0, key, 0, 16); + System.arraycopy(pw, 16, iv, 0, 16); + Arrays.fill(pw, (byte) 0x00); + + /* perform AES-128 encryption */ + final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + + cipher.init( + Cipher.ENCRYPT_MODE, + new SecretKeySpec(key, KEY_ALGORITHM), + new IvParameterSpec(iv)); + + Arrays.fill(key, (byte) 0x00); + Arrays.fill(iv, (byte) 0x00); + + return cipher.doFinal(cleartext); + } + + + /** + * Decrypt the specified ciphertext using the given password. + * With the correct salt, number of iterations, and password, this method reverses the effect + * of the encrypt() method. + * This method uses the user-specified salt, number of iterations, and password + * to recreate the 16-byte secret key and 16-byte initialization vector. + * The secret key and initialization vector are then used in the AES-128 cipher to decrypt + * the given ciphertext. + * + * @param salt + * salt to be used in decryption + * @param iterations + * number of iterations to use in salting + * @param password + * password to be used for decryption + * @param ciphertext + * ciphertext to be decrypted + * @return + * cleartext + * @throws Exception + * on any error encountered in decryption + */ + public static byte[] decrypt( + final byte[] salt, + final int iterations, + final String password, + final byte[] ciphertext) + throws Exception + { + /* compute key and initialization vector */ + final MessageDigest shaDigest = MessageDigest.getInstance(DIGEST_ALGORITHM); + byte[] pw = password.getBytes(CHARSET_NAME); + + for (int i = 0; i < iterations; i++) + { + /* add salt */ + final byte[] salted = new byte[pw.length + salt.length]; + System.arraycopy(pw, 0, salted, 0, pw.length); + System.arraycopy(salt, 0, salted, pw.length, salt.length); + Arrays.fill(pw, (byte) 0x00); + + /* compute SHA-256 digest */ + shaDigest.reset(); + pw = shaDigest.digest(salted); + Arrays.fill(salted, (byte) 0x00); + } + + /* extract the 16-byte key and initialization vector from the SHA-256 digest */ + final byte[] key = new byte[16]; + final byte[] iv = new byte[16]; + System.arraycopy(pw, 0, key, 0, 16); + System.arraycopy(pw, 16, iv, 0, 16); + Arrays.fill(pw, (byte) 0x00); + + /* perform AES-128 decryption */ + final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + + cipher.init( + Cipher.DECRYPT_MODE, + new SecretKeySpec(key, KEY_ALGORITHM), + new IvParameterSpec(iv)); + + Arrays.fill(key, (byte) 0x00); + Arrays.fill(iv, (byte) 0x00); + + return cipher.doFinal(ciphertext); + } +} diff --git a/app/src/main/java/org/connectbot/util/EntropyDialog.java b/app/src/main/java/org/connectbot/util/EntropyDialog.java new file mode 100644 index 0000000..4498ce2 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/EntropyDialog.java @@ -0,0 +1,50 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import org.connectbot.R; + +import android.app.Dialog; +import android.content.Context; +import android.view.View; + +public class EntropyDialog extends Dialog implements OnEntropyGatheredListener { + + public EntropyDialog(Context context) { + super(context); + + this.setContentView(R.layout.dia_gatherentropy); + this.setTitle(R.string.pubkey_gather_entropy); + + ((EntropyView) findViewById(R.id.entropy)).addOnEntropyGatheredListener(this); + } + + public EntropyDialog(Context context, View view) { + super(context); + + this.setContentView(view); + this.setTitle(R.string.pubkey_gather_entropy); + + ((EntropyView) findViewById(R.id.entropy)).addOnEntropyGatheredListener(this); + } + + public void onEntropyGathered(byte[] entropy) { + this.dismiss(); + } + +} diff --git a/app/src/main/java/org/connectbot/util/EntropyView.java b/app/src/main/java/org/connectbot/util/EntropyView.java new file mode 100644 index 0000000..3fb2a90 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/EntropyView.java @@ -0,0 +1,169 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import java.util.Vector; + +import org.connectbot.R; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.Paint.FontMetrics; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +public class EntropyView extends View { + private static final int SHA1_MAX_BYTES = 20; + private static final int MILLIS_BETWEEN_INPUTS = 50; + + private Paint mPaint; + private FontMetrics mFontMetrics; + private boolean mFlipFlop; + private long mLastTime; + private Vector<OnEntropyGatheredListener> listeners; + + private byte[] mEntropy; + private int mEntropyByteIndex; + private int mEntropyBitIndex; + + private int splitText = 0; + + private float lastX = 0.0f, lastY = 0.0f; + + public EntropyView(Context context) { + super(context); + + setUpEntropy(); + } + + public EntropyView(Context context, AttributeSet attrs) { + super(context, attrs); + + setUpEntropy(); + } + + private void setUpEntropy() { + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setTypeface(Typeface.DEFAULT); + mPaint.setTextAlign(Paint.Align.CENTER); + mPaint.setTextSize(16); + mPaint.setColor(Color.WHITE); + mFontMetrics = mPaint.getFontMetrics(); + + mEntropy = new byte[SHA1_MAX_BYTES]; + mEntropyByteIndex = 0; + mEntropyBitIndex = 0; + + listeners = new Vector<OnEntropyGatheredListener>(); + } + + public void addOnEntropyGatheredListener(OnEntropyGatheredListener listener) { + listeners.add(listener); + } + + public void removeOnEntropyGatheredListener(OnEntropyGatheredListener listener) { + listeners.remove(listener); + } + + @Override + public void onDraw(Canvas c) { + String prompt = String.format(getResources().getString(R.string.pubkey_touch_prompt), + (int) (100.0 * (mEntropyByteIndex / 20.0)) + (int) (5.0 * (mEntropyBitIndex / 8.0))); + if (splitText > 0 || + mPaint.measureText(prompt) > (getWidth() * 0.8)) { + if (splitText == 0) + splitText = prompt.indexOf(" ", prompt.length() / 2); + + c.drawText(prompt.substring(0, splitText), + getWidth() / 2.0f, + getHeight() / 2.0f + (mPaint.ascent() + mPaint.descent()), + mPaint); + c.drawText(prompt.substring(splitText), + getWidth() / 2.0f, + getHeight() / 2.0f - (mPaint.ascent() + mPaint.descent()), + mPaint); + } else { + c.drawText(prompt, + getWidth() / 2.0f, + getHeight() / 2.0f - (mFontMetrics.ascent + mFontMetrics.descent) / 2, + mPaint); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mEntropyByteIndex >= SHA1_MAX_BYTES + || lastX == event.getX() + || lastY == event.getY()) + return true; + + // Only get entropy every 200 milliseconds to ensure the user has moved around. + long now = System.currentTimeMillis(); + if ((now - mLastTime) < MILLIS_BETWEEN_INPUTS) + return true; + else + mLastTime = now; + + byte input; + + lastX = event.getX(); + lastY = event.getY(); + + // Get the lowest 4 bits of each X, Y input and concat to the entropy-gathering + // string. + if (mFlipFlop) + input = (byte) ((((int) lastX & 0x0F) << 4) | ((int) lastY & 0x0F)); + else + input = (byte) ((((int) lastY & 0x0F) << 4) | ((int) lastX & 0x0F)); + mFlipFlop = !mFlipFlop; + + for (int i = 0; i < 4 && mEntropyByteIndex < SHA1_MAX_BYTES; i++) { + if ((input & 0x3) == 0x1) { + mEntropy[mEntropyByteIndex] <<= 1; + mEntropy[mEntropyByteIndex] |= 1; + mEntropyBitIndex++; + input >>= 2; + } else if ((input & 0x3) == 0x2) { + mEntropy[mEntropyByteIndex] <<= 1; + mEntropyBitIndex++; + input >>= 2; + } + + if (mEntropyBitIndex >= 8) { + mEntropyBitIndex = 0; + mEntropyByteIndex++; + } + } + + // SHA1PRNG only keeps 160 bits of entropy. + if (mEntropyByteIndex >= SHA1_MAX_BYTES) { + for (OnEntropyGatheredListener listener: listeners) { + listener.onEntropyGathered(mEntropy); + } + } + + invalidate(); + + return true; + } +} diff --git a/app/src/main/java/org/connectbot/util/HelpTopicView.java b/app/src/main/java/org/connectbot/util/HelpTopicView.java new file mode 100644 index 0000000..0cbc267 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/HelpTopicView.java @@ -0,0 +1,62 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import org.connectbot.HelpActivity; + +import android.content.Context; +import android.util.AttributeSet; +import android.webkit.WebSettings; +import android.webkit.WebView; + +/** + * @author Kenny Root + * + */ +public class HelpTopicView extends WebView { + public HelpTopicView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialize(); + } + + public HelpTopicView(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public HelpTopicView(Context context) { + super(context); + initialize(); + } + + private void initialize() { + WebSettings wSet = getSettings(); + wSet.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); + wSet.setUseWideViewPort(false); + } + + public HelpTopicView setTopic(String topic) { + String path = String.format("file:///android_asset/%s/%s%s", + HelpActivity.HELPDIR, topic, HelpActivity.SUFFIX); + loadUrl(path); + + computeScroll(); + + return this; + } +} diff --git a/app/src/main/java/org/connectbot/util/HostDatabase.java b/app/src/main/java/org/connectbot/util/HostDatabase.java new file mode 100644 index 0000000..d1d87c7 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/HostDatabase.java @@ -0,0 +1,774 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.connectbot.bean.HostBean; +import org.connectbot.bean.PortForwardBean; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.util.Log; + +import com.trilead.ssh2.KnownHosts; + +/** + * Contains information about various SSH hosts, include public hostkey if known + * from previous sessions. + * + * @author jsharkey + */ +public class HostDatabase extends RobustSQLiteOpenHelper { + + public final static String TAG = "ConnectBot.HostDatabase"; + + public final static String DB_NAME = "hosts"; + public final static int DB_VERSION = 24; + + public final static String TABLE_HOSTS = "hosts"; + public final static String FIELD_HOST_NICKNAME = "nickname"; + public final static String FIELD_HOST_PROTOCOL = "protocol"; + public final static String FIELD_HOST_USERNAME = "username"; + public final static String FIELD_HOST_HOSTNAME = "hostname"; + public final static String FIELD_HOST_PORT = "port"; + public final static String FIELD_HOST_HOSTKEYALGO = "hostkeyalgo"; + public final static String FIELD_HOST_HOSTKEY = "hostkey"; + public final static String FIELD_HOST_LASTCONNECT = "lastconnect"; + public final static String FIELD_HOST_COLOR = "color"; + public final static String FIELD_HOST_USEKEYS = "usekeys"; + public final static String FIELD_HOST_USEAUTHAGENT = "useauthagent"; + public final static String FIELD_HOST_POSTLOGIN = "postlogin"; + public final static String FIELD_HOST_PUBKEYID = "pubkeyid"; + public final static String FIELD_HOST_WANTSESSION = "wantsession"; + public final static String FIELD_HOST_DELKEY = "delkey"; + public final static String FIELD_HOST_FONTSIZE = "fontsize"; + public final static String FIELD_HOST_COMPRESSION = "compression"; + public final static String FIELD_HOST_ENCODING = "encoding"; + public final static String FIELD_HOST_STAYCONNECTED = "stayconnected"; + public final static String FIELD_HOST_QUICKDISCONNECT = "quickdisconnect"; + + public final static String TABLE_PORTFORWARDS = "portforwards"; + public final static String FIELD_PORTFORWARD_HOSTID = "hostid"; + public final static String FIELD_PORTFORWARD_NICKNAME = "nickname"; + public final static String FIELD_PORTFORWARD_TYPE = "type"; + public final static String FIELD_PORTFORWARD_SOURCEPORT = "sourceport"; + public final static String FIELD_PORTFORWARD_DESTADDR = "destaddr"; + public final static String FIELD_PORTFORWARD_DESTPORT = "destport"; + + public final static String TABLE_COLORS = "colors"; + public final static String FIELD_COLOR_SCHEME = "scheme"; + public final static String FIELD_COLOR_NUMBER = "number"; + public final static String FIELD_COLOR_VALUE = "value"; + + public final static String TABLE_COLOR_DEFAULTS = "colorDefaults"; + public final static String FIELD_COLOR_FG = "fg"; + public final static String FIELD_COLOR_BG = "bg"; + + public final static int DEFAULT_FG_COLOR = 7; + public final static int DEFAULT_BG_COLOR = 0; + + public final static String COLOR_RED = "red"; + public final static String COLOR_GREEN = "green"; + public final static String COLOR_BLUE = "blue"; + public final static String COLOR_GRAY = "gray"; + + public final static String PORTFORWARD_LOCAL = "local"; + public final static String PORTFORWARD_REMOTE = "remote"; + public final static String PORTFORWARD_DYNAMIC4 = "dynamic4"; + public final static String PORTFORWARD_DYNAMIC5 = "dynamic5"; + + public final static String DELKEY_DEL = "del"; + public final static String DELKEY_BACKSPACE = "backspace"; + + public final static String AUTHAGENT_NO = "no"; + public final static String AUTHAGENT_CONFIRM = "confirm"; + public final static String AUTHAGENT_YES = "yes"; + + public final static String ENCODING_DEFAULT = Charset.defaultCharset().name(); + + public final static long PUBKEYID_NEVER = -2; + public final static long PUBKEYID_ANY = -1; + + public static final int DEFAULT_COLOR_SCHEME = 0; + + // Table creation strings + public static final String CREATE_TABLE_COLOR_DEFAULTS = + "CREATE TABLE " + TABLE_COLOR_DEFAULTS + + " (" + FIELD_COLOR_SCHEME + " INTEGER NOT NULL, " + + FIELD_COLOR_FG + " INTEGER NOT NULL DEFAULT " + DEFAULT_FG_COLOR + ", " + + FIELD_COLOR_BG + " INTEGER NOT NULL DEFAULT " + DEFAULT_BG_COLOR + ")"; + public static final String CREATE_TABLE_COLOR_DEFAULTS_INDEX = + "CREATE INDEX " + TABLE_COLOR_DEFAULTS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLOR_DEFAULTS + " (" + FIELD_COLOR_SCHEME + ");"; + + private static final String WHERE_SCHEME_AND_COLOR = FIELD_COLOR_SCHEME + " = ? AND " + + FIELD_COLOR_NUMBER + " = ?"; + + static { + addTableName(TABLE_HOSTS); + addTableName(TABLE_PORTFORWARDS); + addIndexName(TABLE_PORTFORWARDS + FIELD_PORTFORWARD_HOSTID + "index"); + addTableName(TABLE_COLORS); + addIndexName(TABLE_COLORS + FIELD_COLOR_SCHEME + "index"); + addTableName(TABLE_COLOR_DEFAULTS); + addIndexName(TABLE_COLOR_DEFAULTS + FIELD_COLOR_SCHEME + "index"); + } + + public static final Object[] dbLock = new Object[0]; + + /** Used during upgrades from DB version 23 to 24. */ + private final float displayDensity; + + public HostDatabase(Context context) { + super(context, DB_NAME, null, DB_VERSION); + + this.displayDensity = context.getResources().getDisplayMetrics().density; + + getWritableDatabase().close(); + } + + @Override + public void onCreate(SQLiteDatabase db) { + super.onCreate(db); + + db.execSQL("CREATE TABLE " + TABLE_HOSTS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_HOST_NICKNAME + " TEXT, " + + FIELD_HOST_PROTOCOL + " TEXT DEFAULT 'ssh', " + + FIELD_HOST_USERNAME + " TEXT, " + + FIELD_HOST_HOSTNAME + " TEXT, " + + FIELD_HOST_PORT + " INTEGER, " + + FIELD_HOST_HOSTKEYALGO + " TEXT, " + + FIELD_HOST_HOSTKEY + " BLOB, " + + FIELD_HOST_LASTCONNECT + " INTEGER, " + + FIELD_HOST_COLOR + " TEXT, " + + FIELD_HOST_USEKEYS + " TEXT, " + + FIELD_HOST_USEAUTHAGENT + " TEXT, " + + FIELD_HOST_POSTLOGIN + " TEXT, " + + FIELD_HOST_PUBKEYID + " INTEGER DEFAULT " + PUBKEYID_ANY + ", " + + FIELD_HOST_DELKEY + " TEXT DEFAULT '" + DELKEY_DEL + "', " + + FIELD_HOST_FONTSIZE + " INTEGER, " + + FIELD_HOST_WANTSESSION + " TEXT DEFAULT '" + Boolean.toString(true) + "', " + + FIELD_HOST_COMPRESSION + " TEXT DEFAULT '" + Boolean.toString(false) + "', " + + FIELD_HOST_ENCODING + " TEXT DEFAULT '" + ENCODING_DEFAULT + "', " + + FIELD_HOST_STAYCONNECTED + " TEXT, " + + FIELD_HOST_QUICKDISCONNECT + " TEXT DEFAULT '" + Boolean.toString(false) + "')"); + + db.execSQL("CREATE TABLE " + TABLE_PORTFORWARDS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_PORTFORWARD_HOSTID + " INTEGER, " + + FIELD_PORTFORWARD_NICKNAME + " TEXT, " + + FIELD_PORTFORWARD_TYPE + " TEXT NOT NULL DEFAULT " + PORTFORWARD_LOCAL + ", " + + FIELD_PORTFORWARD_SOURCEPORT + " INTEGER NOT NULL DEFAULT 8080, " + + FIELD_PORTFORWARD_DESTADDR + " TEXT, " + + FIELD_PORTFORWARD_DESTPORT + " TEXT)"); + + db.execSQL("CREATE INDEX " + TABLE_PORTFORWARDS + FIELD_PORTFORWARD_HOSTID + "index ON " + + TABLE_PORTFORWARDS + " (" + FIELD_PORTFORWARD_HOSTID + ");"); + + db.execSQL("CREATE TABLE " + TABLE_COLORS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_COLOR_NUMBER + " INTEGER, " + + FIELD_COLOR_VALUE + " INTEGER, " + + FIELD_COLOR_SCHEME + " INTEGER)"); + + db.execSQL("CREATE INDEX " + TABLE_COLORS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLORS + " (" + FIELD_COLOR_SCHEME + ");"); + + db.execSQL(CREATE_TABLE_COLOR_DEFAULTS); + db.execSQL(CREATE_TABLE_COLOR_DEFAULTS_INDEX); + } + + @Override + public void onRobustUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLiteException { + // Versions of the database before the Android Market release will be + // shot without warning. + if (oldVersion <= 9) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_HOSTS); + onCreate(db); + return; + } + + switch (oldVersion) { + case 10: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_PUBKEYID + " INTEGER DEFAULT " + PUBKEYID_ANY); + case 11: + db.execSQL("CREATE TABLE " + TABLE_PORTFORWARDS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_PORTFORWARD_HOSTID + " INTEGER, " + + FIELD_PORTFORWARD_NICKNAME + " TEXT, " + + FIELD_PORTFORWARD_TYPE + " TEXT NOT NULL DEFAULT " + PORTFORWARD_LOCAL + ", " + + FIELD_PORTFORWARD_SOURCEPORT + " INTEGER NOT NULL DEFAULT 8080, " + + FIELD_PORTFORWARD_DESTADDR + " TEXT, " + + FIELD_PORTFORWARD_DESTPORT + " INTEGER)"); + case 12: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_WANTSESSION + " TEXT DEFAULT '" + Boolean.toString(true) + "'"); + case 13: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_COMPRESSION + " TEXT DEFAULT '" + Boolean.toString(false) + "'"); + case 14: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_ENCODING + " TEXT DEFAULT '" + ENCODING_DEFAULT + "'"); + case 15: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_PROTOCOL + " TEXT DEFAULT 'ssh'"); + case 16: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_DELKEY + " TEXT DEFAULT '" + DELKEY_DEL + "'"); + case 17: + db.execSQL("CREATE INDEX " + TABLE_PORTFORWARDS + FIELD_PORTFORWARD_HOSTID + "index ON " + + TABLE_PORTFORWARDS + " (" + FIELD_PORTFORWARD_HOSTID + ");"); + + // Add colors + db.execSQL("CREATE TABLE " + TABLE_COLORS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_COLOR_NUMBER + " INTEGER, " + + FIELD_COLOR_VALUE + " INTEGER, " + + FIELD_COLOR_SCHEME + " INTEGER)"); + db.execSQL("CREATE INDEX " + TABLE_COLORS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLORS + " (" + FIELD_COLOR_SCHEME + ");"); + case 18: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_USEAUTHAGENT + " TEXT DEFAULT '" + AUTHAGENT_NO + "'"); + case 19: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_STAYCONNECTED + " TEXT"); + case 20: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_FONTSIZE + " INTEGER"); + case 21: + db.execSQL("DROP TABLE " + TABLE_COLOR_DEFAULTS); + db.execSQL(CREATE_TABLE_COLOR_DEFAULTS); + db.execSQL(CREATE_TABLE_COLOR_DEFAULTS_INDEX); + case 22: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_QUICKDISCONNECT + " TEXT DEFAULT '" + Boolean.toString(false) + "'"); + case 23: + db.execSQL("UPDATE " + TABLE_HOSTS + + " SET " + FIELD_HOST_FONTSIZE + " = " + FIELD_HOST_FONTSIZE + " / " + displayDensity); + } + } + + /** + * Touch a specific host to update its "last connected" field. + * @param host host to update + */ + public void touchHost(HostBean host) { + long now = System.currentTimeMillis() / 1000; + + ContentValues values = new ContentValues(); + values.put(FIELD_HOST_LASTCONNECT, now); + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + + db.update(TABLE_HOSTS, values, "_id = ?", new String[] { String.valueOf(host.getId()) }); + } + } + + /** + * Create a new host using the given parameters. + */ + public HostBean saveHost(HostBean host) { + long id; + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + + id = db.insert(TABLE_HOSTS, null, host.getValues()); + } + + host.setId(id); + + return host; + } + + /** + * Update a field in a host record. + */ + public boolean updateFontSize(HostBean host) { + long id = host.getId(); + if (id < 0) + return false; + + ContentValues updates = new ContentValues(); + updates.put(FIELD_HOST_FONTSIZE, host.getFontSize()); + + synchronized (dbLock) { + SQLiteDatabase db = getWritableDatabase(); + + db.update(TABLE_HOSTS, updates, "_id = ?", + new String[] { String.valueOf(id) }); + + } + + return true; + } + + /** + * Delete a specific host by its <code>_id</code> value. + */ + public void deleteHost(HostBean host) { + if (host.getId() < 0) + return; + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + db.delete(TABLE_HOSTS, "_id = ?", new String[] { String.valueOf(host.getId()) }); + } + } + + /** + * Return a cursor that contains information about all known hosts. + * @param sortColors If true, sort by color, otherwise sort by nickname. + */ + public List<HostBean> getHosts(boolean sortColors) { + String sortField = sortColors ? FIELD_HOST_COLOR : FIELD_HOST_NICKNAME; + List<HostBean> hosts; + + synchronized (dbLock) { + SQLiteDatabase db = this.getReadableDatabase(); + + Cursor c = db.query(TABLE_HOSTS, null, null, null, null, null, sortField + " ASC"); + + hosts = createHostBeans(c); + + c.close(); + } + + return hosts; + } + + /** + * @param c cursor to read from + */ + private List<HostBean> createHostBeans(Cursor c) { + List<HostBean> hosts = new LinkedList<HostBean>(); + + final int COL_ID = c.getColumnIndexOrThrow("_id"), + COL_NICKNAME = c.getColumnIndexOrThrow(FIELD_HOST_NICKNAME), + COL_PROTOCOL = c.getColumnIndexOrThrow(FIELD_HOST_PROTOCOL), + COL_USERNAME = c.getColumnIndexOrThrow(FIELD_HOST_USERNAME), + COL_HOSTNAME = c.getColumnIndexOrThrow(FIELD_HOST_HOSTNAME), + COL_PORT = c.getColumnIndexOrThrow(FIELD_HOST_PORT), + COL_LASTCONNECT = c.getColumnIndexOrThrow(FIELD_HOST_LASTCONNECT), + COL_COLOR = c.getColumnIndexOrThrow(FIELD_HOST_COLOR), + COL_USEKEYS = c.getColumnIndexOrThrow(FIELD_HOST_USEKEYS), + COL_USEAUTHAGENT = c.getColumnIndexOrThrow(FIELD_HOST_USEAUTHAGENT), + COL_POSTLOGIN = c.getColumnIndexOrThrow(FIELD_HOST_POSTLOGIN), + COL_PUBKEYID = c.getColumnIndexOrThrow(FIELD_HOST_PUBKEYID), + COL_WANTSESSION = c.getColumnIndexOrThrow(FIELD_HOST_WANTSESSION), + COL_DELKEY = c.getColumnIndexOrThrow(FIELD_HOST_DELKEY), + COL_FONTSIZE = c.getColumnIndexOrThrow(FIELD_HOST_FONTSIZE), + COL_COMPRESSION = c.getColumnIndexOrThrow(FIELD_HOST_COMPRESSION), + COL_ENCODING = c.getColumnIndexOrThrow(FIELD_HOST_ENCODING), + COL_STAYCONNECTED = c.getColumnIndexOrThrow(FIELD_HOST_STAYCONNECTED), + COL_QUICKDISCONNECT = c.getColumnIndexOrThrow(FIELD_HOST_QUICKDISCONNECT); + + while (c.moveToNext()) { + HostBean host = new HostBean(); + + host.setId(c.getLong(COL_ID)); + host.setNickname(c.getString(COL_NICKNAME)); + host.setProtocol(c.getString(COL_PROTOCOL)); + host.setUsername(c.getString(COL_USERNAME)); + host.setHostname(c.getString(COL_HOSTNAME)); + host.setPort(c.getInt(COL_PORT)); + host.setLastConnect(c.getLong(COL_LASTCONNECT)); + host.setColor(c.getString(COL_COLOR)); + host.setUseKeys(Boolean.valueOf(c.getString(COL_USEKEYS))); + host.setUseAuthAgent(c.getString(COL_USEAUTHAGENT)); + host.setPostLogin(c.getString(COL_POSTLOGIN)); + host.setPubkeyId(c.getLong(COL_PUBKEYID)); + host.setWantSession(Boolean.valueOf(c.getString(COL_WANTSESSION))); + host.setDelKey(c.getString(COL_DELKEY)); + host.setFontSize(c.getInt(COL_FONTSIZE)); + host.setCompression(Boolean.valueOf(c.getString(COL_COMPRESSION))); + host.setEncoding(c.getString(COL_ENCODING)); + host.setStayConnected(Boolean.valueOf(c.getString(COL_STAYCONNECTED))); + host.setQuickDisconnect(Boolean.valueOf(c.getString(COL_QUICKDISCONNECT))); + + hosts.add(host); + } + + return hosts; + } + + /** + * @param c cursor with zero or more hosts + * @return the first host from the cursor or {@code null} if none. + */ + private HostBean getFirstHostBean(Cursor c) { + HostBean host = null; + + List<HostBean> hosts = createHostBeans(c); + if (hosts.size() > 0) + host = hosts.get(0); + + c.close(); + + return host; + } + + /** + * @param selection parameters describing the desired host + * @return host matching selection or {@code null}. + */ + public HostBean findHost(Map<String, String> selection) { + StringBuilder selectionBuilder = new StringBuilder(); + + Iterator<Entry<String, String>> i = selection.entrySet().iterator(); + + List<String> selectionValuesList = new LinkedList<String>(); + int n = 0; + while (i.hasNext()) { + Entry<String, String> entry = i.next(); + + if (entry.getValue() == null) + continue; + + if (n++ > 0) + selectionBuilder.append(" AND "); + + selectionBuilder.append(entry.getKey()) + .append(" = ?"); + + selectionValuesList.add(entry.getValue()); + } + + String selectionValues[] = new String[selectionValuesList.size()]; + selectionValuesList.toArray(selectionValues); + selectionValuesList = null; + + HostBean host; + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + + Cursor c = db.query(TABLE_HOSTS, null, + selectionBuilder.toString(), + selectionValues, + null, null, null); + + host = getFirstHostBean(c); + } + + return host; + } + + /** + * @param hostId host id for the host + * @return host matching the hostId or {@code null} if none match + */ + public HostBean findHostById(long hostId) { + HostBean host; + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + + Cursor c = db.query(TABLE_HOSTS, null, + "_id = ?", new String[] { String.valueOf(hostId) }, + null, null, null); + + host = getFirstHostBean(c); + } + + return host; + } + + /** + * Record the given hostkey into database under this nickname. + * @param hostname hostname to match + * @param port port to match + * @param hostkeyalgo algorithm for host key + * @param hostkey the bytes of the host key itself + */ + public void saveKnownHost(String hostname, int port, String hostkeyalgo, byte[] hostkey) { + ContentValues values = new ContentValues(); + values.put(FIELD_HOST_HOSTKEYALGO, hostkeyalgo); + values.put(FIELD_HOST_HOSTKEY, hostkey); + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + + db.update(TABLE_HOSTS, values, + FIELD_HOST_HOSTNAME + " = ? AND " + FIELD_HOST_PORT + " = ?", + new String[] { hostname, String.valueOf(port) }); + Log.d(TAG, String.format("Finished saving hostkey information for '%s'", hostname)); + } + } + + /** + * Build list of known hosts for Trilead library. + * @return + */ + public KnownHosts getKnownHosts() { + KnownHosts known = new KnownHosts(); + + synchronized (dbLock) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_HOSTS, new String[] { FIELD_HOST_HOSTNAME, + FIELD_HOST_PORT, FIELD_HOST_HOSTKEYALGO, FIELD_HOST_HOSTKEY }, + null, null, null, null, null); + + if (c != null) { + int COL_HOSTNAME = c.getColumnIndexOrThrow(FIELD_HOST_HOSTNAME), + COL_PORT = c.getColumnIndexOrThrow(FIELD_HOST_PORT), + COL_HOSTKEYALGO = c.getColumnIndexOrThrow(FIELD_HOST_HOSTKEYALGO), + COL_HOSTKEY = c.getColumnIndexOrThrow(FIELD_HOST_HOSTKEY); + + while (c.moveToNext()) { + String hostname = c.getString(COL_HOSTNAME), + hostkeyalgo = c.getString(COL_HOSTKEYALGO); + int port = c.getInt(COL_PORT); + byte[] hostkey = c.getBlob(COL_HOSTKEY); + + if (hostkeyalgo == null || hostkeyalgo.length() == 0) continue; + if (hostkey == null || hostkey.length == 0) continue; + + try { + known.addHostkey(new String[] { String.format("%s:%d", hostname, port) }, hostkeyalgo, hostkey); + } catch (Exception e) { + Log.e(TAG, "Problem while adding a known host from database", e); + } + } + + c.close(); + } + } + + return known; + } + + /** + * Unset any hosts using a pubkey ID that has been deleted. + * @param pubkeyId + */ + public void stopUsingPubkey(long pubkeyId) { + if (pubkeyId < 0) return; + + ContentValues values = new ContentValues(); + values.put(FIELD_HOST_PUBKEYID, PUBKEYID_ANY); + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + + db.update(TABLE_HOSTS, values, FIELD_HOST_PUBKEYID + " = ?", new String[] { String.valueOf(pubkeyId) }); + } + + Log.d(TAG, String.format("Set all hosts using pubkey id %d to -1", pubkeyId)); + } + + /* + * Methods for dealing with port forwards attached to hosts + */ + + /** + * Returns a list of all the port forwards associated with a particular host ID. + * @param host the host for which we want the port forward list + * @return port forwards associated with host ID + */ + public List<PortForwardBean> getPortForwardsForHost(HostBean host) { + List<PortForwardBean> portForwards = new LinkedList<PortForwardBean>(); + + synchronized (dbLock) { + SQLiteDatabase db = this.getReadableDatabase(); + + Cursor c = db.query(TABLE_PORTFORWARDS, new String[] { + "_id", FIELD_PORTFORWARD_NICKNAME, FIELD_PORTFORWARD_TYPE, FIELD_PORTFORWARD_SOURCEPORT, + FIELD_PORTFORWARD_DESTADDR, FIELD_PORTFORWARD_DESTPORT }, + FIELD_PORTFORWARD_HOSTID + " = ?", new String[] { String.valueOf(host.getId()) }, + null, null, null); + + while (c.moveToNext()) { + PortForwardBean pfb = new PortForwardBean( + c.getInt(0), + host.getId(), + c.getString(1), + c.getString(2), + c.getInt(3), + c.getString(4), + c.getInt(5)); + portForwards.add(pfb); + } + + c.close(); + } + + return portForwards; + } + + /** + * Update the parameters of a port forward in the database. + * @param pfb {@link PortForwardBean} to save + * @return true on success + */ + public boolean savePortForward(PortForwardBean pfb) { + boolean success = false; + + synchronized (dbLock) { + SQLiteDatabase db = getWritableDatabase(); + + if (pfb.getId() < 0) { + long id = db.insert(TABLE_PORTFORWARDS, null, pfb.getValues()); + pfb.setId(id); + success = true; + } else { + if (db.update(TABLE_PORTFORWARDS, pfb.getValues(), "_id = ?", new String[] { String.valueOf(pfb.getId()) }) > 0) + success = true; + } + } + + return success; + } + + /** + * Deletes a port forward from the database. + * @param pfb {@link PortForwardBean} to delete + */ + public void deletePortForward(PortForwardBean pfb) { + if (pfb.getId() < 0) + return; + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + db.delete(TABLE_PORTFORWARDS, "_id = ?", new String[] { String.valueOf(pfb.getId()) }); + } + } + + public Integer[] getColorsForScheme(int scheme) { + Integer[] colors = Colors.defaults.clone(); + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + + Cursor c = db.query(TABLE_COLORS, new String[] { + FIELD_COLOR_NUMBER, FIELD_COLOR_VALUE }, + FIELD_COLOR_SCHEME + " = ?", + new String[] { String.valueOf(scheme) }, + null, null, null); + + while (c.moveToNext()) { + colors[c.getInt(0)] = new Integer(c.getInt(1)); + } + + c.close(); + } + + return colors; + } + + public void setColorForScheme(int scheme, int number, int value) { + final SQLiteDatabase db; + + final String[] whereArgs = new String[] { String.valueOf(scheme), String.valueOf(number) }; + + if (value == Colors.defaults[number]) { + synchronized (dbLock) { + db = getWritableDatabase(); + + db.delete(TABLE_COLORS, + WHERE_SCHEME_AND_COLOR, whereArgs); + } + } else { + final ContentValues values = new ContentValues(); + values.put(FIELD_COLOR_VALUE, value); + + synchronized (dbLock) { + db = getWritableDatabase(); + + final int rowsAffected = db.update(TABLE_COLORS, values, + WHERE_SCHEME_AND_COLOR, whereArgs); + + if (rowsAffected == 0) { + values.put(FIELD_COLOR_SCHEME, scheme); + values.put(FIELD_COLOR_NUMBER, number); + db.insert(TABLE_COLORS, null, values); + } + } + } + } + + public void setGlobalColor(int number, int value) { + setColorForScheme(DEFAULT_COLOR_SCHEME, number, value); + } + + public int[] getDefaultColorsForScheme(int scheme) { + int[] colors = new int[] { DEFAULT_FG_COLOR, DEFAULT_BG_COLOR }; + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + + Cursor c = db.query(TABLE_COLOR_DEFAULTS, + new String[] { FIELD_COLOR_FG, FIELD_COLOR_BG }, + FIELD_COLOR_SCHEME + " = ?", + new String[] { String.valueOf(scheme) }, + null, null, null); + + if (c.moveToFirst()) { + colors[0] = c.getInt(0); + colors[1] = c.getInt(1); + } + + c.close(); + } + + return colors; + } + + public int[] getGlobalDefaultColors() { + return getDefaultColorsForScheme(DEFAULT_COLOR_SCHEME); + } + + public void setDefaultColorsForScheme(int scheme, int fg, int bg) { + SQLiteDatabase db; + + String schemeWhere = null; + String[] whereArgs; + + schemeWhere = FIELD_COLOR_SCHEME + " = ?"; + whereArgs = new String[] { String.valueOf(scheme) }; + + ContentValues values = new ContentValues(); + values.put(FIELD_COLOR_FG, fg); + values.put(FIELD_COLOR_BG, bg); + + synchronized (dbLock) { + db = getWritableDatabase(); + + int rowsAffected = db.update(TABLE_COLOR_DEFAULTS, values, + schemeWhere, whereArgs); + + if (rowsAffected == 0) { + values.put(FIELD_COLOR_SCHEME, scheme); + db.insert(TABLE_COLOR_DEFAULTS, null, values); + } + } + } +} diff --git a/app/src/main/java/org/connectbot/util/OnDbWrittenListener.java b/app/src/main/java/org/connectbot/util/OnDbWrittenListener.java new file mode 100644 index 0000000..ef33797 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/OnDbWrittenListener.java @@ -0,0 +1,26 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +/** + * @author kroot + * + */ +public interface OnDbWrittenListener { + public void onDbWritten(); +} diff --git a/app/src/main/java/org/connectbot/util/OnEntropyGatheredListener.java b/app/src/main/java/org/connectbot/util/OnEntropyGatheredListener.java new file mode 100644 index 0000000..5debd65 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/OnEntropyGatheredListener.java @@ -0,0 +1,22 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +public interface OnEntropyGatheredListener { + void onEntropyGathered(byte[] entropy); +} diff --git a/app/src/main/java/org/connectbot/util/PreferenceConstants.java b/app/src/main/java/org/connectbot/util/PreferenceConstants.java new file mode 100644 index 0000000..d3cd832 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/PreferenceConstants.java @@ -0,0 +1,91 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import android.os.Build; + +/** + * @author Kenny Root + * + */ +public class PreferenceConstants { + public static final int SDK_INT = Integer.parseInt(Build.VERSION.SDK); + public static final boolean PRE_ECLAIR = SDK_INT < 5; + public static final boolean PRE_FROYO = SDK_INT < 8; + public static final boolean PRE_HONEYCOMB = SDK_INT < 11; + + public static final String MEMKEYS = "memkeys"; + + public static final String LAST_CHECKED = "lastchecked"; + + public static final String SCROLLBACK = "scrollback"; + + public static final String EMULATION = "emulation"; + + public static final String ROTATION = "rotation"; + + public static final String ROTATION_DEFAULT = "Default"; + public static final String ROTATION_LANDSCAPE = "Force landscape"; + public static final String ROTATION_PORTRAIT = "Force portrait"; + public static final String ROTATION_AUTOMATIC = "Automatic"; + + public static final String FULLSCREEN = "fullscreen"; + public static final String TITLEBARHIDE = "titlebarhide"; + + public static final String KEYMODE = "keymode"; + + public static final String KEYMODE_RIGHT = "Use right-side keys"; + public static final String KEYMODE_LEFT = "Use left-side keys"; + public static final String KEYMODE_NONE = "none"; + + public static final String CAMERA = "camera"; + + public static final String CAMERA_CTRLA_SPACE = "Ctrl+A then Space"; + public static final String CAMERA_CTRLA = "Ctrl+A"; + public static final String CAMERA_ESC = "Esc"; + public static final String CAMERA_ESC_A = "Esc+A"; + + public static final String KEEP_ALIVE = "keepalive"; + + public static final String WIFI_LOCK = "wifilock"; + + public static final String BUMPY_ARROWS = "bumpyarrows"; + + public static final String EULA = "eula"; + + public static final String SORT_BY_COLOR = "sortByColor"; + + public static final String BELL = "bell"; + public static final String BELL_VOLUME = "bellVolume"; + public static final String BELL_VIBRATE = "bellVibrate"; + public static final String BELL_NOTIFICATION = "bellNotification"; + public static final float DEFAULT_BELL_VOLUME = 0.25f; + + public static final String CONNECTION_PERSIST = "connPersist"; + + public static final String SHIFT_FKEYS = "shiftfkeys"; + public static final String CTRL_FKEYS = "ctrlfkeys"; + public static final String VOLUME_FONT = "volumefont"; + public static final String STICKY_MODIFIERS = "stickymodifiers"; + public static final String YES = "yes"; + public static final String NO = "no"; + public static final String ALT = "alt"; + + /* Backup identifiers */ + public static final String BACKUP_PREF_KEY = "prefs"; +} diff --git a/app/src/main/java/org/connectbot/util/PubkeyDatabase.java b/app/src/main/java/org/connectbot/util/PubkeyDatabase.java new file mode 100644 index 0000000..a8993cb --- /dev/null +++ b/app/src/main/java/org/connectbot/util/PubkeyDatabase.java @@ -0,0 +1,329 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import java.util.LinkedList; +import java.util.List; + +import org.connectbot.bean.PubkeyBean; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; + +/** + * Public Key Encryption database. Contains private and public key pairs + * for public key authentication. + * + * @author Kenny Root + */ +public class PubkeyDatabase extends RobustSQLiteOpenHelper { + public final static String TAG = "ConnectBot.PubkeyDatabase"; + + public final static String DB_NAME = "pubkeys"; + public final static int DB_VERSION = 2; + + public final static String TABLE_PUBKEYS = "pubkeys"; + public final static String FIELD_PUBKEY_NICKNAME = "nickname"; + public final static String FIELD_PUBKEY_TYPE = "type"; + public final static String FIELD_PUBKEY_PRIVATE = "private"; + public final static String FIELD_PUBKEY_PUBLIC = "public"; + public final static String FIELD_PUBKEY_ENCRYPTED = "encrypted"; + public final static String FIELD_PUBKEY_STARTUP = "startup"; + public final static String FIELD_PUBKEY_CONFIRMUSE = "confirmuse"; + public final static String FIELD_PUBKEY_LIFETIME = "lifetime"; + + public final static String KEY_TYPE_RSA = "RSA", + KEY_TYPE_DSA = "DSA", + KEY_TYPE_IMPORTED = "IMPORTED", + KEY_TYPE_EC = "EC"; + + private Context context; + + static { + addTableName(TABLE_PUBKEYS); + } + + public PubkeyDatabase(Context context) { + super(context, DB_NAME, null, DB_VERSION); + + this.context = context; + } + + @Override + public void onCreate(SQLiteDatabase db) { + super.onCreate(db); + + db.execSQL("CREATE TABLE " + TABLE_PUBKEYS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_PUBKEY_NICKNAME + " TEXT, " + + FIELD_PUBKEY_TYPE + " TEXT, " + + FIELD_PUBKEY_PRIVATE + " BLOB, " + + FIELD_PUBKEY_PUBLIC + " BLOB, " + + FIELD_PUBKEY_ENCRYPTED + " INTEGER, " + + FIELD_PUBKEY_STARTUP + " INTEGER, " + + FIELD_PUBKEY_CONFIRMUSE + " INTEGER DEFAULT 0, " + + FIELD_PUBKEY_LIFETIME + " INTEGER DEFAULT 0)"); + } + + @Override + public void onRobustUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLiteException { + switch (oldVersion) { + case 1: + db.execSQL("ALTER TABLE " + TABLE_PUBKEYS + + " ADD COLUMN " + FIELD_PUBKEY_CONFIRMUSE + " INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE " + TABLE_PUBKEYS + + " ADD COLUMN " + FIELD_PUBKEY_LIFETIME + " INTEGER DEFAULT 0"); + } + } + + /** + * Delete a specific host by its <code>_id</code> value. + */ + public void deletePubkey(PubkeyBean pubkey) { + HostDatabase hostdb = new HostDatabase(context); + hostdb.stopUsingPubkey(pubkey.getId()); + hostdb.close(); + + SQLiteDatabase db = getWritableDatabase(); + db.delete(TABLE_PUBKEYS, "_id = ?", new String[] { Long.toString(pubkey.getId()) }); + db.close(); + } + + /** + * Return a cursor that contains information about all known hosts. + */ + /* + public Cursor allPubkeys() { + SQLiteDatabase db = this.getReadableDatabase(); + return db.query(TABLE_PUBKEYS, new String[] { "_id", + FIELD_PUBKEY_NICKNAME, FIELD_PUBKEY_TYPE, FIELD_PUBKEY_PRIVATE, + FIELD_PUBKEY_PUBLIC, FIELD_PUBKEY_ENCRYPTED, FIELD_PUBKEY_STARTUP }, + null, null, null, null, null); + }*/ + + public List<PubkeyBean> allPubkeys() { + return getPubkeys(null, null); + } + + public List<PubkeyBean> getAllStartPubkeys() { + return getPubkeys(FIELD_PUBKEY_STARTUP + " = 1 AND " + FIELD_PUBKEY_ENCRYPTED + " = 0", null); + } + + private List<PubkeyBean> getPubkeys(String selection, String[] selectionArgs) { + SQLiteDatabase db = getReadableDatabase(); + + List<PubkeyBean> pubkeys = new LinkedList<PubkeyBean>(); + + Cursor c = db.query(TABLE_PUBKEYS, null, selection, selectionArgs, null, null, null); + + if (c != null) { + final int COL_ID = c.getColumnIndexOrThrow("_id"), + COL_NICKNAME = c.getColumnIndexOrThrow(FIELD_PUBKEY_NICKNAME), + COL_TYPE = c.getColumnIndexOrThrow(FIELD_PUBKEY_TYPE), + COL_PRIVATE = c.getColumnIndexOrThrow(FIELD_PUBKEY_PRIVATE), + COL_PUBLIC = c.getColumnIndexOrThrow(FIELD_PUBKEY_PUBLIC), + COL_ENCRYPTED = c.getColumnIndexOrThrow(FIELD_PUBKEY_ENCRYPTED), + COL_STARTUP = c.getColumnIndexOrThrow(FIELD_PUBKEY_STARTUP), + COL_CONFIRMUSE = c.getColumnIndexOrThrow(FIELD_PUBKEY_CONFIRMUSE), + COL_LIFETIME = c.getColumnIndexOrThrow(FIELD_PUBKEY_LIFETIME); + + while (c.moveToNext()) { + PubkeyBean pubkey = new PubkeyBean(); + + pubkey.setId(c.getLong(COL_ID)); + pubkey.setNickname(c.getString(COL_NICKNAME)); + pubkey.setType(c.getString(COL_TYPE)); + pubkey.setPrivateKey(c.getBlob(COL_PRIVATE)); + pubkey.setPublicKey(c.getBlob(COL_PUBLIC)); + pubkey.setEncrypted(c.getInt(COL_ENCRYPTED) > 0); + pubkey.setStartup(c.getInt(COL_STARTUP) > 0); + pubkey.setConfirmUse(c.getInt(COL_CONFIRMUSE) > 0); + pubkey.setLifetime(c.getInt(COL_LIFETIME)); + + pubkeys.add(pubkey); + } + + c.close(); + } + + db.close(); + + return pubkeys; + } + + /** + * @param hostId + * @return + */ + public PubkeyBean findPubkeyById(long pubkeyId) { + SQLiteDatabase db = getReadableDatabase(); + + Cursor c = db.query(TABLE_PUBKEYS, null, + "_id = ?", new String[] { String.valueOf(pubkeyId) }, + null, null, null); + + PubkeyBean pubkey = null; + + if (c != null) { + if (c.moveToFirst()) + pubkey = createPubkeyBean(c); + + c.close(); + } + + db.close(); + + return pubkey; + } + + private PubkeyBean createPubkeyBean(Cursor c) { + PubkeyBean pubkey = new PubkeyBean(); + + pubkey.setId(c.getLong(c.getColumnIndexOrThrow("_id"))); + pubkey.setNickname(c.getString(c.getColumnIndexOrThrow(FIELD_PUBKEY_NICKNAME))); + pubkey.setType(c.getString(c.getColumnIndexOrThrow(FIELD_PUBKEY_TYPE))); + pubkey.setPrivateKey(c.getBlob(c.getColumnIndexOrThrow(FIELD_PUBKEY_PRIVATE))); + pubkey.setPublicKey(c.getBlob(c.getColumnIndexOrThrow(FIELD_PUBKEY_PUBLIC))); + pubkey.setEncrypted(c.getInt(c.getColumnIndexOrThrow(FIELD_PUBKEY_ENCRYPTED)) > 0); + pubkey.setStartup(c.getInt(c.getColumnIndexOrThrow(FIELD_PUBKEY_STARTUP)) > 0); + pubkey.setConfirmUse(c.getInt(c.getColumnIndexOrThrow(FIELD_PUBKEY_CONFIRMUSE)) > 0); + pubkey.setLifetime(c.getInt(c.getColumnIndexOrThrow(FIELD_PUBKEY_LIFETIME))); + + return pubkey; + } + + /** + * Pull all values for a given column as a list of Strings, probably for use + * in a ListPreference. Sorted by <code>_id</code> ascending. + */ + public List<CharSequence> allValues(String column) { + List<CharSequence> list = new LinkedList<CharSequence>(); + + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_PUBKEYS, new String[] { "_id", column }, + null, null, null, null, "_id ASC"); + + if (c != null) { + int COL = c.getColumnIndexOrThrow(column); + + while (c.moveToNext()) + list.add(c.getString(COL)); + + c.close(); + } + + db.close(); + + return list; + } + + public String getNickname(long id) { + String nickname = null; + + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_PUBKEYS, new String[] { "_id", + FIELD_PUBKEY_NICKNAME }, "_id = ?", + new String[] { Long.toString(id) }, null, null, null); + + if (c != null) { + if (c.moveToFirst()) + nickname = c.getString(c.getColumnIndexOrThrow(FIELD_PUBKEY_NICKNAME)); + + c.close(); + } + + db.close(); + + return nickname; + } + +/* + public void setOnStart(long id, boolean onStart) { + + SQLiteDatabase db = this.getWritableDatabase(); + + ContentValues values = new ContentValues(); + values.put(FIELD_PUBKEY_STARTUP, onStart ? 1 : 0); + + db.update(TABLE_PUBKEYS, values, "_id = ?", new String[] { Long.toString(id) }); + + } + + public boolean changePassword(long id, String oldPassword, String newPassword) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException { + SQLiteDatabase db = this.getWritableDatabase(); + + Cursor c = db.query(TABLE_PUBKEYS, new String[] { FIELD_PUBKEY_TYPE, + FIELD_PUBKEY_PRIVATE, FIELD_PUBKEY_ENCRYPTED }, + "_id = ?", new String[] { String.valueOf(id) }, + null, null, null); + + if (!c.moveToFirst()) + return false; + + String keyType = c.getString(0); + byte[] encPriv = c.getBlob(1); + c.close(); + + PrivateKey priv; + try { + priv = PubkeyUtils.decodePrivate(encPriv, keyType, oldPassword); + } catch (InvalidKeyException e) { + return false; + } catch (BadPaddingException e) { + return false; + } catch (InvalidKeySpecException e) { + return false; + } + + ContentValues values = new ContentValues(); + values.put(FIELD_PUBKEY_PRIVATE, PubkeyUtils.getEncodedPrivate(priv, newPassword)); + values.put(FIELD_PUBKEY_ENCRYPTED, newPassword.length() > 0 ? 1 : 0); + db.update(TABLE_PUBKEYS, values, "_id = ?", new String[] { String.valueOf(id) }); + + return true; + } + */ + + /** + * @param pubkey + */ + public PubkeyBean savePubkey(PubkeyBean pubkey) { + SQLiteDatabase db = this.getWritableDatabase(); + boolean success = false; + + ContentValues values = pubkey.getValues(); + + if (pubkey.getId() > 0) { + values.remove("_id"); + if (db.update(TABLE_PUBKEYS, values, "_id = ?", new String[] { String.valueOf(pubkey.getId()) }) > 0) + success = true; + } + + if (!success) { + long id = db.insert(TABLE_PUBKEYS, null, pubkey.getValues()); + pubkey.setId(id); + } + + db.close(); + + return pubkey; + } +} diff --git a/app/src/main/java/org/connectbot/util/PubkeyUtils.java b/app/src/main/java/org/connectbot/util/PubkeyUtils.java new file mode 100644 index 0000000..53f2481 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/PubkeyUtils.java @@ -0,0 +1,352 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.keyczar.jce.EcCore; + +import android.util.Log; + +import com.trilead.ssh2.crypto.Base64; +import com.trilead.ssh2.crypto.SimpleDERReader; +import com.trilead.ssh2.signature.DSASHA1Verify; +import com.trilead.ssh2.signature.ECDSASHA2Verify; +import com.trilead.ssh2.signature.RSASHA1Verify; + +public class PubkeyUtils { + private static final String TAG = "PubkeyUtils"; + + public static final String PKCS8_START = "-----BEGIN PRIVATE KEY-----"; + public static final String PKCS8_END = "-----END PRIVATE KEY-----"; + + // Size in bytes of salt to use. + private static final int SALT_SIZE = 8; + + // Number of iterations for password hashing. PKCS#5 recommends 1000 + private static final int ITERATIONS = 1000; + + // Cannot be instantiated + private PubkeyUtils() { + } + + public static String formatKey(Key key) { + String algo = key.getAlgorithm(); + String fmt = key.getFormat(); + byte[] encoded = key.getEncoded(); + return "Key[algorithm=" + algo + ", format=" + fmt + + ", bytes=" + encoded.length + "]"; + } + + public static byte[] sha256(byte[] data) throws NoSuchAlgorithmException { + return MessageDigest.getInstance("SHA-256").digest(data); + } + + public static byte[] cipher(int mode, byte[] data, byte[] secret) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + SecretKeySpec secretKeySpec = new SecretKeySpec(sha256(secret), "AES"); + Cipher c = Cipher.getInstance("AES"); + c.init(mode, secretKeySpec); + return c.doFinal(data); + } + + public static byte[] encrypt(byte[] cleartext, String secret) throws Exception { + byte[] salt = new byte[SALT_SIZE]; + + byte[] ciphertext = Encryptor.encrypt(salt, ITERATIONS, secret, cleartext); + + byte[] complete = new byte[salt.length + ciphertext.length]; + + System.arraycopy(salt, 0, complete, 0, salt.length); + System.arraycopy(ciphertext, 0, complete, salt.length, ciphertext.length); + + Arrays.fill(salt, (byte) 0x00); + Arrays.fill(ciphertext, (byte) 0x00); + + return complete; + } + + public static byte[] decrypt(byte[] saltAndCiphertext, String secret) throws Exception { + try { + byte[] salt = new byte[SALT_SIZE]; + byte[] ciphertext = new byte[saltAndCiphertext.length - salt.length]; + + System.arraycopy(saltAndCiphertext, 0, salt, 0, salt.length); + System.arraycopy(saltAndCiphertext, salt.length, ciphertext, 0, ciphertext.length); + + return Encryptor.decrypt(salt, ITERATIONS, secret, ciphertext); + } catch (Exception e) { + Log.d("decrypt", "Could not decrypt with new method", e); + // We might be using the old encryption method. + return cipher(Cipher.DECRYPT_MODE, saltAndCiphertext, secret.getBytes()); + } + } + + public static byte[] getEncodedPrivate(PrivateKey pk, String secret) throws Exception { + final byte[] encoded = pk.getEncoded(); + if (secret == null || secret.length() == 0) { + return encoded; + } + return encrypt(pk.getEncoded(), secret); + } + + public static PrivateKey decodePrivate(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException { + PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded); + KeyFactory kf = KeyFactory.getInstance(keyType); + return kf.generatePrivate(privKeySpec); + } + + public static PrivateKey decodePrivate(byte[] encoded, String keyType, String secret) throws Exception { + if (secret != null && secret.length() > 0) + return decodePrivate(decrypt(encoded, secret), keyType); + else + return decodePrivate(encoded, keyType); + } + + public static PublicKey decodePublic(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException { + X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encoded); + KeyFactory kf = KeyFactory.getInstance(keyType); + return kf.generatePublic(pubKeySpec); + } + + static String getAlgorithmForOid(String oid) throws NoSuchAlgorithmException { + if ("1.2.840.10045.2.1".equals(oid)) { + return "EC"; + } else if ("1.2.840.113549.1.1.1".equals(oid)) { + return "RSA"; + } else if ("1.2.840.10040.4.1".equals(oid)) { + return "DSA"; + } else { + throw new NoSuchAlgorithmException("Unknown algorithm OID " + oid); + } + } + + static String getOidFromPkcs8Encoded(byte[] encoded) throws NoSuchAlgorithmException { + if (encoded == null) { + throw new NoSuchAlgorithmException("encoding is null"); + } + + try { + SimpleDERReader reader = new SimpleDERReader(encoded); + reader.resetInput(reader.readSequenceAsByteArray()); + reader.readInt(); + reader.resetInput(reader.readSequenceAsByteArray()); + return reader.readOid(); + } catch (IOException e) { + Log.w(TAG, "Could not read OID", e); + throw new NoSuchAlgorithmException("Could not read key", e); + } + } + + public static KeyPair recoverKeyPair(byte[] encoded) throws NoSuchAlgorithmException, + InvalidKeySpecException { + final String algo = getAlgorithmForOid(getOidFromPkcs8Encoded(encoded)); + + final KeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded); + + final KeyFactory kf = KeyFactory.getInstance(algo); + final PrivateKey priv = kf.generatePrivate(privKeySpec); + + return new KeyPair(recoverPublicKey(kf, priv), priv); + } + + static PublicKey recoverPublicKey(KeyFactory kf, PrivateKey priv) + throws NoSuchAlgorithmException, InvalidKeySpecException { + if (priv instanceof RSAPrivateCrtKey) { + RSAPrivateCrtKey rsaPriv = (RSAPrivateCrtKey) priv; + return kf.generatePublic(new RSAPublicKeySpec(rsaPriv.getModulus(), rsaPriv + .getPublicExponent())); + } else if (priv instanceof DSAPrivateKey) { + DSAPrivateKey dsaPriv = (DSAPrivateKey) priv; + DSAParams params = dsaPriv.getParams(); + + // Calculate public key Y + BigInteger y = params.getG().modPow(dsaPriv.getX(), params.getP()); + + return kf.generatePublic(new DSAPublicKeySpec(y, params.getP(), params.getQ(), params + .getG())); + } else if (priv instanceof ECPrivateKey) { + ECPrivateKey ecPriv = (ECPrivateKey) priv; + ECParameterSpec params = ecPriv.getParams(); + + // Calculate public key Y + ECPoint generator = params.getGenerator(); + BigInteger[] wCoords = EcCore.multiplyPointA(new BigInteger[] { generator.getAffineX(), + generator.getAffineY() }, ecPriv.getS(), params); + ECPoint w = new ECPoint(wCoords[0], wCoords[1]); + + return kf.generatePublic(new ECPublicKeySpec(w, params)); + } else { + throw new NoSuchAlgorithmException("Key type must be RSA, DSA, or EC"); + } + } + + /* + * OpenSSH compatibility methods + */ + + public static String convertToOpenSSHFormat(PublicKey pk, String origNickname) throws IOException, InvalidKeyException { + String nickname = origNickname; + if (nickname == null) + nickname = "connectbot@android"; + + if (pk instanceof RSAPublicKey) { + String data = "ssh-rsa "; + data += String.valueOf(Base64.encode(RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pk))); + return data + " " + nickname; + } else if (pk instanceof DSAPublicKey) { + String data = "ssh-dss "; + data += String.valueOf(Base64.encode(DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pk))); + return data + " " + nickname; + } else if (pk instanceof ECPublicKey) { + ECPublicKey ecPub = (ECPublicKey) pk; + String keyType = ECDSASHA2Verify.getCurveName(ecPub.getParams().getCurve().getField().getFieldSize()); + String keyData = String.valueOf(Base64.encode(ECDSASHA2Verify.encodeSSHECDSAPublicKey(ecPub))); + return ECDSASHA2Verify.ECDSA_SHA2_PREFIX + keyType + " " + keyData + " " + nickname; + } + + throw new InvalidKeyException("Unknown key type"); + } + + /* + * OpenSSH compatibility methods + */ + + /** + * @param trileadKey + * @return OpenSSH-encoded pubkey + */ + public static byte[] extractOpenSSHPublic(KeyPair pair) { + try { + PublicKey pubKey = pair.getPublic(); + if (pubKey instanceof RSAPublicKey) { + return RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pair.getPublic()); + } else if (pubKey instanceof DSAPublicKey) { + return DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pair.getPublic()); + } else if (pubKey instanceof ECPublicKey) { + return ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey) pair.getPublic()); + } else { + return null; + } + } catch (IOException e) { + return null; + } + } + + public static String exportPEM(PrivateKey key, String secret) throws NoSuchAlgorithmException, InvalidParameterSpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, InvalidKeySpecException, IllegalBlockSizeException, IOException { + StringBuilder sb = new StringBuilder(); + + byte[] data = key.getEncoded(); + + sb.append(PKCS8_START); + sb.append('\n'); + + if (secret != null) { + byte[] salt = new byte[8]; + SecureRandom random = new SecureRandom(); + random.nextBytes(salt); + + PBEParameterSpec defParams = new PBEParameterSpec(salt, 1); + AlgorithmParameters params = AlgorithmParameters.getInstance(key.getAlgorithm()); + + params.init(defParams); + + PBEKeySpec pbeSpec = new PBEKeySpec(secret.toCharArray()); + + SecretKeyFactory keyFact = SecretKeyFactory.getInstance(key.getAlgorithm()); + Cipher cipher = Cipher.getInstance(key.getAlgorithm()); + cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), params); + + byte[] wrappedKey = cipher.wrap(key); + + EncryptedPrivateKeyInfo pinfo = new EncryptedPrivateKeyInfo(params, wrappedKey); + + data = pinfo.getEncoded(); + + sb.append("Proc-Type: 4,ENCRYPTED\n"); + sb.append("DEK-Info: DES-EDE3-CBC,"); + sb.append(encodeHex(salt)); + sb.append("\n\n"); + } + + int i = sb.length(); + sb.append(Base64.encode(data)); + for (i += 63; i < sb.length(); i += 64) { + sb.insert(i, "\n"); + } + + sb.append('\n'); + sb.append(PKCS8_END); + sb.append('\n'); + + return sb.toString(); + } + + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + protected static String encodeHex(byte[] bytes) { + final char[] hex = new char[bytes.length * 2]; + + int i = 0; + for (byte b : bytes) { + hex[i++] = HEX_DIGITS[(b >> 4) & 0x0f]; + hex[i++] = HEX_DIGITS[b & 0x0f]; + } + + return String.valueOf(hex); + } +} diff --git a/app/src/main/java/org/connectbot/util/RobustSQLiteOpenHelper.java b/app/src/main/java/org/connectbot/util/RobustSQLiteOpenHelper.java new file mode 100644 index 0000000..abdd991 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/RobustSQLiteOpenHelper.java @@ -0,0 +1,133 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import java.util.LinkedList; +import java.util.List; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteDatabase.CursorFactory; + +/** + * @author Kenny Root + * + */ +public abstract class RobustSQLiteOpenHelper extends SQLiteOpenHelper { + private static List<String> mTableNames = new LinkedList<String>(); + private static List<String> mIndexNames = new LinkedList<String>(); + + public RobustSQLiteOpenHelper(Context context, String name, + CursorFactory factory, int version) { + super(context, name, factory, version); + } + + protected static void addTableName(String tableName) { + mTableNames.add(tableName); + } + + protected static void addIndexName(String indexName) { + mIndexNames.add(indexName); + } + + @Override + public void onCreate(SQLiteDatabase db) { + dropAllTables(db); + } + + @Override + public final void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + try { + onRobustUpgrade(db, oldVersion, newVersion); + } catch (SQLiteException e) { + // The database has entered an unknown state. Try to recover. + try { + regenerateTables(db); + } catch (SQLiteException e2) { + dropAndCreateTables(db); + } + } + } + + public abstract void onRobustUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLiteException; + + private void regenerateTables(SQLiteDatabase db) { + dropAllTablesWithPrefix(db, "OLD_"); + + for (String tableName : mTableNames) + db.execSQL("ALTER TABLE " + tableName + " RENAME TO OLD_" + + tableName); + + onCreate(db); + + for (String tableName : mTableNames) + repopulateTable(db, tableName); + + dropAllTablesWithPrefix(db, "OLD_"); + } + + private void repopulateTable(SQLiteDatabase db, String tableName) { + String columns = getTableColumnNames(db, tableName); + + StringBuilder sb = new StringBuilder(); + sb.append("INSERT INTO ") + .append(tableName) + .append(" (") + .append(columns) + .append(") SELECT ") + .append(columns) + .append(" FROM OLD_") + .append(tableName); + + String sql = sb.toString(); + db.execSQL(sql); + } + + private String getTableColumnNames(SQLiteDatabase db, String tableName) { + StringBuilder sb = new StringBuilder(); + + Cursor fields = db.rawQuery("PRAGMA table_info(" + tableName + ")", null); + while (fields.moveToNext()) { + if (!fields.isFirst()) + sb.append(", "); + sb.append(fields.getString(1)); + } + fields.close(); + + return sb.toString(); + } + + private void dropAndCreateTables(SQLiteDatabase db) { + dropAllTables(db); + onCreate(db); + } + + private void dropAllTablesWithPrefix(SQLiteDatabase db, String prefix) { + for (String indexName : mIndexNames) + db.execSQL("DROP INDEX IF EXISTS " + prefix + indexName); + for (String tableName : mTableNames) + db.execSQL("DROP TABLE IF EXISTS " + prefix + tableName); + } + + private void dropAllTables(SQLiteDatabase db) { + dropAllTablesWithPrefix(db, ""); + } +} diff --git a/app/src/main/java/org/connectbot/util/UberColorPickerDialog.java b/app/src/main/java/org/connectbot/util/UberColorPickerDialog.java new file mode 100644 index 0000000..dc46525 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/UberColorPickerDialog.java @@ -0,0 +1,1020 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * 090408 + * Keith Wiley + * kwiley@keithwiley.com + * http://keithwiley.com + * + * UberColorPickerDialog v1.1 + * + * This color picker was implemented as a (significant) extension of the + * ColorPickerDialog class provided in the Android API Demos. You are free + * to drop it unchanged into your own projects or to modify it as you see + * fit. I would appreciate it if this comment block were let intact, + * merely for credit's sake. + * + * Enjoy! + */ + +package org.connectbot.util; + +import org.connectbot.R; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ComposeShader; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RadialGradient; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.SweepGradient; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.GradientDrawable.Orientation; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; + +/** + * UberColorPickerDialog is a seriously enhanced version of the UberColorPickerDialog + * class provided in the Android API Demos.<p> + * <p/> + * NOTE (from Kenny Root): This is a VERY slimmed down version custom for ConnectBot. + * Visit Keith's site for the full version at the URL listed in the author line.<p> + * + * @author Keith Wiley, kwiley@keithwiley.com, http://keithwiley.com + */ +public class UberColorPickerDialog extends Dialog { + private final OnColorChangedListener mListener; + private final int mInitialColor; + + /** + * Callback to the creator of the dialog, informing the creator of a new color and notifying that the dialog is about to dismiss. + */ + public interface OnColorChangedListener { + void colorChanged(int color); + } + + /** + * Ctor + * + * @param context + * @param listener + * @param initialColor + */ + public UberColorPickerDialog(Context context, + OnColorChangedListener listener, + int initialColor) { + super(context); + + mListener = listener; + mInitialColor = initialColor; + } + + /** + * Activity entry point + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + OnColorChangedListener l = new OnColorChangedListener() { + public void colorChanged(int color) { + mListener.colorChanged(color); + dismiss(); + } + }; + + DisplayMetrics dm = new DisplayMetrics(); + getWindow().getWindowManager().getDefaultDisplay().getMetrics(dm); + int screenWidth = dm.widthPixels; + int screenHeight = dm.heightPixels; + + setTitle(getContext().getResources().getString(R.string.title_color_picker)); + + try { + setContentView(new ColorPickerView(getContext(), l, screenWidth, screenHeight, mInitialColor)); + } catch (Exception e) { + //There is currently only one kind of ctor exception, that where no methods are enabled. + dismiss(); //This doesn't work! The dialog is still shown (its title at least, the layout is empty from the exception being thrown). <sigh> + } + } + + /** + * ColorPickerView is the meat of this color picker (as opposed to the enclosing class). + * All the heavy lifting is done directly by this View subclass. + * <p/> + * You can enable/disable whichever color chooser methods you want by modifying the ENABLED_METHODS switches. They *should* + * do all the work required to properly enable/disable methods without losing track of what goes with what and what maps to what. + * <p/> + * If you add a new color chooser method, do a text search for "NEW_METHOD_WORK_NEEDED_HERE". That tag indicates all + * the locations in the code that will have to be amended in order to properly add a new color chooser method. + * I highly recommend adding new methods to the end of the list. If you want to try to reorder the list, you're on your own. + */ + private static class ColorPickerView extends View { + private static int SWATCH_WIDTH_PORTRAIT_DP = 95; + private static int SWATCH_WIDTH_LANDSCAPE_DP = 110; + private static final int SWATCH_HEIGHT_DP = 60; + + private static final int PALETTE_DIM_DP = SWATCH_WIDTH_PORTRAIT_DP * 2; + + private static final int SLIDER_THICKNESS_DP = 40; + + //NEW_METHOD_WORK_NEEDED_HERE + private static final int METHOD_HS_V_PALETTE = 0; + + //NEW_METHOD_WORK_NEEDED_HERE + //Add a new entry to the list for each controller in the new method + private static final int TRACKED_NONE = -1; //No object on screen is currently being tracked + private static final int TRACK_SWATCH_OLD = 10; + private static final int TRACK_SWATCH_NEW = 11; + private static final int TRACK_HS_PALETTE = 30; + private static final int TRACK_VER_VALUE_SLIDER = 31; + + private static final int TEXT_SIZE_DP = 12; + private static final int TEXT_SIZE_LABEL_DP = 12; + + private static final int BUTTON_TEXT_MARGIN_DP = 16; + + private static int[] TEXT_HSV_POS = new int[2]; + private static int[] TEXT_RGB_POS = new int[2]; + private static int[] TEXT_YUV_POS = new int[2]; + private static int[] TEXT_HEX_POS = new int[2]; + + private static final float PI = 3.141592653589793f; + + private final int mSwatchWidthPx; + private final int mTextSizePx; + private final int mTextSizeLabelPx; + private final int mPalettePosX; + private final int mPalettePosY; + private final int mPaletteDimPx; + private final int mPaletteRadiusPx; + private final int mSliderThicknessPx; + private final int mViewDimXPx; + private final int mViewDimYPx; + private final int mPaletteCenterPx; + private final int mButtonTextMarginPx; + + private int mMethod = METHOD_HS_V_PALETTE; + private int mTracking = TRACKED_NONE; //What object on screen is currently being tracked for movement + + //Zillions of persistant Paint objecs for drawing the View + + private Paint mSwatchOld, mSwatchNew; + + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the palettes of the new method's UI controllers + private Paint mOvalHueSat; + + private Bitmap mVerSliderBM; + private Canvas mVerSliderCv; + + private Bitmap[] mHorSlidersBM = new Bitmap[3]; + private Canvas[] mHorSlidersCv = new Canvas[3]; + + private Paint mValDimmer; + + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the icon for the new method + private Paint mOvalHueSatSmall; + + private Paint mPosMarker; + private Paint mText; + + private Rect mOldSwatchRect = new Rect(); + private Rect mNewSwatchRect = new Rect(); + private Rect mPaletteRect = new Rect(); + private Rect mVerSliderRect = new Rect(); + + private int[] mSpectrumColorsRev; + private int mOriginalColor = 0; //The color passed in at the beginning, which can be reverted to at any time by tapping the old swatch. + private float[] mHSV = new float[3]; + private int[] mRGB = new int[3]; + private float[] mYUV = new float[3]; + private String mHexStr = ""; + private boolean mHSVenabled = true; //Only true if an HSV method is enabled + private boolean mRGBenabled = true; //Only true if an RGB method is enabled + private boolean mYUVenabled = true; //Only true if a YUV method is enabled + private boolean mHexenabled = true; //Only true if an RGB method is enabled + private int[] mCoord = new int[3]; //For drawing slider/palette markers + private int mFocusedControl = -1; //Which control receives trackball events. + private OnColorChangedListener mListener; + + /** + * Ctor. + * + * @param c + * @param l + * @param width Used to determine orientation and adjust layout accordingly + * @param height Used to determine orientation and adjust layout accordingly + * @param color The initial color + * @throws Exception + */ + ColorPickerView(Context c, OnColorChangedListener l, int width, int height, int color) + throws Exception { + super(c); + + DisplayMetrics metrics = c.getResources().getDisplayMetrics(); + mTextSizePx = (int) (TEXT_SIZE_DP * metrics.density + 0.5f); + mTextSizeLabelPx = (int) (TEXT_SIZE_LABEL_DP * metrics.density + 0.5f); + + //We need to make the dialog focusable to retrieve trackball events. + setFocusable(true); + + mListener = l; + + mOriginalColor = color; + + Color.colorToHSV(color, mHSV); + + updateAllFromHSV(); + + mPaletteDimPx = (int) (PALETTE_DIM_DP * metrics.density + 0.5f); + mSliderThicknessPx = (int) (SLIDER_THICKNESS_DP * metrics.density + 0.5f); + mButtonTextMarginPx = (int) (BUTTON_TEXT_MARGIN_DP * metrics.density + 0.5f); + + //Setup the layout based on whether this is a portrait or landscape orientation. + if (width <= height) { //Portrait layout + mSwatchWidthPx = (int) (((PALETTE_DIM_DP + SLIDER_THICKNESS_DP) / 2) * metrics.density + 0.5f); + final int swatchHeightPx = (int) (SWATCH_HEIGHT_DP * metrics.density + 0.5f); + + mPalettePosX = 0; + mPalettePosY = mTextSizePx * 4 + swatchHeightPx; + + //Set more rects, lots of rects + mOldSwatchRect.set(0, mTextSizePx * 4, mSwatchWidthPx, mTextSizePx * 4 + swatchHeightPx); + mNewSwatchRect.set(mSwatchWidthPx, mTextSizePx * 4, mSwatchWidthPx * 2, mTextSizePx * 4 + swatchHeightPx); + mPaletteRect.set(0, mPalettePosY, mPaletteDimPx, mPalettePosY + mPaletteDimPx); + mVerSliderRect.set(mPaletteDimPx, mPalettePosY, mPaletteDimPx + mSliderThicknessPx, mPalettePosY + mPaletteDimPx); + + TEXT_HSV_POS[0] = (int) (3 * metrics.density + 0.5f); + TEXT_HSV_POS[1] = 0; + TEXT_RGB_POS[0] = TEXT_HSV_POS[0] + (int) (50 * metrics.density + 0.5f); + TEXT_RGB_POS[1] = TEXT_HSV_POS[1]; + TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + (int) (100 * metrics.density + 0.5f); + TEXT_YUV_POS[1] = TEXT_HSV_POS[1]; + TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + (int) (150 * metrics.density + 0.5f); + TEXT_HEX_POS[1] = TEXT_HSV_POS[1]; + + mViewDimXPx = mPaletteDimPx + mSliderThicknessPx; + mViewDimYPx = swatchHeightPx + mPaletteDimPx + mTextSizePx * 4; + } else { //Landscape layout + mSwatchWidthPx = (int) (SWATCH_WIDTH_LANDSCAPE_DP * metrics.density + 0.5f); + final int swatchHeightPx = (int) (SWATCH_HEIGHT_DP * metrics.density + 0.5f); + + mPalettePosX = mSwatchWidthPx; + mPalettePosY = 0; + + //Set more rects, lots of rects + mOldSwatchRect.set(0, mTextSizePx * 7, mSwatchWidthPx, mTextSizePx * 7 + swatchHeightPx); + mNewSwatchRect.set(0, mTextSizePx * 7 + mSliderThicknessPx, mSwatchWidthPx, mTextSizePx * 7 + swatchHeightPx * 2); + mPaletteRect.set(mSwatchWidthPx, mPalettePosY, mSwatchWidthPx + mPaletteDimPx, mPalettePosY + mPaletteDimPx); + mVerSliderRect.set(mSwatchWidthPx + mPaletteDimPx, mPalettePosY, mSwatchWidthPx + mPaletteDimPx + mSliderThicknessPx, mPalettePosY + mPaletteDimPx); + + TEXT_HSV_POS[0] = (int) (3 * metrics.density + 0.5f); + TEXT_HSV_POS[1] = 0; + TEXT_RGB_POS[0] = TEXT_HSV_POS[0]; + TEXT_RGB_POS[1] = (int) (TEXT_HSV_POS[1] + mTextSizePx * 3.5); + TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + (int) (50 * metrics.density + 0.5f); + TEXT_YUV_POS[1] = (int) (TEXT_HSV_POS[1] + mTextSizePx * 3.5); + TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + (int) (50 * metrics.density + 0.5f); + TEXT_HEX_POS[1] = TEXT_HSV_POS[1]; + + mViewDimXPx = mPalettePosX + mPaletteDimPx + mSliderThicknessPx; + mViewDimYPx = Math.max(mNewSwatchRect.bottom, mPaletteDimPx); + } + + mPaletteCenterPx = mPaletteDimPx / 2; + mPaletteRadiusPx = mPaletteDimPx / 2; + + //Rainbows make everybody happy! + mSpectrumColorsRev = new int[]{ + 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, + 0xFF00FF00, 0xFFFFFF00, 0xFFFF0000, + }; + + //Setup all the Paint and Shader objects. There are lots of them! + + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the palettes of the new method's UI controllers + + mSwatchOld = new Paint(Paint.ANTI_ALIAS_FLAG); + mSwatchOld.setStyle(Paint.Style.FILL); + mSwatchOld.setColor(Color.HSVToColor(mHSV)); + + mSwatchNew = new Paint(Paint.ANTI_ALIAS_FLAG); + mSwatchNew.setStyle(Paint.Style.FILL); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + Shader shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); + Shader shaderB = new RadialGradient(0, 0, mPaletteCenterPx, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); + Shader shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN); + mOvalHueSat = new Paint(Paint.ANTI_ALIAS_FLAG); + mOvalHueSat.setShader(shader); + mOvalHueSat.setStyle(Paint.Style.FILL); + mOvalHueSat.setDither(true); + + mVerSliderBM = Bitmap.createBitmap(mSliderThicknessPx, mPaletteDimPx, Bitmap.Config.RGB_565); + mVerSliderCv = new Canvas(mVerSliderBM); + + for (int i = 0; i < 3; i++) { + mHorSlidersBM[i] = Bitmap.createBitmap(mPaletteDimPx, mSliderThicknessPx, Bitmap.Config.RGB_565); + mHorSlidersCv[i] = new Canvas(mHorSlidersBM[i]); + } + + mValDimmer = new Paint(Paint.ANTI_ALIAS_FLAG); + mValDimmer.setStyle(Paint.Style.FILL); + mValDimmer.setDither(true); + mValDimmer.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); + + //Whew, we're done making the big Paints and Shaders for the swatches, palettes, and sliders. + //Now we need to make the Paints and Shaders that will draw the little method icons in the method selector list. + + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the icon for the new method + + shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); + shaderB = new RadialGradient(0, 0, mPaletteDimPx / 2, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); + shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN); + mOvalHueSatSmall = new Paint(Paint.ANTI_ALIAS_FLAG); + mOvalHueSatSmall.setShader(shader); + mOvalHueSatSmall.setStyle(Paint.Style.FILL); + + //Make a simple stroking Paint for drawing markers and borders and stuff like that. + mPosMarker = new Paint(Paint.ANTI_ALIAS_FLAG); + mPosMarker.setStyle(Paint.Style.STROKE); + mPosMarker.setStrokeWidth(2); + + //Make a basic text Paint. + mText = new Paint(Paint.ANTI_ALIAS_FLAG); + mText.setTextSize(mTextSizePx); + mText.setColor(Color.WHITE); + + //Kickstart + initUI(); + } + + /** + * Draw the entire view (the entire dialog). + */ + @Override + protected void onDraw(Canvas canvas) { + //Draw the old and new swatches + drawSwatches(canvas); + + //Write the text + writeColorParams(canvas); + + //Draw the palette and sliders (the UI) + if (mMethod == METHOD_HS_V_PALETTE) + drawHSV1Palette(canvas); + } + + /** + * Draw the old and new swatches. + * + * @param canvas + */ + private void drawSwatches(Canvas canvas) { + float[] hsv = new float[3]; + + mText.setTextSize(mTextSizePx); + + //Draw the original swatch + canvas.drawRect(mOldSwatchRect, mSwatchOld); + Color.colorToHSV(mOriginalColor, hsv); + //if (UberColorPickerDialog.isGray(mColor)) //Don't need this right here, but imp't to note + // hsv[1] = 0; + if (hsv[2] > .5) + mText.setColor(Color.BLACK); + canvas.drawText("Revert", mOldSwatchRect.left + mSwatchWidthPx / 2 - mText.measureText("Revert") / 2, mOldSwatchRect.top + mButtonTextMarginPx, mText); + mText.setColor(Color.WHITE); + + //Draw the new swatch + canvas.drawRect(mNewSwatchRect, mSwatchNew); + if (mHSV[2] > .5) + mText.setColor(Color.BLACK); + canvas.drawText("Accept", mNewSwatchRect.left + mSwatchWidthPx / 2 - mText.measureText("Accept") / 2, mNewSwatchRect.top + mButtonTextMarginPx, mText); + mText.setColor(Color.WHITE); + + mText.setTextSize(mTextSizePx); + } + + /** + * Write the color parametes (HSV, RGB, YUV, Hex, etc.). + * + * @param canvas + */ + private void writeColorParams(Canvas canvas) { + if (mHSVenabled) { + canvas.drawText("H: " + Integer.toString((int) (mHSV[0] / 360.0f * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + mTextSizePx, mText); + canvas.drawText("S: " + Integer.toString((int) (mHSV[1] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + mTextSizePx * 2, mText); + canvas.drawText("V: " + Integer.toString((int) (mHSV[2] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + mTextSizePx * 3, mText); + } + + if (mRGBenabled) { + canvas.drawText("R: " + mRGB[0], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + mTextSizePx, mText); + canvas.drawText("G: " + mRGB[1], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + mTextSizePx * 2, mText); + canvas.drawText("B: " + mRGB[2], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + mTextSizePx * 3, mText); + } + + if (mYUVenabled) { + canvas.drawText("Y: " + Integer.toString((int) (mYUV[0] * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + mTextSizePx, mText); + canvas.drawText("U: " + Integer.toString((int) ((mYUV[1] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + mTextSizePx * 2, mText); + canvas.drawText("V: " + Integer.toString((int) ((mYUV[2] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + mTextSizePx * 3, mText); + } + + if (mHexenabled) + canvas.drawText("#" + mHexStr, TEXT_HEX_POS[0], TEXT_HEX_POS[1] + mTextSizePx, mText); + } + + /** + * Place a small circle on the 2D palette to indicate the current values. + * + * @param canvas + * @param markerPosX + * @param markerPosY + */ + private void mark2DPalette(Canvas canvas, int markerPosX, int markerPosY) { + mPosMarker.setColor(Color.BLACK); + canvas.drawOval(new RectF(markerPosX - 5, markerPosY - 5, markerPosX + 5, markerPosY + 5), mPosMarker); + mPosMarker.setColor(Color.WHITE); + canvas.drawOval(new RectF(markerPosX - 3, markerPosY - 3, markerPosX + 3, markerPosY + 3), mPosMarker); + } + + /** + * Draw a line across the slider to indicate its current value. + * + * @param canvas + * @param markerPos + */ + private void markVerSlider(Canvas canvas, int markerPos) { + mPosMarker.setColor(Color.BLACK); + canvas.drawRect(new Rect(0, markerPos - 2, mSliderThicknessPx, markerPos + 3), mPosMarker); + mPosMarker.setColor(Color.WHITE); + canvas.drawRect(new Rect(0, markerPos, mSliderThicknessPx, markerPos + 1), mPosMarker); + } + + /** + * Frame the slider to indicate that it has trackball focus. + * + * @param canvas + */ + private void hilightFocusedVerSlider(Canvas canvas) { + mPosMarker.setColor(Color.WHITE); + canvas.drawRect(new Rect(0, 0, mSliderThicknessPx, mPaletteDimPx), mPosMarker); + mPosMarker.setColor(Color.BLACK); + canvas.drawRect(new Rect(2, 2, mSliderThicknessPx - 2, mPaletteDimPx - 2), mPosMarker); + } + + /** + * Frame the 2D palette to indicate that it has trackball focus. + * + * @param canvas + */ + private void hilightFocusedOvalPalette(Canvas canvas) { + mPosMarker.setColor(Color.WHITE); + canvas.drawOval(new RectF(-mPaletteRadiusPx, -mPaletteRadiusPx, mPaletteRadiusPx, mPaletteRadiusPx), mPosMarker); + mPosMarker.setColor(Color.BLACK); + canvas.drawOval(new RectF(-mPaletteRadiusPx + 2, -mPaletteRadiusPx + 2, mPaletteRadiusPx - 2, mPaletteRadiusPx - 2), mPosMarker); + } + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate the basic draw functions here. Use the 2D palette or 1D sliders as templates for the new method. + + /** + * Draw the UI for HSV with angular H and radial S combined in 2D and a 1D V slider. + * + * @param canvas + */ + private void drawHSV1Palette(Canvas canvas) { + canvas.save(); + + canvas.translate(mPalettePosX, mPalettePosY); + + //Draw the 2D palette + canvas.translate(mPaletteCenterPx, mPaletteCenterPx); + canvas.drawOval(new RectF(-mPaletteRadiusPx, -mPaletteRadiusPx, mPaletteRadiusPx, mPaletteRadiusPx), mOvalHueSat); + canvas.drawOval(new RectF(-mPaletteRadiusPx, -mPaletteRadiusPx, mPaletteRadiusPx, mPaletteRadiusPx), mValDimmer); + if (mFocusedControl == 0) + hilightFocusedOvalPalette(canvas); + mark2DPalette(canvas, mCoord[0], mCoord[1]); + canvas.translate(-mPaletteCenterPx, -mPaletteCenterPx); + + //Draw the 1D slider + canvas.translate(mPaletteDimPx, 0); + canvas.drawBitmap(mVerSliderBM, 0, 0, null); + if (mFocusedControl == 1) + hilightFocusedVerSlider(canvas); + markVerSlider(canvas, mCoord[2]); + + canvas.restore(); + } + + /** + * Initialize the current color chooser's UI (set its color parameters and set its palette and slider values accordingly). + */ + private void initUI() { + initHSV1Palette(); + + //Focus on the first controller (arbitrary). + mFocusedControl = 0; + } + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last init function shown below + + /** + * Initialize a color chooser. + */ + private void initHSV1Palette() { + setOvalValDimmer(); + setVerValSlider(); + + float angle = 2 * PI - mHSV[0] / (180 / 3.1415927f); + float radius = mHSV[1] * mPaletteRadiusPx; + mCoord[0] = (int) (Math.cos(angle) * radius); + mCoord[1] = (int) (Math.sin(angle) * radius); + + mCoord[2] = mPaletteDimPx - (int) (mHSV[2] * mPaletteDimPx); + } + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the set functions below, one per UI controller in the new method + + /** + * Adjust a Paint which, when painted, dims its underlying object to show the effects of varying value (brightness). + */ + private void setOvalValDimmer() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = 0; + hsv[2] = mHSV[2]; + int gray = Color.HSVToColor(hsv); + mValDimmer.setColor(gray); + } + + /** + * Create a linear gradient shader to show variations in value. + */ + private void setVerValSlider() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = mHSV[1]; + hsv[2] = 1; + int col = Color.HSVToColor(hsv); + + int colors[] = new int[2]; + colors[0] = col; + colors[1] = 0xFF000000; + GradientDrawable gradDraw = new GradientDrawable(Orientation.TOP_BOTTOM, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, mSliderThicknessPx, mPaletteDimPx); + gradDraw.draw(mVerSliderCv); + } + + /** + * Report the correct tightly bounded dimensions of the view. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(mViewDimXPx, mViewDimYPx); + } + + /** + * Wrap Math.round(). I'm not a Java expert. Is this the only way to avoid writing "(int)Math.round" everywhere? + * + * @param x + * @return + */ + private int round(double x) { + return (int) Math.round(x); + } + + /** + * Limit a value to the range [0,1]. + * + * @param n + * @return + */ + private float pinToUnit(float n) { + if (n < 0) { + n = 0; + } else if (n > 1) { + n = 1; + } + return n; + } + + /** + * Limit a value to the range [0,max]. + * + * @param n + * @param max + * @return + */ + private float pin(float n, float max) { + if (n < 0) { + n = 0; + } else if (n > max) { + n = max; + } + return n; + } + + /** + * Limit a value to the range [min,max]. + * + * @param n + * @param min + * @param max + * @return + */ + private float pin(float n, float min, float max) { + if (n < min) { + n = min; + } else if (n > max) { + n = max; + } + return n; + } + + /** + * No clue what this does (some sort of average/mean I presume). It came with the original UberColorPickerDialog + * in the API Demos and wasn't documented. I don't feel like spending any time figuring it out, I haven't looked at it at all. + * + * @param s + * @param d + * @param p + * @return + */ + private int ave(int s, int d, float p) { + return s + round(p * (d - s)); + } + + /** + * Came with the original UberColorPickerDialog in the API Demos, wasn't documented. I believe it takes an array of + * colors and a value in the range [0,1] and interpolates a resulting color in a seemingly predictable manner. + * I haven't looked at it at all. + * + * @param colors + * @param unit + * @return + */ + private int interpColor(int colors[], float unit) { + if (unit <= 0) { + return colors[0]; + } + if (unit >= 1) { + return colors[colors.length - 1]; + } + + float p = unit * (colors.length - 1); + int i = (int) p; + p -= i; + + // now p is just the fractional part [0...1) and i is the index + int c0 = colors[i]; + int c1 = colors[i + 1]; + int a = ave(Color.alpha(c0), Color.alpha(c1), p); + int r = ave(Color.red(c0), Color.red(c1), p); + int g = ave(Color.green(c0), Color.green(c1), p); + int b = ave(Color.blue(c0), Color.blue(c1), p); + + return Color.argb(a, r, g, b); + } + + /** + * A standard point-in-rect routine. + * + * @param x + * @param y + * @param r + * @return true if point x,y is in rect r + */ + public boolean ptInRect(int x, int y, Rect r) { + return x > r.left && x < r.right && y > r.top && y < r.bottom; + } + + /** + * Process trackball events. Used mainly for fine-tuned color adjustment, or alternatively to switch between slider controls. + */ + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + + //A longer event history implies faster trackball movement. + //Use it to infer a larger jump and therefore faster palette/slider adjustment. + int jump = event.getHistorySize() + 1; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + } + break; + case MotionEvent.ACTION_MOVE: { + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the appropriate entry in this list, + //depending on whether you use 1D or 2D controllers + switch (mMethod) { + case METHOD_HS_V_PALETTE: + if (mFocusedControl == 0) { + changeHSPalette(x, y, jump); + } + else if (mFocusedControl == 1) { + if (y < 0) + changeSlider(mFocusedControl, true, jump); + else if (y > 0) + changeSlider(mFocusedControl, false, jump); + } + break; + } + } + break; + case MotionEvent.ACTION_UP: { + } + break; + } + + return true; + } + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the appropriate functions below, + //one per UI controller in the new method + /** + * Effect a trackball change to a 2D palette. + * @param x -1: negative x change, 0: no x change, +1: positive x change. + * @param y -1: negative y change, 0, no y change, +1: positive y change. + * @param jump the amount by which to change. + */ + private void changeHSPalette(float x, float y, int jump) { + int x2 = 0, y2 = 0; + if (x < 0) + x2 = -jump; + else if (x > 0) + x2 = jump; + if (y < 0) + y2 = -jump; + else if (y > 0) + y2 = jump; + + mCoord[0] += x2; + mCoord[1] += y2; + + if (mCoord[0] < -mPaletteRadiusPx) + mCoord[0] = -mPaletteRadiusPx; + else if (mCoord[0] > mPaletteRadiusPx) + mCoord[0] = mPaletteRadiusPx; + if (mCoord[1] < -mPaletteRadiusPx) + mCoord[1] = -mPaletteRadiusPx; + else if (mCoord[1] > mPaletteRadiusPx) + mCoord[1] = mPaletteRadiusPx; + + float radius = (float) java.lang.Math.sqrt(mCoord[0] * mCoord[0] + mCoord[1] * mCoord[1]); + if (radius > mPaletteRadiusPx) + radius = mPaletteRadiusPx; + + float angle = (float) java.lang.Math.atan2(mCoord[1], mCoord[0]); + // need to turn angle [-PI ... PI] into unit [0....1] + float unit = angle / (2 * PI); + if (unit < 0) { + unit += 1; + } + + mCoord[0] = round(Math.cos(angle) * radius); + mCoord[1] = round(Math.sin(angle) * radius); + + int c = interpColor(mSpectrumColorsRev, unit); + float[] hsv = new float[3]; + Color.colorToHSV(c, hsv); + mHSV[0] = hsv[0]; + mHSV[1] = radius / mPaletteRadiusPx; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setVerValSlider(); + + invalidate(); + } + + /** + * Effect a trackball change to a 1D slider. + * @param slider id of the slider to be effected + * @param increase true if the change is an increase, false if a decrease + * @param jump the amount by which to change in units of the range [0,255] + */ + private void changeSlider(int slider, boolean increase, int jump) { + //NEW_METHOD_WORK_NEEDED_HERE + //It is only necessary to add an entry here for a new method if the new method uses a 1D slider. + //Note, some sliders are horizontal and others are vertical. + //They differ a bit, especially in a sign flip on the vertical axis. + if (mMethod == METHOD_HS_V_PALETTE) { + //slider *must* equal 1 + + mHSV[2] += (increase ? jump : -jump) / 256.0f; + mHSV[2] = pinToUnit(mHSV[2]); + updateAllFromHSV(); + mCoord[2] = mPaletteDimPx - (int) (mHSV[2] * mPaletteDimPx); + + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setOvalValDimmer(); + + invalidate(); + } + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateRGBfromHSV() { + int color = Color.HSVToColor(mHSV); + mRGB[0] = Color.red(color); + mRGB[1] = Color.green(color); + mRGB[2] = Color.blue(color); + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateYUVfromRGB() { + float r = mRGB[0] / 255.0f; + float g = mRGB[1] / 255.0f; + float b = mRGB[2] / 255.0f; + + ColorMatrix cm = new ColorMatrix(); + cm.setRGB2YUV(); + final float[] a = cm.getArray(); + + mYUV[0] = a[0] * r + a[1] * g + a[2] * b; + mYUV[0] = pinToUnit(mYUV[0]); + mYUV[1] = a[5] * r + a[6] * g + a[7] * b; + mYUV[1] = pin(mYUV[1], -.5f, .5f); + mYUV[2] = a[10] * r + a[11] * g + a[12] * b; + mYUV[2] = pin(mYUV[2], -.5f, .5f); + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateHexFromHSV() { + //For now, assume 100% opacity + mHexStr = Integer.toHexString(Color.HSVToColor(mHSV)).toUpperCase(); + mHexStr = mHexStr.substring(2, mHexStr.length()); + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateAllFromHSV() { + //Update mRGB + if (mRGBenabled || mYUVenabled) + updateRGBfromHSV(); + + //Update mYUV + if (mYUVenabled) + updateYUVfromRGB(); + + //Update mHexStr + if (mRGBenabled) + updateHexFromHSV(); + } + + /** + * Process touch events: down, move, and up + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + + //Generate coordinates which are palette=local with the origin at the upper left of the main 2D palette + int y2 = (int) (pin(round(y - mPalettePosY), mPaletteDimPx)); + + //Generate coordinates which are palette-local with the origin at the center of the main 2D palette + float circlePinnedX = x - mPalettePosX - mPaletteCenterPx; + float circlePinnedY = y - mPalettePosY - mPaletteCenterPx; + + //Is the event in a swatch? + boolean inSwatchOld = ptInRect(round(x), round(y), mOldSwatchRect); + boolean inSwatchNew = ptInRect(round(x), round(y), mNewSwatchRect); + + //Get the event's distance from the center of the main 2D palette + float radius = (float) java.lang.Math.sqrt(circlePinnedX * circlePinnedX + circlePinnedY * circlePinnedY); + + //Is the event in a circle-pinned 2D palette? + boolean inOvalPalette = radius <= mPaletteRadiusPx; + + //Pin the radius + if (radius > mPaletteRadiusPx) + radius = mPaletteRadiusPx; + + //Is the event in a vertical slider to the right of the main 2D palette + boolean inVerSlider = ptInRect(round(x), round(y), mVerSliderRect); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mTracking = TRACKED_NONE; + + if (inSwatchOld) + mTracking = TRACK_SWATCH_OLD; + else if (inSwatchNew) + mTracking = TRACK_SWATCH_NEW; + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last entry in this list + else if (mMethod == METHOD_HS_V_PALETTE) { + if (inOvalPalette) { + mTracking = TRACK_HS_PALETTE; + mFocusedControl = 0; + } else if (inVerSlider) { + mTracking = TRACK_VER_VALUE_SLIDER; + mFocusedControl = 1; + } + } + case MotionEvent.ACTION_MOVE: + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the entries in this list, + //one per UI controller the new method requires. + if (mTracking == TRACK_HS_PALETTE) { + float angle = (float) java.lang.Math.atan2(circlePinnedY, circlePinnedX); + // need to turn angle [-PI ... PI] into unit [0....1] + float unit = angle / (2 * PI); + if (unit < 0) { + unit += 1; + } + + mCoord[0] = round(Math.cos(angle) * radius); + mCoord[1] = round(Math.sin(angle) * radius); + + int c = interpColor(mSpectrumColorsRev, unit); + float[] hsv = new float[3]; + Color.colorToHSV(c, hsv); + mHSV[0] = hsv[0]; + mHSV[1] = radius / mPaletteRadiusPx; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setVerValSlider(); + + invalidate(); + } else if (mTracking == TRACK_VER_VALUE_SLIDER) { + if (mCoord[2] != y2) { + mCoord[2] = y2; + float value = 1.0f - (float) y2 / (float) mPaletteDimPx; + + mHSV[2] = value; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + + setOvalValDimmer(); + + invalidate(); + } + } + break; + case MotionEvent.ACTION_UP: + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last entry in this list. + if (mTracking == TRACK_SWATCH_OLD && inSwatchOld) { + Color.colorToHSV(mOriginalColor, mHSV); + mSwatchNew.setColor(mOriginalColor); + initUI(); + invalidate(); + } else if (mTracking == TRACK_SWATCH_NEW && inSwatchNew) { + mListener.colorChanged(mSwatchNew.getColor()); + invalidate(); + } + + mTracking = TRACKED_NONE; + break; + } + + return true; + } + } +} diff --git a/app/src/main/java/org/connectbot/util/VolumePreference.java b/app/src/main/java/org/connectbot/util/VolumePreference.java new file mode 100644 index 0000000..09feb61 --- /dev/null +++ b/app/src/main/java/org/connectbot/util/VolumePreference.java @@ -0,0 +1,72 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import android.content.Context; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +/** + * @author kenny + * + */ +public class VolumePreference extends DialogPreference implements OnSeekBarChangeListener { + /** + * @param context + * @param attrs + */ + public VolumePreference(Context context, AttributeSet attrs) { + super(context, attrs); + + setupLayout(context, attrs); + } + + public VolumePreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + setupLayout(context, attrs); + } + + private void setupLayout(Context context, AttributeSet attrs) { + setPersistent(true); + } + + @Override + protected View onCreateDialogView() { + SeekBar sb = new SeekBar(getContext()); + + sb.setMax(100); + sb.setProgress((int) (getPersistedFloat( + PreferenceConstants.DEFAULT_BELL_VOLUME) * 100)); + sb.setPadding(10, 10, 10, 10); + sb.setOnSeekBarChangeListener(this); + + return sb; + } + + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + persistFloat(progress / 100f); + } + + public void onStartTrackingTouch(SeekBar seekBar) { } + + public void onStopTrackingTouch(SeekBar seekBar) { } +} diff --git a/app/src/main/java/org/connectbot/util/XmlBuilder.java b/app/src/main/java/org/connectbot/util/XmlBuilder.java new file mode 100644 index 0000000..4a6f62d --- /dev/null +++ b/app/src/main/java/org/connectbot/util/XmlBuilder.java @@ -0,0 +1,71 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.util; + +import com.trilead.ssh2.crypto.Base64; + +/** + * @author Kenny Root + * + */ +public class XmlBuilder { + private StringBuilder sb; + + public XmlBuilder() { + sb = new StringBuilder(); + } + + public XmlBuilder append(String data) { + sb.append(data); + + return this; + } + + public XmlBuilder append(String field, Object data) { + if (data == null) { + sb.append(String.format("<%s/>", field)); + } else if (data instanceof String) { + String input = (String) data; + boolean binary = false; + + for (byte b : input.getBytes()) { + if (b < 0x20 || b > 0x7e) { + binary = true; + break; + } + } + + sb.append(String.format("<%s>%s</%s>", field, + binary ? new String(Base64.encode(input.getBytes())) : input, field)); + } else if (data instanceof Integer) { + sb.append(String.format("<%s>%d</%s>", field, (Integer) data, field)); + } else if (data instanceof Long) { + sb.append(String.format("<%s>%d</%s>", field, (Long) data, field)); + } else if (data instanceof byte[]) { + sb.append(String.format("<%s>%s</%s>", field, new String(Base64.encode((byte[]) data)), field)); + } else if (data instanceof Boolean) { + sb.append(String.format("<%s>%s</%s>", field, (Boolean) data, field)); + } + + return this; + } + + public String toString() { + return sb.toString(); + } +} diff --git a/app/src/main/java/org/keyczar/jce/EcCore.java b/app/src/main/java/org/keyczar/jce/EcCore.java new file mode 100644 index 0000000..681d5db --- /dev/null +++ b/app/src/main/java/org/keyczar/jce/EcCore.java @@ -0,0 +1,679 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.keyczar.jce; + +import java.math.BigInteger; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; + +/** + * This class implements the basic EC operations such as point addition and + * doubling and point multiplication. Only NSA Suite B / NIST curves are + * supported. + * + * Todo: + * - Add (more) comments - Performance optimizations - Cleanup ASN.1 code, + * possibly replace with own impl - ... + * + * References: + * + * [1] Software Implementation of the NIST Elliptic Curves Over Prime Fields, M. + * Brown et al. [2] Efficient elliptic curve exponentiation using mixed + * coordinates, H. Cohen et al. [3] SEC 1: Elliptic Curve Cryptography. [4] + * Guide to Elliptic Curve Cryptography, D. Hankerson et al., Springer. + * + * @author martclau@gmail.com + * + */ +// BEGIN connectbot-changed +public final class EcCore { +// END connectbot-changed +// BEGIN connectbot-removed +// private static final long serialVersionUID = -1376116429660095993L; +// +// private static final String INFO = "Google Keyczar (EC key/parameter generation; EC signing)"; +// +// public static final String NAME = "GooKey"; +// +// @SuppressWarnings("unchecked") +// public EcCore() { +// super(NAME, 0.1, INFO); +// AccessController.doPrivileged(new PrivilegedAction<Object>() { +// @Override +// public Object run() { +// put("Signature.SHA1withECDSA", "org.keyczar.jce.EcSignatureImpl$SHA1"); +// put("Alg.Alias.Signature.ECDSA", "SHA1withDSA"); +// put("Signature.SHA256withECDSA", +// "org.keyczar.jce.EcSignatureImpl$SHA256"); +// put("Signature.SHA384withECDSA", +// "org.keyczar.jce.EcSignatureImpl$SHA384"); +// put("Signature.SHA512withECDSA", +// "org.keyczar.jce.EcSignatureImpl$SHA512"); +// put("KeyPairGenerator.EC", "org.keyczar.jce.EcKeyPairGeneratorImpl"); +// put("KeyFactory.EC", "org.keyczar.jce.EcKeyFactoryImpl"); +// put("Signature.SHA1withECDSA KeySize", "521"); +// put("Signature.SHA1withECDSA ImplementedIn", "Software"); +// put("Signature.SHA256withECDSA KeySize", "521"); +// put("Signature.SHA256withECDSA ImplementedIn", "Software"); +// put("Signature.SHA384withECDSA KeySize", "521"); +// put("Signature.SHA384withECDSA ImplementedIn", "Software"); +// put("Signature.SHA512withECDSA KeySize", "521"); +// put("Signature.SHA512withECDSA ImplementedIn", "Software"); +// put("KeyPairGenerator.EC ImplementedIn", "Software"); +// put("KeyFactory.EC ImplementedIn", "Software"); +// return null; +// } +// }); +// } +// +// private static final ECParameterSpec P192 = new ECParameterSpec( +// new EllipticCurve( +// new ECFieldFp(new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF", 16)), +// new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC", 16), +// new BigInteger("64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1", 16)), +// new ECPoint( +// new BigInteger("188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012", 16), +// new BigInteger("07192B95FFC8DA78631011ED6B24CDD573F977A11E794811", 16)), +// new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831", 16), 1); +// +// private static final ECParameterSpec P224 = new ECParameterSpec( +// new EllipticCurve(new ECFieldFp(new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001", 16)), +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE", 16), +// new BigInteger( +// "B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4", 16)), +// new ECPoint(new BigInteger( +// "B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21", 16), +// new BigInteger( +// "BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34", 16)), +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D", 16), 1); +// +// private static final ECParameterSpec P256 = new ECParameterSpec( +// new EllipticCurve(new ECFieldFp(new BigInteger( +// "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", +// 16)), new BigInteger( +// "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", +// 16), new BigInteger( +// "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", +// 16)), new ECPoint(new BigInteger( +// "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", +// 16), new BigInteger( +// "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", +// 16)), new BigInteger( +// "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", +// 16), 1); +// +// private static final ECParameterSpec P384 = new ECParameterSpec( +// new EllipticCurve( +// new ECFieldFp( +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", +// 16)), +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", +// 16), +// new BigInteger( +// "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", +// 16)), +// new ECPoint( +// new BigInteger( +// "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", +// 16), +// new BigInteger( +// "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", +// 16)), +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", +// 16), 1); +// +// private static final ECParameterSpec P521 = new ECParameterSpec( +// new EllipticCurve( +// new ECFieldFp( +// new BigInteger( +// "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", +// 16)), +// new BigInteger( +// "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", +// 16), +// new BigInteger( +// "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", +// 16)), +// new ECPoint( +// new BigInteger( +// "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", +// 16), +// new BigInteger( +// "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", +// 16)), +// new BigInteger( +// "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", +// 16), 1); +// +// public static final String EC_PARAMS_P192_OID = "1.2.840.10045.3.1.1"; +// public static final String EC_PARAMS_P224_OID = "1.3.132.0.33"; +// public static final String EC_PARAMS_P256_OID = "1.2.840.10045.3.1.7"; +// public static final String EC_PARAMS_P384_OID = "1.3.132.0.34"; +// public static final String EC_PARAMS_P521_OID = "1.3.132.0.35"; +// +// private static Map<String, ECParameterSpec> oidMap = new HashMap<String, ECParameterSpec>(); +// private static Map<ECParameterSpec, String> paramsMap = new HashMap<ECParameterSpec, String>(); +// private static Map<ECParameterSpec, String> friendlyNameMap = new HashMap<ECParameterSpec, String>(); +// +// static { +// oidMap.put(EC_PARAMS_P192_OID, P192); +// oidMap.put(EC_PARAMS_P224_OID, P224); +// oidMap.put(EC_PARAMS_P256_OID, P256); +// oidMap.put(EC_PARAMS_P384_OID, P384); +// oidMap.put(EC_PARAMS_P521_OID, P521); +// paramsMap.put(P192, EC_PARAMS_P192_OID); +// paramsMap.put(P224, EC_PARAMS_P224_OID); +// paramsMap.put(P256, EC_PARAMS_P256_OID); +// paramsMap.put(P384, EC_PARAMS_P384_OID); +// paramsMap.put(P521, EC_PARAMS_P521_OID); +// friendlyNameMap.put(P192, "P-192"); +// friendlyNameMap.put(P224, "P-224"); +// friendlyNameMap.put(P256, "P-256"); +// friendlyNameMap.put(P384, "P-384"); +// friendlyNameMap.put(P521, "P-521"); +// } +// +// public static ECParameterSpec getParams(String oid) { +// ECParameterSpec params; +// if ((params = oidMap.get(oid)) != null) return params; +// throw new IllegalArgumentException("Unsupported EC parameters: " + oid); +// } +// +// public static String getOID(ECParameterSpec params) { +// String oid; +// if ((oid = paramsMap.get(params)) != null) return oid; +// throw new IllegalArgumentException("Unsupport EC parameters"); +// } +// +// public static String getFriendlyName(ECParameterSpec params) { +// String name; +// if ((name = friendlyNameMap.get(params)) != null) return name; +// throw new IllegalArgumentException("Unsupport EC parameters"); +// } +// +// private static final BigInteger ZERO = BigInteger.ZERO; +// private static final BigInteger ONE = BigInteger.ONE; +// private static final BigInteger TWO = BigInteger.valueOf(2); +// END connectbot-removed + private static final BigInteger THREE = BigInteger.valueOf(3); +// BEGIN connectbot-removed +// private static final BigInteger FOUR = BigInteger.valueOf(4); +// private static final BigInteger EIGHT = BigInteger.valueOf(8); +// END connectbot-removed + + private static BigInteger[] doublePointA(BigInteger[] P, + ECParameterSpec params) { + final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); + final BigInteger a = params.getCurve().getA(); + + if (P[0] == null || P[1] == null) return P; + + BigInteger d = (P[0].pow(2).multiply(THREE).add(a)).multiply(P[1] + .shiftLeft(1).modInverse(p)); + BigInteger[] R = new BigInteger[2]; + R[0] = d.pow(2).subtract(P[0].shiftLeft(1)).mod(p); + R[1] = d.multiply(P[0].subtract(R[0])).subtract(P[1]).mod(p); + + return R; + } + + private static BigInteger[] addPointsA(BigInteger[] P1, BigInteger[] P2, + ECParameterSpec params) { + final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); + + if (P2[0] == null || P2[1] == null) return P1; + + if (P1[0] == null || P1[1] == null) return P2; + + BigInteger d = (P2[1].subtract(P1[1])).multiply((P2[0].subtract(P1[0])) + .modInverse(p)); + BigInteger[] R = new BigInteger[2]; + R[0] = d.pow(2).subtract(P1[0]).subtract(P2[0]).mod(p); + R[1] = d.multiply(P1[0].subtract(R[0])).subtract(P1[1]).mod(p); + + return R; + } + + public static BigInteger[] multiplyPointA(BigInteger[] P, BigInteger k, + ECParameterSpec params) { + BigInteger[] Q = new BigInteger[] {null, null}; + + for (int i = k.bitLength() - 1; i >= 0; i--) { + Q = doublePointA(Q, params); + if (k.testBit(i)) Q = addPointsA(Q, P, params); + } + + return Q; + } + +// BEGIN connectbot-removed +// private static BigInteger[] doublePointJ(BigInteger[] P, +// ECParameterSpec params) { +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// BigInteger A, B, C, D; +// +// if (P[2].signum() == 0) // point at inf +// return P; +// +// A = FOUR.multiply(P[0]).multiply(P[1].pow(2)).mod(p); +// B = EIGHT.multiply(P[1].pow(4)).mod(p); +// C = THREE.multiply(P[0].subtract(P[2].pow(2))).multiply( +// P[0].add(P[2].pow(2))).mod(p); +// D = C.pow(2).subtract(A.add(A)).mod(p); +// +// return new BigInteger[] { +// D, C.multiply(A.subtract(D)).subtract(B).mod(p), +// TWO.multiply(P[1]).multiply(P[2]).mod(p)}; +// } +// +// private static BigInteger[] addPointsJA(BigInteger[] P1, BigInteger[] P2, +// ECParameterSpec params) { +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// BigInteger A, B, C, D; +// BigInteger X3; +// +// if (P1[2].signum() == 0) // point at inf +// return new BigInteger[] {P2[0], P2[1], ONE}; +// +// A = P2[0].multiply(P1[2].pow(2)).mod(p); +// B = P2[1].multiply(P1[2].pow(3)).mod(p); +// C = A.subtract(P1[0]).mod(p); +// D = B.subtract(P1[1]).mod(p); +// +// X3 = D.pow(2) +// .subtract(C.pow(3).add(TWO.multiply(P1[0]).multiply(C.pow(2)))).mod(p); +// return new BigInteger[] { +// X3, +// D.multiply(P1[0].multiply(C.pow(2)).subtract(X3)).subtract( +// P1[1].multiply(C.pow(3))).mod(p), P1[2].multiply(C).mod(p)}; +// } +// +// // Binary NAF method for point multiplication +// public static BigInteger[] multiplyPoint(BigInteger[] P, BigInteger k, +// ECParameterSpec params) { +// BigInteger h = THREE.multiply(k); +// +// BigInteger[] Pneg = new BigInteger[] {P[0], P[1].negate()}; +// BigInteger[] R = new BigInteger[] {P[0], P[1], ONE}; +// +// int bitLen = h.bitLength(); +// for (int i = bitLen - 2; i > 0; --i) { +// R = doublePointJ(R, params); +// if (h.testBit(i)) R = addPointsJA(R, P, params); +// if (k.testBit(i)) R = addPointsJA(R, Pneg, params); +// } +// +// // // <DEBUG> +// // BigInteger[] SS = new BigInteger[] { R[0], R[1], R[2] }; +// // toAffine(SS, params); +// // BigInteger[] RR = multiplyPointA(P, k, params); +// // if (!SS[0].equals(RR[0]) || !SS[1].equals(RR[1])) +// // throw new RuntimeException("Internal mult error"); +// // // </DEBUG> +// +// return R; +// } + +// // Simultaneous multiple point multiplication, also known as Shamir's trick +// static BigInteger[] multiplyPoints(BigInteger[] P, BigInteger k, +// BigInteger[] Q, BigInteger l, ECParameterSpec params) { +// BigInteger[] PQ = addPointsA(P, Q, params); +// BigInteger[] R = new BigInteger[] {null, null, ZERO}; +// +// int max = Math.max(k.bitLength(), l.bitLength()); +// for (int i = max - 1; i >= 0; --i) { +// R = doublePointJ(R, params); +// if (k.testBit(i)) { +// if (l.testBit(i)) +// R = addPointsJA(R, PQ, params); +// else +// R = addPointsJA(R, P, params); +// } else if (l.testBit(i)) R = addPointsJA(R, Q, params); +// } +// +// // // <DEBUG> +// // BigInteger[] SS = new BigInteger[] { R[0], R[1], R[2] }; +// // toAffine(SS, params); +// // BigInteger[] AA = multiplyPointA(P, k, params); +// // BigInteger[] BB = multiplyPointA(Q, l, params); +// // BigInteger[] AB = addPointsA(AA, BB, params); +// // if (!SS[0].equals(AB[0]) || !SS[1].equals(AB[1])) +// // throw new RuntimeException("Internal mult error"); +// // // </DEBUG> +// +// return R; +// } +// +// // SEC 1, 2.3.5 +// static byte[] fieldElemToBytes(BigInteger a, ECParameterSpec params) { +// int len = (((ECFieldFp) params.getCurve().getField()).getP().bitLength() + 7) / 8; +// byte[] bytes = a.toByteArray(); +// if (len < bytes.length) { +// byte[] tmp = new byte[len]; +// System.arraycopy(bytes, bytes.length - tmp.length, tmp, 0, tmp.length); +// return tmp; +// } else if (len > bytes.length) { +// byte[] tmp = new byte[len]; +// System.arraycopy(bytes, 0, tmp, tmp.length - bytes.length, bytes.length); +// return tmp; +// } +// return bytes; +// } +// +// static int fieldElemToBytes(BigInteger a, ECParameterSpec params, +// byte[] data, int off) { +// int len = (((ECFieldFp) params.getCurve().getField()).getP().bitLength() + 7) / 8; +// byte[] bytes = a.toByteArray(); +// if (len < bytes.length) { +// System.arraycopy(bytes, bytes.length - len, data, off, len); +// return len; +// } else if (len > bytes.length) { +// System.arraycopy(bytes, 0, data, len - bytes.length + off, bytes.length); +// return len; +// } +// System.arraycopy(bytes, 0, data, off, bytes.length); +// return bytes.length; +// } +// +// // SEC 1, 2.3.3 +// static byte[] ecPointToBytes(ECPoint a, ECParameterSpec params) { +// byte[] fe1 = fieldElemToBytes(a.getAffineX(), params); +// byte[] fe2 = fieldElemToBytes(a.getAffineY(), params); +// byte[] bytes = new byte[1 + fe1.length + fe2.length]; +// bytes[0] = 0x04; +// System.arraycopy(fe1, 0, bytes, 1, fe1.length); +// System.arraycopy(fe2, 0, bytes, 1 + fe1.length, fe2.length); +// return bytes; +// } +// +// // SEC 1, 2.3.4 +// static ECPoint bytesToECPoint(byte[] bytes, ECParameterSpec params) { +// switch (bytes[0]) { +// case 0x00: // point at inf +// throw new IllegalArgumentException( +// "Point at infinity is not a valid argument"); +// case 0x02: // point compression +// case 0x03: +// throw new UnsupportedOperationException( +// "Point compression is not supported"); +// case 0x04: +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// byte[] fe = new byte[(p.bitLength() + 7) / 8]; +// System.arraycopy(bytes, 1, fe, 0, fe.length); +// BigInteger x = new BigInteger(1, fe); +// System.arraycopy(bytes, 1 + fe.length, fe, 0, fe.length); +// return new ECPoint(x, new BigInteger(1, fe)); +// default: +// throw new IllegalArgumentException("Invalid point encoding"); +// } +// } +// +// // Convert Jacobian point to affine +// static void toAffine(BigInteger[] P, ECParameterSpec params) { +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// P[0] = P[0].multiply(P[2].pow(2).modInverse(p)).mod(p); +// P[1] = P[1].multiply(P[2].pow(3).modInverse(p)).mod(p); +// } +// +// static void toAffineX(BigInteger[] P, ECParameterSpec params) { +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// P[0] = P[0].multiply(P[2].pow(2).modInverse(p)).mod(p); +// } +// +// static BigInteger[] internalPoint(ECPoint P) { +// return new BigInteger[] {P.getAffineX(), P.getAffineY()}; +// } +// +// // private static void printPerf(String msg, long start, long stop) { +// // String unit = "ms"; +// // long diff = stop - start; +// // if (diff > 1000) { +// // diff /= 1000; +// // unit = "s"; +// // } +// // System.out.printf("%s: %d %s\n", msg, diff, unit); +// // } +// +// public static void main(String[] args) throws Exception { +// +// Security.insertProviderAt(new EcCore(), 0); +// +// // ---- +// // Test primitives +// // ---- +// +// // GooKey EC private key, 256 bit +// // Private value: +// // a9231e0d113abdacd3bb5edb24124fbef6f562c5f90b835670f5e48f775019f2 +// // Parameters: P-256 (1.2.840.10045.3.1.7) +// // GooKey EC public key, 256 bit +// // Public value (x coordinate): +// // 86645e0320c0f9dc1a9b8456396cc105754df67a9829c21e13ab6ecf944cf68c +// // Public value (y coordinate): +// // ea1721a578043d48f12738359b5eb5f0dac2242ec6128ee0ab6ff40c8fe0cae6 +// // Parameters: P-256 (1.2.840.10045.3.1.7) +// // GooKey EC private key, 256 bit +// // Private value: +// // b84d5cfab214fc3928864abb85f668a85b1006ca0147c78f22deb1dcc7e4a022 +// // Parameters: P-256 (1.2.840.10045.3.1.7) +// // GooKey EC public key, 256 bit +// // Public value (x coordinate): +// // 61f6f7264f0a19f0debcca3efd079667a0112cc0b8be07a815b4c375e96ad3d1 +// // Public value (y coordinate): +// // 3308c0016d776ed5aa9f021e43348b2e684b3b7a0f25dc9e4c8670b5d87cb705 +// // Parameters: P-256 (1.2.840.10045.3.1.7) +// +// // P = kG +// BigInteger k = new BigInteger( +// "a9231e0d113abdacd3bb5edb24124fbef6f562c5f90b835670f5e48f775019f2", 16); +// BigInteger[] P = new BigInteger[] { +// new BigInteger( +// "86645e0320c0f9dc1a9b8456396cc105754df67a9829c21e13ab6ecf944cf68c", +// 16), +// new BigInteger( +// "ea1721a578043d48f12738359b5eb5f0dac2242ec6128ee0ab6ff40c8fe0cae6", +// 16), ONE}; +// +// // Q = lG +// BigInteger l = new BigInteger( +// "b84d5cfab214fc3928864abb85f668a85b1006ca0147c78f22deb1dcc7e4a022", 16); +// BigInteger[] Q = new BigInteger[] { +// new BigInteger( +// "61f6f7264f0a19f0debcca3efd079667a0112cc0b8be07a815b4c375e96ad3d1", +// 16), +// new BigInteger( +// "3308c0016d776ed5aa9f021e43348b2e684b3b7a0f25dc9e4c8670b5d87cb705", +// 16), ONE}; +// +// // Known answer for P+Q +// BigInteger[] kat1 = new BigInteger[] { +// new BigInteger( +// "bc7adb05bca2460bbfeb4e0f88b61c384ea88ed3fd56017938ac2582513d4220", +// 16), +// new BigInteger( +// "a640a43df2e9df39eec11445b7e3f7835b743ef1ac4a83cecb570a060b3f1c6c", +// 16)}; +// +// BigInteger[] R = addPointsA(P, Q, P256); +// if (!R[0].equals(kat1[0]) || !R[1].equals(kat1[1])) +// throw new RuntimeException("kat1 failed"); +// +// R = addPointsJA(P, Q, P256); +// toAffine(R, P256); +// if (!R[0].equals(kat1[0]) || !R[1].equals(kat1[1])) +// throw new RuntimeException("kat1 failed"); +// +// +// // Known answer for Q+Q +// BigInteger[] kat2 = new BigInteger[] { +// new BigInteger( +// "c79d7f9100c14a70f0bb9bdce59654abf99e10d1ac5afc1a0f1b6bc650d6429b", +// 16), +// new BigInteger( +// "6856814e47adce42bc0d7c3bef308c6c737c418ed093effb31e21f53c7735c97", +// 16)}; +// +// R = doublePointA(P, P256); +// if (!R[0].equals(kat2[0]) || !R[1].equals(kat2[1])) +// throw new RuntimeException("kat2 failed"); +// +// R = doublePointJ(P, P256); +// toAffine(R, P256); +// if (!R[0].equals(kat2[0]) || !R[1].equals(kat2[1])) +// throw new RuntimeException("kat2 failed"); +// +// // Known answer for kP +// BigInteger[] kat3 = new BigInteger[] { +// new BigInteger( +// "97a82a834b9e6b50660ae30d43dac9b200276e8bcd2ed6a6593048de09276d1a", +// 16), +// new BigInteger( +// "30a9590a01066d8ef54a910afcc8648dbc7400c01750af423ce95547f2154d56", +// 16)}; +// +// R = multiplyPointA(P, k, P256); +// if (!R[0].equals(kat3[0]) || !R[1].equals(kat3[1])) +// throw new RuntimeException("kat3 failed"); +// +// R = multiplyPoint(P, k, P256); +// toAffine(R, P256); +// if (!R[0].equals(kat3[0]) || !R[1].equals(kat3[1])) +// throw new RuntimeException("kat3 failed"); +// +// // Known answer for kP+lQ +// BigInteger[] kat4 = new BigInteger[] { +// new BigInteger( +// "6fd51be5cf3d6a6bcb62594bbe41ccf549b37d8fefff6e293a5bea0836efcfc6", +// 16), +// new BigInteger( +// "9bc21a930137aa3814908974c431e4545a05dce61321253c337f3883129c42ca", +// 16)}; +// +// BigInteger[] RR = multiplyPointA(Q, l, P256); +// R = addPointsA(R, RR, P256); +// if (!R[0].equals(kat4[0]) || !R[1].equals(kat4[1])) +// throw new RuntimeException("kat4 failed"); +// +// R = multiplyPoints(P, k, Q, l, P256); +// toAffine(R, P256); +// if (!R[0].equals(kat4[0]) || !R[1].equals(kat4[1])) +// throw new RuntimeException("kat4 failed"); +// +// // ---- +// // Test ECDSA in various combinations +// // ---- +// +// Provider gooProv = Security.getProvider("GooKey"); +// Provider nssProv = Security.getProvider("SunPKCS11-NSS"); +// +// // Number of iterations: trust me, this is a (stress) good test +// // and does provoke bugs in a fuzzing way. +// int iter = 50; +// +// // Iterate over all key lengths and signature schemes. +// int[] keyLengths = {192, 224, 256, 384, 521}; +// String[] ecdsas = { +// "SHA1withECDSA", "SHA256withECDSA", "SHA384withECDSA", +// "SHA512withECDSA"}; +// for (int s = 0; s < ecdsas.length; s++) { +// System.out.println("Signature scheme " + ecdsas[s]); +// for (int i = 0; i < keyLengths.length; i++) { +// System.out.print("Testing P-" + keyLengths[i] + ": "); +// for (int n = 0; n < iter; n++) { +// System.out.print("."); +// +// KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", gooProv); +// kpGen.initialize(keyLengths[i]); +// KeyPair ecKeyPair = kpGen.generateKeyPair(); +// +// ECPrivateKey ecPrivKey = (ECPrivateKey) ecKeyPair.getPrivate(); +// byte[] tmp = ecPrivKey.getEncoded(); +// KeyFactory keyFab = KeyFactory.getInstance("EC", gooProv); +// keyFab.generatePrivate(new PKCS8EncodedKeySpec(tmp)); +// ECPrivateKeySpec ecPrivSpec = new ECPrivateKeySpec(ecPrivKey.getS(), +// ecPrivKey.getParams()); +// keyFab.generatePrivate(ecPrivSpec); +// +// ECPublicKey ecPubKey = (ECPublicKey) ecKeyPair.getPublic(); +// tmp = ecPubKey.getEncoded(); // dont modify tmp now - is used below +// keyFab.generatePublic(new X509EncodedKeySpec(tmp)); +// ECPublicKeySpec ecPubSpec = new ECPublicKeySpec(ecPubKey.getW(), +// ecPubKey.getParams()); +// keyFab.generatePublic(ecPubSpec); +// +// Signature ecdsa = Signature.getInstance(ecdsas[s], gooProv); +// ecdsa.initSign(ecPrivKey); +// ecdsa.update(tmp); +// byte[] sig = ecdsa.sign(); +// ecdsa.initVerify(ecPubKey); +// ecdsa.update(tmp); +// if (!ecdsa.verify(sig)) +// throw new RuntimeException("Signature not verified: " +// + keyLengths[i]); +// +// // Cross verify using NSS if present +// if (nssProv != null) { +// keyFab = KeyFactory.getInstance("EC", nssProv); +// +// // For some reason NSS doesnt seem to work for P-192 and P-224?! +// if (keyLengths[i] == 192 || keyLengths[i] == 224) continue; +// +// ECPrivateKey nssPrivKey = (ECPrivateKey) keyFab +// .generatePrivate(new PKCS8EncodedKeySpec(ecPrivKey.getEncoded())); +// ECPublicKey nssPubKey = (ECPublicKey) keyFab +// .generatePublic(new X509EncodedKeySpec(ecPubKey.getEncoded())); +// +// ecdsa = Signature.getInstance(ecdsas[s], nssProv); +// ecdsa.initVerify(nssPubKey); +// ecdsa.update(tmp); +// if (!ecdsa.verify(sig)) +// throw new RuntimeException("Signature not verified 2: " +// + keyLengths[i]); +// +// ecdsa.initSign(nssPrivKey); +// ecdsa.update(tmp); +// sig = ecdsa.sign(); +// ecdsa = Signature.getInstance(ecdsas[s], gooProv); +// ecdsa.initVerify(ecPubKey); +// ecdsa.update(tmp); +// if (!ecdsa.verify(sig)) +// throw new RuntimeException("Signature not verified 3: " +// + keyLengths[i]); +// } +// } +// System.out.println(" done"); +// } +// } +// +// // Test Keyczar integration +// // Signer ecdsaSigner = new Signer("c:\\temp\\eckeyset"); +// // String tbs = "Sign this"; +// // String sig = ecdsaSigner.sign(tbs); +// // if (ecdsaSigner.verify(sig, tbs)) +// // System.out.println("Keyczar EC OK"); +// // else +// // System.out.println("Keyczar EC not OK"); +// } +//END connectbot-removed +} diff --git a/app/src/main/java/org/openintents/intents/FileManagerIntents.java b/app/src/main/java/org/openintents/intents/FileManagerIntents.java new file mode 100644 index 0000000..fba2555 --- /dev/null +++ b/app/src/main/java/org/openintents/intents/FileManagerIntents.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2008 OpenIntents.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openintents.intents; + +// Version Dec 9, 2008 + + +/** + * Provides OpenIntents actions, extras, and categories used by providers. + * <p>These specifiers extend the standard Android specifiers.</p> + */ +public final class FileManagerIntents { + + /** + * Activity Action: Pick a file through the file manager, or let user + * specify a custom file name. + * Data is the current file name or file name suggestion. + * Returns a new file name as file URI in data. + * + * <p>Constant Value: "org.openintents.action.PICK_FILE"</p> + */ + public static final String ACTION_PICK_FILE = "org.openintents.action.PICK_FILE"; + + /** + * Activity Action: Pick a directory through the file manager, or let user + * specify a custom file name. + * Data is the current directory name or directory name suggestion. + * Returns a new directory name as file URI in data. + * + * <p>Constant Value: "org.openintents.action.PICK_DIRECTORY"</p> + */ + public static final String ACTION_PICK_DIRECTORY = "org.openintents.action.PICK_DIRECTORY"; + + /** + * The title to display. + * + * <p>This is shown in the title bar of the file manager.</p> + * + * <p>Constant Value: "org.openintents.extra.TITLE"</p> + */ + public static final String EXTRA_TITLE = "org.openintents.extra.TITLE"; + + /** + * The text on the button to display. + * + * <p>Depending on the use, it makes sense to set this to "Open" or "Save".</p> + * + * <p>Constant Value: "org.openintents.extra.BUTTON_TEXT"</p> + */ + public static final String EXTRA_BUTTON_TEXT = "org.openintents.extra.BUTTON_TEXT"; + +} diff --git a/app/src/main/jni/com_google_ase_Exec.cpp b/app/src/main/jni/com_google_ase_Exec.cpp new file mode 100644 index 0000000..b78f356 --- /dev/null +++ b/app/src/main/jni/com_google_ase_Exec.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "com_google_ase_Exec.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <termios.h> +#include <unistd.h> + +#include "android/log.h" + +#define LOG_TAG "Exec" +#define LOG(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +void JNU_ThrowByName(JNIEnv* env, const char* name, const char* msg) { + jclass clazz = env->FindClass(name); + if (clazz != NULL) { + env->ThrowNew(clazz, msg); + } + env->DeleteLocalRef(clazz); +} + +char* JNU_GetStringNativeChars(JNIEnv* env, jstring jstr) { + if (jstr == NULL) { + return NULL; + } + jbyteArray bytes = 0; + jthrowable exc; + char* result = 0; + if (env->EnsureLocalCapacity(2) < 0) { + return 0; /* out of memory error */ + } + jclass Class_java_lang_String = env->FindClass("java/lang/String"); + jmethodID MID_String_getBytes = env->GetMethodID( + Class_java_lang_String, "getBytes", "()[B"); + bytes = (jbyteArray) env->CallObjectMethod(jstr, MID_String_getBytes); + exc = env->ExceptionOccurred(); + if (!exc) { + jint len = env->GetArrayLength(bytes); + result = (char*) malloc(len + 1); + if (result == 0) { + JNU_ThrowByName(env, "java/lang/OutOfMemoryError", 0); + env->DeleteLocalRef(bytes); + return 0; + } + env->GetByteArrayRegion(bytes, 0, len, (jbyte*) result); + result[len] = 0; /* NULL-terminate */ + } else { + env->DeleteLocalRef(exc); + } + env->DeleteLocalRef(bytes); + return result; +} + +int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) { + jclass Class_java_io_FileDescriptor = env->FindClass("java/io/FileDescriptor"); + jfieldID descriptor = env->GetFieldID(Class_java_io_FileDescriptor, + "descriptor", "I"); + return env->GetIntField(fileDescriptor, descriptor); +} + +static int create_subprocess( + const char* cmd, const char* arg0, const char* arg1, int* pProcessId) { + char* devname; + int ptm; + pid_t pid; + + ptm = open("/dev/ptmx", O_RDWR); // | O_NOCTTY); + if(ptm < 0){ + LOG("[ cannot open /dev/ptmx - %s ]\n", strerror(errno)); + return -1; + } + fcntl(ptm, F_SETFD, FD_CLOEXEC); + + if(grantpt(ptm) || unlockpt(ptm) || + ((devname = (char*) ptsname(ptm)) == 0)){ + LOG("[ trouble with /dev/ptmx - %s ]\n", strerror(errno)); + return -1; + } + + pid = fork(); + if(pid < 0) { + LOG("- fork failed: %s -\n", strerror(errno)); + return -1; + } + + if(pid == 0){ + int pts; + + setsid(); + + pts = open(devname, O_RDWR); + if(pts < 0) exit(-1); + + dup2(pts, 0); + dup2(pts, 1); + dup2(pts, 2); + + close(ptm); + + execl(cmd, cmd, arg0, arg1, NULL); + exit(-1); + } else { + *pProcessId = (int) pid; + return ptm; + } +} + +JNIEXPORT jobject JNICALL Java_com_google_ase_Exec_createSubprocess( + JNIEnv* env, jclass clazz, jstring cmd, jstring arg0, jstring arg1, + jintArray processIdArray) { + char* cmd_8 = JNU_GetStringNativeChars(env, cmd); + char* arg0_8 = JNU_GetStringNativeChars(env, arg0); + char* arg1_8 = JNU_GetStringNativeChars(env, arg1); + + int procId; + int ptm = create_subprocess(cmd_8, arg0_8, arg1_8, &procId); + + if (processIdArray) { + int procIdLen = env->GetArrayLength(processIdArray); + if (procIdLen > 0) { + jboolean isCopy; + int* pProcId = (int*) env->GetPrimitiveArrayCritical(processIdArray, &isCopy); + if (pProcId) { + *pProcId = procId; + env->ReleasePrimitiveArrayCritical(processIdArray, pProcId, 0); + } + } + } + + jclass Class_java_io_FileDescriptor = env->FindClass("java/io/FileDescriptor"); + jmethodID init = env->GetMethodID(Class_java_io_FileDescriptor, + "<init>", "()V"); + jobject result = env->NewObject(Class_java_io_FileDescriptor, init); + + if (!result) { + LOG("Couldn't create a FileDescriptor."); + } else { + jfieldID descriptor = env->GetFieldID(Class_java_io_FileDescriptor, + "descriptor", "I"); + env->SetIntField(result, descriptor, ptm); + } + + return result; +} + +JNIEXPORT void Java_com_google_ase_Exec_setPtyWindowSize( + JNIEnv* env, jclass clazz, jobject fileDescriptor, jint row, jint col, + jint xpixel, jint ypixel) { + int fd; + struct winsize sz; + + fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + + if (env->ExceptionOccurred() != NULL) { + return; + } + + sz.ws_row = row; + sz.ws_col = col; + sz.ws_xpixel = xpixel; + sz.ws_ypixel = ypixel; + + ioctl(fd, TIOCSWINSZ, &sz); +} + +JNIEXPORT jint Java_com_google_ase_Exec_waitFor(JNIEnv* env, jclass clazz, + jint procId) { + int status; + waitpid(procId, &status, 0); + int result = 0; + if (WIFEXITED(status)) { + result = WEXITSTATUS(status); + } + return result; +} diff --git a/app/src/main/jni/com_google_ase_Exec.h b/app/src/main/jni/com_google_ase_Exec.h new file mode 100644 index 0000000..a2a6052 --- /dev/null +++ b/app/src/main/jni/com_google_ase_Exec.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class com_google_ase_Exec */ + +#ifndef _Included_com_google_ase_Exec +#define _Included_com_google_ase_Exec +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_google_ase_Exec + * Method: createSubprocess + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I)Ljava/io/FileDescriptor; + */ +JNIEXPORT jobject JNICALL Java_com_google_ase_Exec_createSubprocess + (JNIEnv *, jclass, jstring, jstring, jstring, jintArray); + +/* + * Class: com_google_ase_Exec + * Method: setPtyWindowSize + * Signature: (Ljava/io/FileDescriptor;IIII)V + */ +JNIEXPORT void JNICALL Java_com_google_ase_Exec_setPtyWindowSize + (JNIEnv *, jclass, jobject, jint, jint, jint, jint); + +/* + * Class: com_google_ase_Exec + * Method: waitFor + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_com_google_ase_Exec_waitFor + (JNIEnv *, jclass, jint); + +/* + * Class: com_google_ase_Exec + * Method: register + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_google_ase_Exec_register + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/app/src/main/res/anim/fade_out_delayed.xml b/app/src/main/res/anim/fade_out_delayed.xml new file mode 100644 index 0000000..20ca839 --- /dev/null +++ b/app/src/main/res/anim/fade_out_delayed.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:fromAlpha="1.0" + android:toAlpha="0.0" + android:duration="500" + android:startOffset="1000" + android:fillAfter="true" + /> diff --git a/app/src/main/res/anim/fade_stay_hidden.xml b/app/src/main/res/anim/fade_stay_hidden.xml new file mode 100644 index 0000000..e62ca8b --- /dev/null +++ b/app/src/main/res/anim/fade_stay_hidden.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:fromAlpha="0.0" + android:toAlpha="0.0" + android:duration="500" + android:fillAfter="true" + /> diff --git a/app/src/main/res/anim/keyboard_fade_in.xml b/app/src/main/res/anim/keyboard_fade_in.xml new file mode 100644 index 0000000..edd5b94 --- /dev/null +++ b/app/src/main/res/anim/keyboard_fade_in.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/accelerate_interpolator" + android:fromAlpha="0.0" + android:toAlpha="1.0" + android:duration="100" /> diff --git a/app/src/main/res/anim/keyboard_fade_out.xml b/app/src/main/res/anim/keyboard_fade_out.xml new file mode 100644 index 0000000..1f37d32 --- /dev/null +++ b/app/src/main/res/anim/keyboard_fade_out.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/accelerate_interpolator" + android:fromAlpha="1.0" + android:toAlpha="0.0" + android:duration="100" /> diff --git a/app/src/main/res/anim/slide_left_in.xml b/app/src/main/res/anim/slide_left_in.xml new file mode 100644 index 0000000..29a0048 --- /dev/null +++ b/app/src/main/res/anim/slide_left_in.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="300"/> + <!-- <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> --> +</set> diff --git a/app/src/main/res/anim/slide_left_out.xml b/app/src/main/res/anim/slide_left_out.xml new file mode 100644 index 0000000..9c46442 --- /dev/null +++ b/app/src/main/res/anim/slide_left_out.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromXDelta="0" android:toXDelta="-100%p" android:duration="300"/> + <!-- <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> --> +</set> diff --git a/app/src/main/res/anim/slide_right_in.xml b/app/src/main/res/anim/slide_right_in.xml new file mode 100644 index 0000000..0d52c9f --- /dev/null +++ b/app/src/main/res/anim/slide_right_in.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="300"/> + <!-- <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> --> +</set> diff --git a/app/src/main/res/anim/slide_right_out.xml b/app/src/main/res/anim/slide_right_out.xml new file mode 100644 index 0000000..ace4e9d --- /dev/null +++ b/app/src/main/res/anim/slide_right_out.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:fromXDelta="0" android:toXDelta="100%p" android:duration="300"/> + <!-- <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> --> +</set> diff --git a/app/src/main/res/color/blue.xml b/app/src/main/res/color/blue.xml new file mode 100644 index 0000000..981c3ef --- /dev/null +++ b/app/src/main/res/color/blue.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="#000" /> + <item android:state_focused="true" android:color="#000" /> + <item android:state_pressed="true" android:color="#000" /> + <item android:color="#88f" /> +</selector> diff --git a/app/src/main/res/color/green.xml b/app/src/main/res/color/green.xml new file mode 100644 index 0000000..388ea58 --- /dev/null +++ b/app/src/main/res/color/green.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="#000" /> + <item android:state_focused="true" android:color="#000" /> + <item android:state_pressed="true" android:color="#000" /> + <item android:color="#8f8" /> +</selector> diff --git a/app/src/main/res/color/red.xml b/app/src/main/res/color/red.xml new file mode 100644 index 0000000..b7e18cd --- /dev/null +++ b/app/src/main/res/color/red.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true" android:color="#000" /> + <item android:state_focused="true" android:color="#000" /> + <item android:state_pressed="true" android:color="#000" /> + <item android:color="#f00" /> +</selector> diff --git a/app/src/main/res/drawable-hdpi/icon.png b/app/src/main/res/drawable-hdpi/icon.png Binary files differnew file mode 100644 index 0000000..a0e8560 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/icon.png diff --git a/app/src/main/res/drawable-hdpi/notification_icon.png b/app/src/main/res/drawable-hdpi/notification_icon.png Binary files differnew file mode 100644 index 0000000..ecc44a3 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/notification_icon.png diff --git a/app/src/main/res/drawable-mdpi/icon.png b/app/src/main/res/drawable-mdpi/icon.png Binary files differnew file mode 100644 index 0000000..46d97d3 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/icon.png diff --git a/app/src/main/res/drawable-mdpi/notification_icon.png b/app/src/main/res/drawable-mdpi/notification_icon.png Binary files differnew file mode 100644 index 0000000..118d838 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/notification_icon.png diff --git a/app/src/main/res/drawable-xhdpi/icon.png b/app/src/main/res/drawable-xhdpi/icon.png Binary files differnew file mode 100644 index 0000000..891f59e --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/icon.png diff --git a/app/src/main/res/drawable-xhdpi/notification_icon.png b/app/src/main/res/drawable-xhdpi/notification_icon.png Binary files differnew file mode 100644 index 0000000..3b79360 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/notification_icon.png diff --git a/app/src/main/res/drawable-xxhdpi/icon.png b/app/src/main/res/drawable-xxhdpi/icon.png Binary files differnew file mode 100644 index 0000000..9bc6c3e --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/icon.png diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon.png b/app/src/main/res/drawable-xxhdpi/notification_icon.png Binary files differnew file mode 100644 index 0000000..0d8b9e1 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/notification_icon.png diff --git a/app/src/main/res/drawable-xxxhdpi/icon.png b/app/src/main/res/drawable-xxxhdpi/icon.png Binary files differnew file mode 100644 index 0000000..226eee9 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/icon.png diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon.png b/app/src/main/res/drawable-xxxhdpi/notification_icon.png Binary files differnew file mode 100644 index 0000000..2bafbb9 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/notification_icon.png diff --git a/app/src/main/res/drawable/button_ctrl.png b/app/src/main/res/drawable/button_ctrl.png Binary files differnew file mode 100644 index 0000000..dc4d786 --- /dev/null +++ b/app/src/main/res/drawable/button_ctrl.png diff --git a/app/src/main/res/drawable/button_esc.png b/app/src/main/res/drawable/button_esc.png Binary files differnew file mode 100644 index 0000000..5f0cfc6 --- /dev/null +++ b/app/src/main/res/drawable/button_esc.png diff --git a/app/src/main/res/drawable/button_keyboard.png b/app/src/main/res/drawable/button_keyboard.png Binary files differnew file mode 100644 index 0000000..9205d8b --- /dev/null +++ b/app/src/main/res/drawable/button_keyboard.png diff --git a/app/src/main/res/drawable/connected.xml b/app/src/main/res/drawable/connected.xml new file mode 100644 index 0000000..7b633d3 --- /dev/null +++ b/app/src/main/res/drawable/connected.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<selector + xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:state_checked="true" + android:drawable="@android:drawable/presence_online" /> + + <item + android:state_expanded="true" + android:drawable="@android:drawable/presence_busy" /> + + <item + android:drawable="@android:drawable/presence_invisible" /> + +</selector> diff --git a/app/src/main/res/drawable/highlight_disabled_pressed.9.png b/app/src/main/res/drawable/highlight_disabled_pressed.9.png Binary files differnew file mode 100644 index 0000000..807fcb5 --- /dev/null +++ b/app/src/main/res/drawable/highlight_disabled_pressed.9.png diff --git a/app/src/main/res/drawable/ic_btn_back.png b/app/src/main/res/drawable/ic_btn_back.png Binary files differnew file mode 100644 index 0000000..9615e67 --- /dev/null +++ b/app/src/main/res/drawable/ic_btn_back.png diff --git a/app/src/main/res/drawable/ic_btn_next.png b/app/src/main/res/drawable/ic_btn_next.png Binary files differnew file mode 100644 index 0000000..ea2a80e --- /dev/null +++ b/app/src/main/res/drawable/ic_btn_next.png diff --git a/app/src/main/res/drawable/pubkey.xml b/app/src/main/res/drawable/pubkey.xml new file mode 100644 index 0000000..1926d24 --- /dev/null +++ b/app/src/main/res/drawable/pubkey.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<selector + xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:state_checked="true" + android:drawable="@drawable/pubkey_unlocked" /> + + <item + android:drawable="@drawable/pubkey_locked" /> + +</selector>
\ No newline at end of file diff --git a/app/src/main/res/drawable/pubkey_locked.png b/app/src/main/res/drawable/pubkey_locked.png Binary files differnew file mode 100644 index 0000000..7df56b5 --- /dev/null +++ b/app/src/main/res/drawable/pubkey_locked.png diff --git a/app/src/main/res/drawable/pubkey_unlocked.png b/app/src/main/res/drawable/pubkey_unlocked.png Binary files differnew file mode 100644 index 0000000..eec4558 --- /dev/null +++ b/app/src/main/res/drawable/pubkey_unlocked.png diff --git a/app/src/main/res/layout-land/item_host.xml b/app/src/main/res/layout-land/item_host.xml new file mode 100644 index 0000000..9cde624 --- /dev/null +++ b/app/src/main/res/layout-land/item_host.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/content" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:padding="10dip" + > + + <ImageView + android:id="@android:id/icon" + android:src="@drawable/connected" + android:contentDescription="@string/image_description_connected" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:paddingTop="5dip" + /> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + > + + <TextView + android:id="@android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="end" + android:textAppearance="?android:attr/textAppearanceLarge" + /> + + <TextView + android:id="@android:id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="10dip" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + + + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout-port/item_host.xml b/app/src/main/res/layout-port/item_host.xml new file mode 100644 index 0000000..24d34ba --- /dev/null +++ b/app/src/main/res/layout-port/item_host.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/content" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:padding="10dip" + > + + <ImageView + android:id="@android:id/icon" + android:src="@drawable/connected" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:paddingTop="5dip" + android:contentDescription="@string/image_description_connected" + /> + + <TextView + android:id="@android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingRight="20dip" + android:singleLine="true" + android:ellipsize="end" + android:textAppearance="?android:attr/textAppearanceLarge" + /> + + <TextView + android:id="@android:id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_below="@android:id/text1" + /> + + +</RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/act_colors.xml b/app/src/main/res/layout/act_colors.xml new file mode 100644 index 0000000..3bda649 --- /dev/null +++ b/app/src/main/res/layout/act_colors.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:id="@+id/spinners"> + + <Spinner + android:id="@+id/fg" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:minHeight="60dp" + android:layout_weight="1" + android:gravity="center" + /> + + <Spinner + android:id="@+id/bg" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignTop="@id/fg" + android:layout_alignParentRight="true" + android:layout_toRightOf="@id/fg" + android:minHeight="60dp" + android:layout_weight="1" + android:gravity="center" + /> + </LinearLayout> + + <GridView + android:id="@+id/color_grid" + android:layout_height="fill_parent" + android:layout_width="fill_parent" + android:padding="10dp" + android:verticalSpacing="10dp" + android:horizontalSpacing="10dp" + android:numColumns="auto_fit" + android:columnWidth="60dp" + android:stretchMode="columnWidth" + android:gravity="center" + android:minHeight="60dp" + android:layout_below="@+id/spinners" + /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/act_console.xml b/app/src/main/res/layout/act_console.xml new file mode 100644 index 0000000..f7db787 --- /dev/null +++ b/app/src/main/res/layout/act_console.xml @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="#ff000000" + > + + <TextView + android:id="@android:id/empty" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:text="@string/terminal_no_hosts_connected" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center" + /> + + <ViewFlipper + android:id="@+id/console_flip" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + /> + + <RelativeLayout + android:id="@+id/console_password_group" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:padding="5dip" + android:background="#80000000" + android:fadingEdge="horizontal" + android:fadingEdgeLength="25dip" + android:visibility="gone" + > + + <TextView + android:id="@+id/console_password_instructions" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="fill_parent" + android:visibility="gone" + android:layout_marginBottom="5dip" + /> + + <EditText + android:id="@+id/console_password" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:password="true" + android:singleLine="true" + android:layout_below="@+id/console_password_instructions" + /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/console_boolean_group" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:padding="5dip" + android:background="#80000000" + android:fadingEdge="horizontal" + android:fadingEdgeLength="25dip" + android:visibility="gone" + > + + <TextView + android:id="@+id/console_prompt" + android:layout_height="wrap_content" + android:layout_width="fill_parent" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <Button + android:id="@+id/console_prompt_no" + android:text="@string/button_no" + android:paddingTop="5dip" + android:paddingBottom="10dip" + android:paddingLeft="40dip" + android:paddingRight="40dip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/console_prompt" + android:clickable="false" + /> + + <Button + android:id="@+id/console_prompt_yes" + android:text="@string/button_yes" + android:paddingTop="5dip" + android:paddingBottom="10dip" + android:paddingLeft="40dip" + android:paddingRight="40dip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toLeftOf="@+id/console_prompt_no" + android:layout_below="@+id/console_prompt" + /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/keyboard_group" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:padding="15dip" + android:visibility="gone"> + + <ImageView + android:id="@+id/button_keyboard" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" + android:src="@+drawable/button_keyboard" + android:contentDescription="@string/image_description_show_keyboard" + /> + + <ImageView + android:id="@+id/button_ctrl" + android:paddingRight="15dip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentBottom="true" + android:src="@+drawable/button_ctrl" + android:contentDescription="@string/image_description_toggle_control_character" + /> + + <ImageView + android:id="@+id/button_esc" + android:paddingRight="15dip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@+id/button_ctrl" + android:layout_alignParentBottom="true" + android:src="@+drawable/button_esc" + android:contentDescription="@string/image_description_send_escape_character" + /> + + </RelativeLayout> + +</RelativeLayout> diff --git a/app/src/main/res/layout/act_generatepubkey.xml b/app/src/main/res/layout/act_generatepubkey.xml new file mode 100644 index 0000000..171ffd8 --- /dev/null +++ b/app/src/main/res/layout/act_generatepubkey.xml @@ -0,0 +1,185 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:scrollbars="vertical" + android:layout_width="fill_parent"> + + <TableLayout + android:orientation="vertical" + android:layout_height="wrap_content" + android:paddingLeft="10dip" + android:paddingRight="10dip" + android:scrollbars="vertical" + android:layout_width="fill_parent"> + + <TableRow> + <TextView + android:text="@string/prompt_nickname" + android:paddingRight="10dip" + android:gravity="right|center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <EditText + android:id="@+id/nickname" + android:hint="@string/prompt_nickname_hint_pubkey" + android:layout_height="wrap_content" + android:singleLine="true" + android:layout_weight="1" /> + </TableRow> + + <TableRow> + <TextView + android:text="@string/prompt_type" + android:paddingRight="10dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="bottom|right" + android:layout_gravity="center_vertical" /> + + <RadioGroup + android:id="@+id/key_type" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:checkedButton="@+id/rsa"> + + <RadioButton + android:id="@+id/rsa" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="RSA" + android:paddingRight="3dip"/> + + <RadioButton + android:id="@+id/dsa" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="DSA" + android:paddingRight="3dip"/> + + <RadioButton + android:id="@+id/ec" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="EC" /> + </RadioGroup> + </TableRow> + + <TableRow> + <TextView + android:text="@string/prompt_bits" + android:paddingRight="10dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="right|center_vertical" /> + + <EditText + android:id="@+id/bits" + android:inputType="number" + android:layout_height="wrap_content" + android:text="1024" + android:singleLine="true" + android:layout_weight="1" /> + </TableRow> + + <SeekBar + android:layout_height="wrap_content" + android:id="@+id/bits_slider" + android:layout_width="fill_parent" + android:paddingBottom="10dip" + android:max="3328" + android:progress="256" /> + + <TextView + android:text="@string/prompt_password_can_be_blank" + android:gravity="left" + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> + + <TableRow> + <TextView + android:paddingRight="10dip" + android:gravity="right|center_vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/prompt_password" /> + + <EditText + android:id="@+id/password1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:password="true" + android:singleLine="true" + android:layout_weight="1" /> + </TableRow> + + <TableRow android:paddingBottom="6dip"> + <LinearLayout + android:paddingRight="10dip" + android:orientation="vertical" + android:gravity="right|center_vertical"> + + <TextView + android:gravity="right|bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="@string/prompt_password" /> + + <TextView + android:gravity="right|top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="@string/prompt_again" /> + </LinearLayout> + + <EditText + android:id="@+id/password2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:password="true" + android:singleLine="true" + android:layout_weight="1" /> + </TableRow> + + <CheckBox + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/unlock_at_startup" + android:text="@string/pubkey_load_on_start" /> + + <CheckBox + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/confirm_use" + android:text="@string/pubkey_confirm_use" /> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/save" + android:text="@string/pubkey_generate" + android:enabled="false" + android:layout_marginTop="6dip"/> + </TableLayout> +</ScrollView> diff --git a/app/src/main/res/layout/act_help.xml b/app/src/main/res/layout/act_help.xml new file mode 100644 index 0000000..833aefb --- /dev/null +++ b/app/src/main/res/layout/act_help.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + > + + <LinearLayout + android:id="@+id/topics" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + > + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/msg_version" + android:paddingTop="2dip" + android:textAppearance="?android:attr/textAppearanceSmall" + android:gravity="right" + android:paddingRight="2dip" + /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/help_intro" + android:paddingTop="2dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center_horizontal" + /> + + </LinearLayout> +</ScrollView> diff --git a/app/src/main/res/layout/act_help_topic.xml b/app/src/main/res/layout/act_help_topic.xml new file mode 100644 index 0000000..7123d63 --- /dev/null +++ b/app/src/main/res/layout/act_help_topic.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + + <org.connectbot.util.HelpTopicView + android:id="@+id/topic_text" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + /> + +</LinearLayout> diff --git a/app/src/main/res/layout/act_hostlist.xml b/app/src/main/res/layout/act_hostlist.xml new file mode 100644 index 0000000..3c6f4b1 --- /dev/null +++ b/app/src/main/res/layout/act_hostlist.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + + <RelativeLayout + android:id="@+id/quickconnect" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + > + + <Spinner + android:id="@+id/transport_selection" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_alignParentLeft="true" + /> + + <EditText + android:id="@+id/front_quickconnect" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:hint="username@hostname:port" + android:layout_toRightOf="@+id/transport_selection" + android:layout_alignBaseline="@+id/transport_selection" + android:inputType="textEmailAddress" + android:maxLines="1" + android:ellipsize="end" + android:focusableInTouchMode="true" + android:singleLine="true"/> + </RelativeLayout> + + + <ListView + android:id="@android:id/list" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_above="@+id/quickconnect" + /> + + <TextView + android:id="@android:id/empty" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:text="@string/list_host_empty" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center" + android:layout_above="@+id/quickconnect" + /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/act_portforwardlist.xml b/app/src/main/res/layout/act_portforwardlist.xml new file mode 100644 index 0000000..b8589d0 --- /dev/null +++ b/app/src/main/res/layout/act_portforwardlist.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + + <ListView android:id="@android:id/list" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1" + /> + + <TextView android:id="@android:id/empty" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:text="@string/portforward_list_empty" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center" + /> +</LinearLayout> diff --git a/app/src/main/res/layout/act_pubkeylist.xml b/app/src/main/res/layout/act_pubkeylist.xml new file mode 100644 index 0000000..ceabb52 --- /dev/null +++ b/app/src/main/res/layout/act_pubkeylist.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + + <ListView android:id="@android:id/list" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1" + /> + + <TextView android:id="@android:id/empty" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:text="@string/pubkey_list_empty" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center" + /> +</LinearLayout> diff --git a/app/src/main/res/layout/act_wizard.xml b/app/src/main/res/layout/act_wizard.xml new file mode 100644 index 0000000..c68a561 --- /dev/null +++ b/app/src/main/res/layout/act_wizard.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:gravity="right" + > + + <ViewFlipper + android:id="@+id/wizard_flipper" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1" + /> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="5dip" + > + + <Button + android:id="@+id/action_prev" + android:layout_width="120dip" + android:layout_height="wrap_content" + android:drawableLeft="@drawable/ic_btn_back" + android:text="Cancel" + android:gravity="center" + /> + + <Button + android:id="@+id/action_next" + android:layout_width="120dip" + android:layout_height="wrap_content" + android:drawableRight="@drawable/ic_btn_next" + android:text="Agree" + android:gravity="center" + /> + + </LinearLayout> + +</LinearLayout> diff --git a/app/src/main/res/layout/dia_changepassword.xml b/app/src/main/res/layout/dia_changepassword.xml new file mode 100644 index 0000000..1548b3d --- /dev/null +++ b/app/src/main/res/layout/dia_changepassword.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingLeft="10dip" + android:paddingRight="10dip" + > + + <TableRow android:id="@+id/old_password_prompt"> + <TextView + android:text="@string/prompt_old_password" + android:paddingRight="10dip" + android:gravity="right|center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="wrap_content" + /> + + <EditText + android:id="@+id/old_password" + android:layout_width="200dip" + android:layout_height="wrap_content" + android:singleLine="true" + android:password="true" + /> + </TableRow> + + <TableRow> + <TextView + android:text="@string/prompt_password" + android:paddingRight="10dip" + android:gravity="right|center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="wrap_content" + /> + + <EditText + android:id="@+id/password1" + android:layout_width="200dip" + android:layout_height="wrap_content" + android:singleLine="true" + android:password="true" + /> + </TableRow> + + <TableRow> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="right" + > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/prompt_password" + android:paddingRight="10dip" + android:gravity="right|bottom" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/prompt_again" + android:paddingRight="10dip" + android:gravity="right|top" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + </LinearLayout> + + <EditText + android:id="@+id/password2" + android:layout_width="200dip" + android:layout_height="wrap_content" + android:singleLine="true" + android:password="true" + /> + </TableRow> +</TableLayout> diff --git a/app/src/main/res/layout/dia_gatherentropy.xml b/app/src/main/res/layout/dia_gatherentropy.xml new file mode 100644 index 0000000..8c7153a --- /dev/null +++ b/app/src/main/res/layout/dia_gatherentropy.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="10dip" + android:paddingRight="10dip" + > + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/pubkey_touch_hint" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center"/> + + <org.connectbot.util.EntropyView + android:id="@+id/entropy" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="#666666ff" + android:layout_marginBottom="10dip" + android:layout_marginTop="10dip" + android:drawingCacheQuality="auto"/> +</LinearLayout> diff --git a/app/src/main/res/layout/dia_password.xml b/app/src/main/res/layout/dia_password.xml new file mode 100644 index 0000000..599af83 --- /dev/null +++ b/app/src/main/res/layout/dia_password.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="250dip" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="10dip" + > + + <EditText + android:id="@android:id/text1" + android:layout_width="250dip" + android:singleLine="true" + android:layout_height="wrap_content" + android:password="true" + android:hint="Password" + /> + +</LinearLayout> + diff --git a/app/src/main/res/layout/dia_portforward.xml b/app/src/main/res/layout/dia_portforward.xml new file mode 100644 index 0000000..9402eef --- /dev/null +++ b/app/src/main/res/layout/dia_portforward.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="fill_parent" + android:layout_width="fill_parent" + android:scrollbars="vertical" + > + + <TableLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingLeft="10dip" + android:paddingRight="10dip" + > + + <TableRow> + <TextView + android:text="@string/prompt_nickname" + android:paddingRight="10dip" + android:gravity="right|center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <EditText + android:id="@+id/nickname" + android:hint="Internal Webserver" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:layout_weight="1" + /> + </TableRow> + + <TableRow> + <TextView + android:text="@string/prompt_type" + android:paddingRight="10dip" + android:gravity="right|center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <Spinner android:id="@+id/portforward_type" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:entries="@array/list_portforward_types" + android:prompt="@string/prompt_type" + android:layout_weight="1" + /> + </TableRow> + + <TableRow> + <TextView + android:text="@string/prompt_source_port" + android:paddingRight="10dip" + android:gravity="right|center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <EditText + android:id="@+id/portforward_source" + android:hint="8080" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:inputType="number" + /> + </TableRow> + + <TableRow> + <TextView + android:text="@string/prompt_destination" + android:paddingRight="10dip" + android:gravity="right|center_vertical" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <EditText + android:id="@+id/portforward_destination" + android:hint="localhost:80" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:inputType="textEmailAddress" + /> + </TableRow> + </TableLayout> +</ScrollView> diff --git a/app/src/main/res/layout/dia_resize.xml b/app/src/main/res/layout/dia_resize.xml new file mode 100644 index 0000000..2269bd6 --- /dev/null +++ b/app/src/main/res/layout/dia_resize.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="10dip" + android:paddingRight="10dip" + > + + <EditText + android:id="@+id/width" + android:layout_width="100dip" + android:layout_height="wrap_content" + android:singleLine="true" + android:numeric="integer" + android:text="80"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="x" + android:paddingLeft="10dip" + android:paddingRight="10dip" + android:gravity="right|bottom" + android:textAppearance="?android:attr/textAppearanceLarge" + /> + + + <EditText + android:id="@+id/height" + android:layout_width="100dip" + android:layout_height="wrap_content" + android:singleLine="true" + android:numeric="integer" + android:text="25"/> +</LinearLayout> diff --git a/app/src/main/res/layout/item_portforward.xml b/app/src/main/res/layout/item_portforward.xml new file mode 100644 index 0000000..4a98f24 --- /dev/null +++ b/app/src/main/res/layout/item_portforward.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="left|center_vertical" + android:layout_width="fill_parent" + android:padding="10dip" + > + + <TextView + android:id="@android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="Tunnel Nickname" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true" + /> + + <TextView + android:id="@android:id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="Local port 8080 to 192.168.1.1:80" + android:layout_below="@android:id/text1" + /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/item_pubkey.xml b/app/src/main/res/layout/item_pubkey.xml new file mode 100644 index 0000000..7f4fa5f --- /dev/null +++ b/app/src/main/res/layout/item_pubkey.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/content" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:padding="10dip"> + + <ImageView + android:id="@android:id/icon1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/pubkey" + android:contentDescription="@string/image_description_key_is_locked" + android:layout_alignParentRight="true" + android:layout_alignParentTop="true" + android:layout_marginLeft="10dip"/> + + <TextView + android:id="@android:id/text1" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="Key Example" + android:layout_alignParentLeft="true" + android:bufferType="normal" android:layout_width="wrap_content" android:layout_toLeftOf="@android:id/icon1"/> + + <TextView + android:id="@android:id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="DSA 1024-bit" + android:layout_below="@android:id/text1" + /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/item_terminal.xml b/app/src/main/res/layout/item_terminal.xml new file mode 100644 index 0000000..9a8ff19 --- /dev/null +++ b/app/src/main/res/layout/item_terminal.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + + <TextView + android:id="@+id/terminal_overlay" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:background="#aa000000" + android:padding="10dip" + android:layout_centerInParent="true" + /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/wiz_eula.xml b/app/src/main/res/layout/wiz_eula.xml new file mode 100644 index 0000000..06b7c56 --- /dev/null +++ b/app/src/main/res/layout/wiz_eula.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + > + + <LinearLayout + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="10dip" + > + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="ConnectBot is a simple, powerful, open-source Secure Shell (SSH) client for your Android device." + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="10dip" + android:text="It can manage several SSH sessions, create secure tunnels, and copy/paste between other apps." + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="10dip" + android:text="@string/copyright_info" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="10dip" + android:autoLink="web" + android:text="You can view the full text at http://www.gnu.org/licenses/gpl-3.0.txt" + android:textSize="14sp" + android:textColor="#bebebe" + /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="10dip" + android:text="About and Credits" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="10dip" + android:autoLink="web" + android:text="Copyright \u00a9 2007-2008 Kenny Root http://the-b.org, Jeffrey Sharkey http://jsharkey.org\n\nBased in part on the Trilead SSH2 client, provided under a BSD-style license. Copyright \u00a9 2007 Trilead AG. http://www.trilead.com\n\nAlso based on JTA Telnet/SSH client, provided under the GPLv2 license. Copyright \u00a9 Matthias L. Jugel, Marcus Meiner 1996-2005. http://www.javassh.org\n\nAlso based in part on the JSOCKS library, provided under the GNU LGPL license. http://jsocks.sourceforge.net\n\nAlso based in part on JZlib provided under a BSD-style license. Copyright \u00a9 JCraft, Inc., 2000-20004 http://www.jcraft.com" + android:textSize="14sp" + android:textColor="#bebebe" + /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="10dip" + android:text="@string/msg_version" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + + </LinearLayout> + +</ScrollView> diff --git a/app/src/main/res/raw/bell.ogg b/app/src/main/res/raw/bell.ogg Binary files differnew file mode 100644 index 0000000..674f25d --- /dev/null +++ b/app/src/main/res/raw/bell.ogg diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml new file mode 100644 index 0000000..442f28f --- /dev/null +++ b/app/src/main/res/values-af/strings.xml @@ -0,0 +1,42 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="title_hosts_list">Gashere</string> + <string name="title_pubkey_list">Publieke Sleutels</string> + <string name="title_port_forwards_list">Poort aansturings</string> + <string name="title_host_editor">Redigeer Gasheer</string> + <string name="title_help">Hulp</string> + <string name="title_colors">Kleure</string> + <string name="resolve_connect">Verbind</string> + <string name="menu_insert">Voeg Gasheer by</string> + <string name="menu_delete">Verwyder Gasheer</string> + <string name="menu_preferences">Voorkeure</string> + <string name="help_about">Aangaande ConnectBot</string> + <string name="help_keyboard">Sleutelbord</string> + <string name="pubkey_generate">Genereer</string> + <string name="pubkey_import">Voer in</string> + <string name="pubkey_delete">Verwyder sleutel</string> + <string name="pubkey_gather_entropy">Versamel Ewekansigheid</string> + <string name="pubkey_touch_prompt">Raak hierdie boks om ewekansigheid te versamel: %1$d%% voltooi</string> + <string name="pubkey_touch_hint">Om ewekansigheid te verseker gedurende die sleutel generering, beweeg u vinger ewekansig oor die boks hieronder.</string> + <string name="pubkey_copy_private">Dupliseer privaat sleutel</string> + <string name="pubkey_copy_public">Dupliseer publieke sleutel</string> + <string name="pubkey_list_empty">Tap Menu to create\nor import key pairs.</string> + <string name="pubkey_unknown_format">Onbekende formaat</string> + <string name="pubkey_change_password">Verander wagwoord</string> + <string name="pubkey_list_pick">Kies van /sdcard</string> + <string name="pubkey_unlock">Ontsluit sleutel</string> + <string name="pubkey_memory_load">Laai na geheue</string> + <string name="pubkey_confirm_use">Bevestig voor gebruik</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="portforward_edit">Redigeer poort aansturing</string> + <string name="portforward_delete">Verwyder poort aanstuur</string> + <string name="prompt_nickname">Bynaam:</string> + <string name="prompt_source_port">Oorsprong poort:</string> + <string name="prompt_destination">Bestemming:</string> + <string name="prompt_old_password">Ou wagwoord:</string> + <string name="prompt_password">Wagwoord:</string> + <string name="prompt_type">Tipe:</string> + <string name="prompt_password_can_be_blank">Nota: wagwoord kan leeg gelos word</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> +</resources> diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..5fb33d6 --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,45 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">عميل SSH مفتوح المصدر و بسيط وقوي</string> + <string name="title_hosts_list">المخدمات</string> + <string name="title_pubkey_list">المفاتيح العامة</string> + <string name="title_port_forwards_list">تحويلات المنافذ</string> + <string name="title_host_editor">تعديل مخدم</string> + <string name="title_help">مساعدة</string> + <string name="title_colors">الألوان</string> + <string name="resolve_connect">اتصل</string> + <string name="menu_insert">اضافة مخدم</string> + <string name="menu_delete">حذف مخدم</string> + <string name="menu_preferences">التفضيلات</string> + <string name="help_intro">الرجاء اختار من الاسفل من اجل معلومات اكثر عن هذا الموضوع</string> + <string name="help_about">حول البرنامج</string> + <string name="help_keyboard">لوحة المفاتيح</string> + <string name="pubkey_generate">توليد</string> + <string name="pubkey_import">استيراد</string> + <string name="pubkey_delete">حذف المفتاح</string> + <string name="pubkey_touch_hint">للتاكد من العشوائية خلال توليد المفتاح , حرك اصبعك بشكل عشوائي فوق المربع في الاسفل</string> + <string name="pubkey_generating">جاري توليد المفتاح</string> + <string name="pubkey_copy_private">نسخ مفتاح خاص</string> + <string name="pubkey_copy_public">نسخ مفتاح عام</string> + <string name="pubkey_list_empty">"حدد القائمة لانشاء او استيراد المفاتيح"</string> + <string name="pubkey_unknown_format">صيغة غير معروفة</string> + <string name="pubkey_change_password">تغيير كلمة المرور</string> + <string name="pubkey_list_pick">اختار من بطاقة الذاكرة</string> + <string name="pubkey_import_parse_problem">مشكلة في تفسير المفتاح الخاص المستورد</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="button_yes">نعم</string> + <string name="button_no">ﻻ</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="no">ﻻ</string> + <string name="yes">نعم</string> + <string name="exceptions_submit_message">الظاهر بان البرنامج واجه مشكلة في اخر جلسة عمل. هل ترغب بارسال تقرير لمطورين البرنامج؟</string> + <string name="menu_colors_reset">إعادة ضبط</string> + <string name="app_is_running">البرنامج يعمل</string> + <string name="color_red">أحمر</string> + <string name="color_green">أخضر</string> + <string name="color_blue">أزرق</string> + <string name="color_gray">رمادي</string> + <string name="image_description_connected">متصل</string> + <string name="image_description_key_is_locked">المفتاح مقفل</string> + <string name="image_description_show_keyboard">عرض لوحة المفاتيح</string> +</resources> diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml new file mode 100644 index 0000000..1646a69 --- /dev/null +++ b/app/src/main/res/values-be/strings.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="pubkey_list_empty">Tap Menu to create\nor import key pairs.</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> +</resources> diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000..f87787b --- /dev/null +++ b/app/src/main/res/values-bg/strings.xml @@ -0,0 +1,59 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Прост, мощен SSH клиент с отворен код</string> + <string name="title_hosts_list">Хостове</string> + <string name="title_pubkey_list">Публични ключове</string> + <string name="title_port_forwards_list">Пренасочване на портове</string> + <string name="title_host_editor">Редактиране на хост</string> + <string name="title_help">Помощ</string> + <string name="title_colors">Цветове</string> + <string name="resolve_connect">Свързване</string> + <string name="menu_insert">Добавяне на хост</string> + <string name="menu_delete">Изтрий хост</string> + <string name="menu_preferences">Предпочитания</string> + <string name="help_about">Относно ConnectBot</string> + <string name="help_keyboard">Клавиатура</string> + <string name="pubkey_generate">Генериране</string> + <string name="pubkey_import">Импортиране</string> + <string name="pubkey_delete">Клавиш Delete</string> + <string name="pubkey_copy_private">Копира частния ключ</string> + <string name="pubkey_copy_public">Копира публичния ключ</string> + <string name="pubkey_list_empty">Tap Menu to create\nor import key pairs.</string> + <string name="pubkey_unknown_format">Непознат формат</string> + <string name="pubkey_change_password">Промени паролата</string> + <string name="pubkey_list_pick">Избери от /sdcard</string> + <string name="pubkey_import_parse_problem">Проблем при прочитането на импортирания частен ключ</string> + <string name="pubkey_unlock">Клавиш за отключване</string> + <string name="pubkey_memory_load">Зареди в паметта</string> + <string name="pubkey_memory_unload">Премахни от паметта</string> + <string name="pubkey_load_on_start">Зареди ключа при стартиране</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="portforward_edit">Променете пренасочването на портове</string> + <string name="portforward_delete">Изтрийте пренасочването на портове</string> + <string name="prompt_nickname">Псевдоним:</string> + <string name="prompt_destination">Назначение:</string> + <string name="prompt_old_password">Стара парола</string> + <string name="prompt_password">Парола:</string> + <string name="prompt_again">(отново)</string> + <string name="prompt_type">Тип:</string> + <string name="prompt_password_can_be_blank">Бележка: паролата не може да е празна</string> + <string name="prompt_bits">Битове</string> + <string name="alert_passwords_do_not_match_msg">Паролите не съвпадат!</string> + <string name="alert_wrong_password_msg">Грешна парола!</string> + <string name="alert_sdcard_absent">SD картата не е поставена</string> + <string name="button_add">Добавяне</string> + <string name="button_change">Промяна</string> + <string name="button_generate">Генерирай ключ</string> + <string name="button_resize">Промяна на размера на екрана</string> + <string name="alert_disconnect_msg">Връзката изгубена</string> + <string name="msg_copyright">Запазени права © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="list_keymode_none">Забраняване</string> + <string name="console_copy_done">Копирани %1$d байта в клипборда</string> + <string name="console_menu_close">Затваряне</string> + <string name="console_menu_copy">Копиране</string> + <string name="console_menu_paste">Поставяне</string> + <string name="console_menu_portforwards">Пренасочване на портове</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="list_host_disconnect">Прекъсване на връзката</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> +</resources> diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000..7696bb8 --- /dev/null +++ b/app/src/main/res/values-ca/strings.xml @@ -0,0 +1,209 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Client SSH simple, potent i lliure</string> + <string name="service_desc">Manté les connexions SSH i les claus públiques</string> + <string name="title_hosts_list">Servidors</string> + <string name="title_pubkey_list">Claus Públiques</string> + <string name="title_port_forwards_list">Redirecció de Ports</string> + <string name="title_host_editor">Edició del Servidor</string> + <string name="title_help">Ajuda</string> + <string name="title_colors">Colors</string> + <string name="resolve_connect">Connecta</string> + <string name="resolve_entropy">Recull Entropia</string> + <string name="menu_insert">Afegeix servidor</string> + <string name="menu_delete">Elimina servidor</string> + <string name="menu_preferences">Preferències</string> + <string name="help_intro">Si us plau, esculli un tema per a més informació en aquest assumpte en particular</string> + <string name="help_about">Sobre ConnectBot</string> + <string name="help_keyboard">Teclat</string> + <string name="pubkey_generate">Genera</string> + <string name="pubkey_import">Importa</string> + <string name="pubkey_delete">Elimina la clau</string> + <string name="pubkey_gather_entropy">Recull Entropia</string> + <string name="pubkey_touch_prompt">Toca aquesta caixa per obtenir aleatorietat: %1$d%% completat</string> + <string name="pubkey_touch_hint">Per assegurar l\'aleatorietat durant la generació de la clau, mou el dit aleatoriament per sobre la caixa de sota.</string> + <string name="pubkey_generating">Generant parell de claus...</string> + <string name="pubkey_copy_private">Còpia la clau privada</string> + <string name="pubkey_copy_public">Còpia la clau pública</string> + <string name="pubkey_list_empty">Pulsa Menu per crear\no importar parells de claus.</string> + <string name="pubkey_unknown_format">Format desconegut</string> + <string name="pubkey_change_password">Canvia la contrasenya</string> + <string name="pubkey_list_pick">Obté de /sdcard</string> + <string name="pubkey_import_parse_problem">Problema analitzant les claus privades importades</string> + <string name="pubkey_unlock">Desbloqueja clau</string> + <string name="pubkey_failed_add">Contrasenya incorrecta per la clau \'%1$s\'. Autenticació fallada</string> + <string name="pubkey_memory_load">Carrega a la memòria</string> + <string name="pubkey_memory_unload">Descarrega de la memòria</string> + <string name="pubkey_load_on_start">Carrega la clau a l\'inici</string> + <string name="pubkey_confirm_use">Confirmar abans d\'usar</string> + <string name="portforward_list_empty">Pulsa Menu per crear\nredireccions de ports.</string> + <string name="portforward_edit">Edita redireccions de ports</string> + <string name="portforward_delete">Elimina redireccions de ports</string> + <string name="prompt_nickname">Sobrenom:</string> + <string name="prompt_nickname_hint_pubkey">La meva clau</string> + <string name="prompt_source_port">Port d\'origen:</string> + <string name="prompt_destination">Destí:</string> + <string name="prompt_old_password">Contrasenya anterior:</string> + <string name="prompt_password">Contrasenya:</string> + <string name="prompt_again">(altre cop)</string> + <string name="prompt_type">Tipus:</string> + <string name="prompt_password_can_be_blank">Nota: la contrasenya pot ser en blanc</string> + <string name="prompt_bits">Bits</string> + <string name="prompt_pubkey_password">Contrasenya per la clau \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Permet al servidor remot\nutilitzar la clau \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">ALERTA: LA IDENTIFICACIÓ DEL SERVIDOR REMOT HA CANVIAT!</string> + <string name="host_verification_failure_warning">ES POSSIBLE QUE ALGÚ ESTIGUI FENT ALGUNA COSA LLETGA!\nAlgú pot estar escoltant la teva comunicació (atac home-al-mig)!\nTambé és possible que simplement el servidor hagi canviat la clau.</string> + <string name="prompt_host_disconnected">Servidor desconnectat.\nTancar sessió?</string> + <string name="prompt_continue_connecting">Estàs segur que vols\ncontinuar connectant?</string> + <string name="host_authenticity_warning">La autenticació amb el servidor \'%1$s\' no es pot establir.</string> + <string name="host_fingerprint">Servidor %1$s empremta de la clau és %2$s</string> + <string name="alert_passwords_do_not_match_msg">Les contrasenyes no coincideixen!</string> + <string name="alert_wrong_password_msg">La contrasenya no és correcta.</string> + <string name="alert_key_corrupted_msg">Les claus privades semblen malmèses!</string> + <string name="alert_sdcard_absent">La targeta SD no es troba present!</string> + <string name="button_add">Afegeix</string> + <string name="button_change">Canvia</string> + <string name="button_generate">Genera clau</string> + <string name="button_resize">Redimensiona</string> + <string name="alert_disconnect_msg">Connexió perduda</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulador del terminal</string> + <string name="pref_emulation_title">Mode d\'emulació</string> + <string name="pref_emulation_summary">Mode d\'emulació de terminal per l\'ús de connexions PTY</string> + <string name="pref_scrollback_title">Tamany de la història</string> + <string name="pref_scrollback_summary">Tamany de la memòria per mantenir la història de cada consola</string> + <string name="pref_ui_category">Interfície d\'usuari</string> + <string name="pref_rotation_title">Mode de rotació</string> + <string name="pref_rotation_summary">Com canvia la orientació quan el teclat s\'obre o es tanca</string> + <string name="pref_fullscreen_title">Pantalla completa</string> + <string name="pref_fullscreen_summary">Amaga la barra d\'estat en consola</string> + <string name="pref_memkeys_title">Recorda les claus en memòria</string> + <string name="pref_memkeys_summary">Manté les claus descarregadesen memòria fins que el servei finalitzi</string> + <string name="pref_conn_persist_title">Connexions persistents</string> + <string name="pref_conn_persist_summary">Manté les connexions mentre l\'aplicació és en segon pla</string> + <string name="pref_keymode_title">Drecera de carpetes</string> + <string name="pref_keymode_summary">Escull com usar Alt per \'/\' i Shift per Tab</string> + <string name="pref_camera_title">Drecera de càmera</string> + <string name="pref_camera_summary">Escull quina acció fer quan el botó de la càmera es clica</string> + <string name="pref_keepalive_title">Manté la pantalla encesa</string> + <string name="pref_keepalive_summary">Evita que la pantalla s\'apagui mentre s\'usa la consola</string> + <string name="pref_wifilock_title">Manté activa la Wi-Fi</string> + <string name="pref_wifilock_summary">Evita que la Wi-Fi s\'apagui mentre hi ha una sessió activa</string> + <string name="pref_bumpyarrows_title">Cursors vibrants</string> + <string name="pref_bumpyarrows_summary">Vibra quan s\'envien els cursors de direcció amb la rodeta; útil per connexions lentes</string> + <string name="pref_bell_category">So del Terminal</string> + <string name="pref_bell_title">So activat</string> + <string name="pref_bell_volume_title">Volum del so</string> + <string name="pref_bell_vibrate_title">Vibra en sonar</string> + <string name="pref_bell_notification_title">Notificacions en rerefons</string> + <string name="pref_bell_notification_summary">Envia notificacions quan un terminal actiu al rerefons envia una senyal de so.</string> + <string name="list_keymode_right">Utilitza les tecles de la dreta</string> + <string name="list_keymode_left">Utilitza les tecles de l\'esquerra</string> + <string name="list_keymode_none">Deshabilita</string> + <string name="list_pubkeyids_none">No usis claus</string> + <string name="list_pubkeyids_any">Fes servir qualsevol clau desbloquejada</string> + <string name="hostpref_nickname_title">Sobrenom</string> + <string name="hostpref_color_title">Color de la categoria</string> + <string name="hostpref_fontsize_title">Mida de la lletra (pt)</string> + <string name="hostpref_pubkeyid_title">Usa autenticació de claus públiques</string> + <string name="hostpref_authagent_title">Usa agent d\'autenticació SSH</string> + <string name="hostpref_postlogin_title">Automatització post-entrada</string> + <string name="hostpref_postlogin_summary">Comandes a executar en el servidor remot un cop autenticat.</string> + <string name="hostpref_compression_title">Compressió</string> + <string name="hostpref_compression_summary">Això pot ajudar per xarxes lentes</string> + <string name="hostpref_wantsession_title">Inicia una sessió de shell</string> + <string name="hostpref_wantsession_summary">Deshabilita aquesta preferència per només usar redireccionament de ports</string> + <string name="hostpref_stayconnected_title">Manté connectat</string> + <string name="hostpref_stayconnected_summary">Intenta reconnectar al servidor si es desconnecta</string> + <string name="hostpref_delkey_title">Tecla DEL</string> + <string name="hostpref_delkey_summary">El codi enviat al pulsar la tecla DEL</string> + <string name="hostpref_encoding_title">Codificació</string> + <string name="hostpref_encoding_summary">Codificació de caràcters per el servidor</string> + <string name="hostpref_connection_category">Paràmetres de connexió</string> + <string name="hostpref_username_title">Nom d\'usuari</string> + <string name="hostpref_hostname_title">Servidor</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Mai connectat</string> + <string name="bind_minutes">Fa %1$s minuts</string> + <string name="bind_hours">Fa %1$s hores</string> + <string name="bind_days">Fa %1$s dies</string> + <string name="console_copy_done">Copiats %1$d bytes al porta-retalls</string> + <string name="console_copy_start">Toca i arrosega\no fes servir les tecles de direcció\nper escollir l\'àrea a copiar</string> + <string name="console_menu_close">Tanca</string> + <string name="console_menu_copy">Copia</string> + <string name="console_menu_paste">Enganxa</string> + <string name="console_menu_portforwards">Redireccionament de ports</string> + <string name="console_menu_resize">Força el tamany</string> + <string name="console_menu_urlscan">Llistat de URL</string> + <string name="button_yes">Sí</string> + <string name="button_no">No</string> + <string name="portforward_local">Local</string> + <string name="portforward_remote">Remot</string> + <string name="portforward_dynamic">Dinàmic (SOCKS)</string> + <string name="portforward_pos">Crea redireccionament de ports</string> + <string name="portforward_done">Redireccionament creat correctament</string> + <string name="portforward_problem">Problemes creant el redireccionament de ports, potser s\'usen ports per sota del 1024 o el port és en ús actualment?</string> + <string name="portforward_menu_add">Afegeix redireccionament de ports</string> + <string name="hint_userhost">usuari\@servidor</string> + <string name="list_format_error">Usa el format %1$s</string> + <string name="format_username">Usuari</string> + <string name="format_hostname">servidor</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Gestiona Claus</string> + <string name="list_menu_sortcolor">Ordena per color</string> + <string name="list_menu_sortname">Ordena per nom</string> + <string name="list_menu_settings">Arrenjaments</string> + <string name="list_host_disconnect">Desconnecta</string> + <string name="list_host_edit">Edita el servidor</string> + <string name="list_host_portforwards">Edita el redireccionament de ports</string> + <string name="list_host_delete">Elimina servidors</string> + <string name="list_host_empty">Utilitza el quadre de connexió ràpida\nde sota per connectar al servidor</string> + <string name="list_rotation_default">Per defecte</string> + <string name="list_rotation_land">Força horitzontal</string> + <string name="list_rotation_port">Força vertical</string> + <string name="list_rotation_auto">Automàtic</string> + <string name="list_camera_ctrlaspace">Ctrl+A llavors Espai</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Res</string> + <string name="list_delkey_backspace">Retrocés</string> + <string name="list_delkey_del">Suprimeix</string> + <string name="delete_message">Estas segur que vols esborrar \'%1$s\'?</string> + <string name="delete_pos">Sí, esborra</string> + <string name="delete_neg">Cancel·la</string> + <string name="wizard_agree">Accepto</string> + <string name="wizard_next">Següent</string> + <string name="wizard_back">Enrere</string> + <string name="terminal_no_hosts_connected">No hi ha servidors connectats actualment</string> + <string name="terminal_connecting">Connectant a %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Clau de servidor \'%1$s\' verificada: %2$s</string> + <string name="terminal_failed">Verificació de la clau del servidor errònia.</string> + <string name="terminal_using_s2c_algorithm">Algoritme servidor-a-client: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algoritme client-a-servidor: %1$s %2$s</string> + <string name="terminal_using_algorithm">Usant algoritme: %1$s %2$s</string> + <string name="terminal_auth">Intentant autenticar</string> + <string name="terminal_auth_pass">Intentant autenticació de \'contrasenya\'</string> + <string name="terminal_auth_pass_fail">Autenticació amb \'contrasenya\' errònia</string> + <string name="terminal_auth_pubkey_any">Intentant autenticació amb \'claus públiques\' amb totes les claus en memòria</string> + <string name="terminal_auth_pubkey_invalid">La clau pública seleccionada és invàlida, intenta reseleccionar la clau en l\'editor del servidor</string> + <string name="terminal_auth_pubkey_specific">Intentant autenticació amb \'claus públiques\' amb una clau específica</string> + <string name="terminal_auth_pubkey_fail">Mètode d\'autenticació \'claus públiques\' amb clau \'%1$s\' fallit</string> + <string name="terminal_auth_ki">Intentant autenticació \'teclat-interactiu\'</string> + <string name="terminal_auth_ki_fail">Mètode d\'autenticació \'teclat-interactiu\' fallit.</string> + <string name="terminal_auth_fail">[El servidor no admet autenticació amb \'contrasenya\' o \'teclat-interactiu\'.]</string> + <string name="terminal_no_session">La sessió no s\'iniciarà degut a la preferencia de servidor.</string> + <string name="terminal_enable_portfoward">Activa el redireccionament de ports: %1$s</string> + <string name="local_shell_unavailable">Fallada! El shell local no està disponible en aquest telèfon.</string> + <string name="notification_text">%1$s necessita la teva atenció.</string> + <string name="no">No</string> + <string name="with_confirmation">Amb confirmació</string> + <string name="yes">Sí</string> + <string name="exceptions_submit_message">Sembla que ConnectBot ha tingut un problema l\'últim cop que s\'ha iniciat. Enviar la informació de l\'error als desenvolupadors de ConnectBot?</string> + <string name="menu_colors_reset">Reinicia</string> + <string name="app_is_running">El ConnectBot està en marxa</string> + <string name="color_red">vermell</string> + <string name="color_green">verd</string> + <string name="color_blue">blau</string> + <string name="color_gray">gris</string> +</resources> diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000..6bc0b87 --- /dev/null +++ b/app/src/main/res/values-cs/strings.xml @@ -0,0 +1,197 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Jednoduchý, všestranný, open-source SSH klient</string> + <string name="service_desc">Správa SSH spojení a nahraných klíčů</string> + <string name="title_hosts_list">Hostitelé</string> + <string name="title_pubkey_list">Veřejné klíče</string> + <string name="title_port_forwards_list">Přesměrování portu</string> + <string name="title_host_editor">Upravit hostitele</string> + <string name="title_help">Nápověda</string> + <string name="title_colors">Barvy</string> + <string name="resolve_connect">Připojit</string> + <string name="resolve_entropy">Získat entropii</string> + <string name="menu_insert">Přidat hostitele</string> + <string name="menu_delete">Smazat hostitele</string> + <string name="menu_preferences">Nastavení</string> + <string name="help_intro">Prosím, vyberte následující téma pro více informací na daný subjekt.</string> + <string name="help_about">O aplikaci ConnectBot</string> + <string name="help_keyboard">Klávesnice</string> + <string name="pubkey_generate">Vygenerovat</string> + <string name="pubkey_import">Importovat</string> + <string name="pubkey_delete">Smazat klíč</string> + <string name="pubkey_gather_entropy">Získávání entropie</string> + <string name="pubkey_touch_prompt">Dotkněte se plochy pro získání náhodnosti: %1$d%% hotovo</string> + <string name="pubkey_touch_hint">Pro zajištění náhodnosti během generování klíče pohybujte prstem náhodně po ploše níže.</string> + <string name="pubkey_generating">Generování páru klíčů..</string> + <string name="pubkey_copy_private">Kopírovat soukromý klíč</string> + <string name="pubkey_copy_public">Kopírovat veřejný klíč</string> + <string name="pubkey_list_empty">Zvolte \"Menu\" pro vytvoření\nnebo import párů klíčů.</string> + <string name="pubkey_unknown_format">Neznámý formát</string> + <string name="pubkey_change_password">Změnit heslo</string> + <string name="pubkey_list_pick">Vyberte z /sdcard</string> + <string name="pubkey_import_parse_problem">Problém s importem soukromého klíče</string> + <string name="pubkey_unlock">Odemknout klíč</string> + <string name="pubkey_failed_add">Špatné heslo pro klíč \'%1$s\'. Ověření selhalo.</string> + <string name="pubkey_memory_load">Nahrát do paměti</string> + <string name="pubkey_memory_unload">Odstranit z paměti</string> + <string name="pubkey_load_on_start">Nahrát klíč při startu</string> + <string name="pubkey_confirm_use">Potvrdit před použitím</string> + <string name="portforward_list_empty">Zvolte \"Menu\" pro vytvoření\npřesměrování portů.</string> + <string name="portforward_edit">Upravit přesměrování portů</string> + <string name="portforward_delete">Smazat přesměrování portů</string> + <string name="prompt_nickname">Přezdívka:</string> + <string name="prompt_nickname_hint_pubkey">Můj pracovní klíč</string> + <string name="prompt_source_port">Zdrojový port:</string> + <string name="prompt_destination">Cíl:</string> + <string name="prompt_old_password">Původní heslo:</string> + <string name="prompt_password">Heslo:</string> + <string name="prompt_again">(znovu)</string> + <string name="prompt_type">Typ:</string> + <string name="prompt_password_can_be_blank">Poznámka: heslo může být prázdné</string> + <string name="prompt_bits">Počet bitů:</string> + <string name="prompt_pubkey_password">Heslo pro klíč \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Povolit vzdálenému počítači\npoužít klíč \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">VAROVÁNÍ: IDENTIFIKACE VZDÁLENÉHO HOSTA SE ZMĚNILA!</string> + <string name="host_verification_failure_warning">JE MOŽNÉ ŽE NĚKDO PROVÁDÍ NĚCO NEKALÉHO!\nNěkdo vás může odposlouchávat (man-in-the-middle)!\nTaké je možné že se klíč počítače právě změnil.</string> + <string name="prompt_host_disconnected">Host se odpojil.\nUkončit sezení?</string> + <string name="prompt_continue_connecting">Opravdu chcete\npokračovat v připojování?</string> + <string name="host_authenticity_warning">Nelze ověřit autenticitu počítače \'%1$s\'.</string> + <string name="host_fingerprint">Otisk klíče počítače %1$s je %2$s</string> + <string name="alert_passwords_do_not_match_msg">Hesla se neshodují!</string> + <string name="alert_wrong_password_msg">Špatné heslo!</string> + <string name="alert_key_corrupted_msg">Soukromý klíč vypadá poškozeně!</string> + <string name="alert_sdcard_absent">Není vložena SD karta!</string> + <string name="button_add">Přidat</string> + <string name="button_change">Změnit</string> + <string name="button_generate">Generovat klíč</string> + <string name="button_resize">Změnit velikost</string> + <string name="alert_disconnect_msg">Spojení ztraceno</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulace terminálu</string> + <string name="pref_emulation_title">Režim emulace</string> + <string name="pref_emulation_summary">Režim emulace terminálu použitý při PTY spojeních</string> + <string name="pref_scrollback_title">Velikost historie</string> + <string name="pref_scrollback_summary">Velikost historie uložená v paměti pro každou konzoli</string> + <string name="pref_ui_category">Uživatelské rozhraní</string> + <string name="pref_rotation_title">Režim rotace</string> + <string name="pref_rotation_summary">Jak změnit rotaci když se zobrazí / skryje klávesnice</string> + <string name="pref_fullscreen_title">Celá obrazovka</string> + <string name="pref_fullscreen_summary">Skrýt stavový řádek když je zobrazena konzole</string> + <string name="pref_memkeys_title">Uchovávat klíče v paměti</string> + <string name="pref_memkeys_summary">Ponechat odemčené klíče v paměti dokud není ukončena služba na pozadí</string> + <string name="pref_conn_persist_title">Trvalá spojení</string> + <string name="pref_conn_persist_summary">Vynutit spojení i v pozadí</string> + <string name="pref_keymode_title">Zkratky adresářů</string> + <string name="pref_keymode_summary">Volba použití kláves Alt pro znak \"/\" a Shift pro tabulátor</string> + <string name="pref_camera_title">Zkratka fotoaparátu</string> + <string name="pref_camera_summary">Vyberte kterou zkratku použít když je stisknuto tlačítko fotoaparátu</string> + <string name="pref_keepalive_title">Ponechat zapnutou obrazovku</string> + <string name="pref_keepalive_summary">Zabránit obrazovce ve vypnutí při práci v konzoli</string> + <string name="pref_wifilock_title">Ponechat Wi-Fi aktivní</string> + <string name="pref_wifilock_summary">Zabránit Wi-Fi ve vypnutí když je sezení aktivní</string> + <string name="pref_bumpyarrows_title">Hmatová odezva šipek</string> + <string name="pref_bumpyarrows_summary">Vibrovat při posílání kurzorových kláves z trackballu; užitečná pro špatná spojení</string> + <string name="pref_bell_category">Zvonek terminálu</string> + <string name="pref_bell_title">Zvukové upozornění</string> + <string name="pref_bell_volume_title">Hlasitost zvonku</string> + <string name="pref_bell_vibrate_title">Vibrovat při zvonku</string> + <string name="pref_bell_notification_title">Upozornění v pozadí</string> + <string name="pref_bell_notification_summary">Zobrazit upozornění když terminál běžící v pozadí zazvoní zvonkem</string> + <string name="list_keymode_right">Použít klávesy napravo</string> + <string name="list_keymode_left">Použít klávesy nalevo</string> + <string name="list_keymode_none">Vypnout</string> + <string name="list_pubkeyids_none">Nepoužívat klíče</string> + <string name="list_pubkeyids_any">Použít jakýkoliv odemčený klíč</string> + <string name="hostpref_nickname_title">Přezdívka</string> + <string name="hostpref_color_title">Barevná kategorie</string> + <string name="hostpref_fontsize_title">Velikost fontu (pt)</string> + <string name="hostpref_pubkeyid_title">Použít autentizaci veřejným klíčem</string> + <string name="hostpref_authagent_title">Použít SSH autorizačního agenta</string> + <string name="hostpref_postlogin_title">Automatizace po přihlášení</string> + <string name="hostpref_postlogin_summary">Příkazy které se spustí na vzdáleném serveru po přihlášení</string> + <string name="hostpref_compression_title">Komprese</string> + <string name="hostpref_compression_summary">Může pomoct na pomalejších sítích</string> + <string name="hostpref_wantsession_title">Spustit sezení shellu</string> + <string name="hostpref_wantsession_summary">Vypněte tuto možnost pokud pouze přesměrováváte porty</string> + <string name="hostpref_stayconnected_title">Zůstat připojený</string> + <string name="hostpref_stayconnected_summary">Zkusit se znovu připojit při odpojení</string> + <string name="hostpref_delkey_title">Klávesa DEL</string> + <string name="hostpref_delkey_summary">Kód klávesy odeslaný při stisku DEL</string> + <string name="hostpref_encoding_title">Kódování</string> + <string name="hostpref_encoding_summary">Kódování znaků na hostovi</string> + <string name="hostpref_connection_category">Nastavení připojení</string> + <string name="hostpref_username_title">Uživatelské jméno</string> + <string name="bind_never">Nikdy nepřipojen</string> + <string name="bind_minutes">Před %1$s minutami</string> + <string name="bind_hours">Před %1$s hodinami</string> + <string name="bind_days">Před %1$s dny</string> + <string name="console_copy_done">Zkopírováno %1$d bajtů do schránky</string> + <string name="console_copy_start">Stiskněte a táhněte\nnebo použijte směrový ovladač\npro výběr oblasti ke zkopírování</string> + <string name="console_menu_close">Zavřít</string> + <string name="console_menu_copy">Kopírovat</string> + <string name="console_menu_paste">Vložit</string> + <string name="console_menu_portforwards">Přesměrování portů</string> + <string name="console_menu_resize">Vynutit velikost</string> + <string name="console_menu_urlscan">Najít URL</string> + <string name="button_yes">Ano</string> + <string name="button_no">Ne</string> + <string name="portforward_local">Místní</string> + <string name="portforward_remote">Vzdálený</string> + <string name="portforward_dynamic">Dynamický (SOCKS)</string> + <string name="portforward_pos">Vytvořit přesměrování portu</string> + <string name="portforward_done">Přesměrování portu úspěšně vytvořeno</string> + <string name="portforward_menu_add">Přidat přesměrování portu</string> + <string name="hint_userhost">user\@hostname</string> + <string name="list_format_error">Použijte formát %1$s</string> + <string name="format_username">uživatelské jméno</string> + <string name="format_hostname">jméno hosta</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Spravovat veřejné klíče</string> + <string name="list_menu_sortcolor">Seřadit podle barvy</string> + <string name="list_menu_sortname">Seřadit podle jména</string> + <string name="list_menu_settings">Nastavení</string> + <string name="list_host_disconnect">Odpojit</string> + <string name="list_host_edit">Upravit hosta</string> + <string name="list_host_portforwards">Upravit přesměrování portů</string> + <string name="list_host_delete">Smazat hosta</string> + <string name="list_host_empty">Použijte pole rychlého připojení\nníže pro připojení k hostovi.</string> + <string name="list_rotation_default">Standardní</string> + <string name="list_rotation_land">Vynutit na šířku</string> + <string name="list_rotation_port">Vynutit na výšku</string> + <string name="list_rotation_auto">Automaticky</string> + <string name="list_camera_ctrlaspace">Ctrl+A potom mezerník</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Nic</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Delete</string> + <string name="delete_message">Určitě chcete smazat \'%1$s\'?</string> + <string name="delete_pos">Ano, smazat</string> + <string name="delete_neg">Zrušit</string> + <string name="wizard_agree">Souhlasím</string> + <string name="wizard_next">Další</string> + <string name="wizard_back">Zpět</string> + <string name="terminal_no_hosts_connected">Žádný host není připojený</string> + <string name="terminal_connecting">Připojování k %1$s:%2$d přes %3$s</string> + <string name="terminal_failed">Selhalo ověření klíče hosta</string> + <string name="terminal_using_s2c_algorithm">Algoritmus server-klient: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algoritmus klient-server: %1$s %2$s</string> + <string name="terminal_using_algorithm">Použitý algoritmus: %1$s %2$s</string> + <string name="terminal_auth">Pokus o autentizaci</string> + <string name="terminal_auth_pubkey_invalid">Vybraný veřejný klič je neplatný, zkuste jej znovu vybrat při úpravě hosta</string> + <string name="terminal_no_session">Sezení nebude spuštěno kvůli nastavení hosta.</string> + <string name="terminal_enable_portfoward">Povolit přesměrování portu: %1$s</string> + <string name="local_shell_unavailable">Selhání! Místní shell není na tomto telefonu podporován</string> + <string name="notification_text">%1$s žádá pozornost.</string> + <string name="no">Ne</string> + <string name="with_confirmation">S potvrzením</string> + <string name="yes">Ano</string> + <string name="exceptions_submit_message">Vypadá to že měl ConnectBot problém když naposledy běžel. Odeslat hlášení o chybě vývojářům ConnectBota?</string> + <string name="menu_colors_reset">Obnovit</string> + <string name="app_is_running">ConnectBot běží</string> + <string name="color_red">červená</string> + <string name="color_green">zelená</string> + <string name="color_blue">modrá</string> + <string name="color_gray">šedá</string> +</resources> diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml new file mode 100644 index 0000000..5e72bcb --- /dev/null +++ b/app/src/main/res/values-da/strings.xml @@ -0,0 +1,209 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Enkel, kraftfuld, open source SSH klient</string> + <string name="service_desc">Opretholder SSH forbindelser og indlæste offentlige nøgler</string> + <string name="title_hosts_list">Værter</string> + <string name="title_pubkey_list">Offentlige nøgler</string> + <string name="title_port_forwards_list">Port-videresendelser</string> + <string name="title_host_editor">Rediger vært</string> + <string name="title_help">Hjælp</string> + <string name="title_colors">Farver</string> + <string name="resolve_connect">Tilslut</string> + <string name="resolve_entropy">Indsamler entropi</string> + <string name="menu_insert">Tilføj vært</string> + <string name="menu_delete">Fjern vært</string> + <string name="menu_preferences">Egenskaber</string> + <string name="help_intro">Vælg et emne nedenfor for yderligere information.</string> + <string name="help_about">Om ConnectBot</string> + <string name="help_keyboard">Tastatur</string> + <string name="pubkey_generate">Opret</string> + <string name="pubkey_import">Importér</string> + <string name="pubkey_delete">Slet nøgle</string> + <string name="pubkey_gather_entropy">Indsamler entropi</string> + <string name="pubkey_touch_prompt">Berør denne boks for at generere tilfældighed: %1$d%% færdig</string> + <string name="pubkey_touch_hint">For at garantere tilfældig data til oprettelse af nøglen, skal du flytte din finger tilfældigt indenfor boksen nedenfor.</string> + <string name="pubkey_generating">Opretter nøglepar...</string> + <string name="pubkey_copy_private">Kopier privat nøgle</string> + <string name="pubkey_copy_public">Kopier offentlig nøgle</string> + <string name="pubkey_list_empty">Tryk Menu for at oprette\nog importere nøglepar.</string> + <string name="pubkey_unknown_format">Ukendt format</string> + <string name="pubkey_change_password">Ændre adgangskode</string> + <string name="pubkey_list_pick">Vælg fra /sdcard</string> + <string name="pubkey_import_parse_problem">Fejl ved indlæsning af privat nøgle</string> + <string name="pubkey_unlock">Lås nøgle op</string> + <string name="pubkey_failed_add">Forkert adgangskode for nøglen \'%1$s\'. Autorisering mislykkedes.</string> + <string name="pubkey_memory_load">Indlæs i hukommelse</string> + <string name="pubkey_memory_unload">Fjern fra hukommelse</string> + <string name="pubkey_load_on_start">Indlæs nøgle ved start</string> + <string name="pubkey_confirm_use">Bekræft før brug</string> + <string name="portforward_list_empty">Tryk Menu for at oprette\nport-videresendelser.</string> + <string name="portforward_edit">Rediger port-videresendelse</string> + <string name="portforward_delete">Slet port-videresendelse</string> + <string name="prompt_nickname">Kaldenavn:</string> + <string name="prompt_nickname_hint_pubkey">Min arbejdsnøgle</string> + <string name="prompt_source_port">Kildeport:</string> + <string name="prompt_destination">Destination:</string> + <string name="prompt_old_password">Gammel adgangskode:</string> + <string name="prompt_password">Adgangskode:</string> + <string name="prompt_again">(igen)</string> + <string name="prompt_type">Type:</string> + <string name="prompt_password_can_be_blank">Bemærk: Adgangskoden kan være blank</string> + <string name="prompt_bits">Bit:</string> + <string name="prompt_pubkey_password">Adgangskode for nøglen \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Tillad fjernvært at\nbruge nøglen \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">ADVARSEL: FJERNVÆRTENS IDENTIFIKATION ER ÆNDRET!</string> + <string name="host_verification_failure_warning">DET ER MULIGT AT NOGEN FORETAGER SIG BESKIDTE HANDLINGER!\nDer er mulighed for at nogen aflytter dig (man-in-the-middle angreb)!\nDet er også muligt at værtens nøgle er blevet skiftet.</string> + <string name="prompt_host_disconnected">Værten har afbrudt forbindelsen.\nLuk session?</string> + <string name="prompt_continue_connecting">Er du sikker på at du ønsker\nat skabe forbindelse?</string> + <string name="host_authenticity_warning">Ægtheden for værten \'%1$s\' kan ikke påvises.</string> + <string name="host_fingerprint">%1$s nøgle fingeraftryk er %2$s</string> + <string name="alert_passwords_do_not_match_msg">Adgangskoderne er ikke ens!</string> + <string name="alert_wrong_password_msg">Forkert adgangskode!</string> + <string name="alert_key_corrupted_msg">Privat nøgle synes ødelagt!</string> + <string name="alert_sdcard_absent">SD-kort er ikke sat i!</string> + <string name="button_add">Tilføj</string> + <string name="button_change">Ændre</string> + <string name="button_generate">Opret nøgle</string> + <string name="button_resize">Ændre størrelse</string> + <string name="alert_disconnect_msg">Forbindelse tabt</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminal emulering</string> + <string name="pref_emulation_title">Emulerings metode</string> + <string name="pref_emulation_summary">Terminal emulerings metode til brug ved PTY forbindelser</string> + <string name="pref_scrollback_title">Rul-tilbage buffer</string> + <string name="pref_scrollback_summary">Størrelsen af rul-tilbage bufferen for hver konsol</string> + <string name="pref_ui_category">Brugerflade</string> + <string name="pref_rotation_title">Rotations tilstand</string> + <string name="pref_rotation_summary">Hvordan skærmen roteres når keyboard skubbes ind/ud</string> + <string name="pref_fullscreen_title">Fuld skærm</string> + <string name="pref_fullscreen_summary">Skjul statusbar i konsol</string> + <string name="pref_memkeys_title">Husk nøgler i hukommelsen</string> + <string name="pref_memkeys_summary">Gem oplåste nøgler i hukommelsen indtil baggrunds servicen er afsluttet</string> + <string name="pref_conn_persist_title">Vedvarende forbindelser</string> + <string name="pref_conn_persist_summary">Tving forbindelser til at være aktive i baggrunden</string> + <string name="pref_keymode_title">Biblioteksgenveje</string> + <string name="pref_keymode_summary">Vælg hvordan Alt for \'/\' og Skift for Tabulater skal bruges</string> + <string name="pref_camera_title">Kamera genveje</string> + <string name="pref_camera_summary">Vælg genvej som kamera-knap skal aktivere</string> + <string name="pref_keepalive_title">Hold skærm tændt</string> + <string name="pref_keepalive_summary">Undgå skærm slukker når der arbejdes i en konsol</string> + <string name="pref_wifilock_title">Hold Wi-Fi aktiv</string> + <string name="pref_wifilock_summary">Undgå at Wi-Fi slår fra imens en session er aktiv</string> + <string name="pref_bumpyarrows_title">Vibrerende pile</string> + <string name="pref_bumpyarrows_summary">Vibrér når der afsendes piletaster fra trackballen; nyttig ved langsomme forbindelser</string> + <string name="pref_bell_category">Terminalklokke</string> + <string name="pref_bell_title">Hørbar klokke</string> + <string name="pref_bell_volume_title">Klokke volume</string> + <string name="pref_bell_vibrate_title">Vibrér når klokken ringer</string> + <string name="pref_bell_notification_title">Baggrundspåmindelser</string> + <string name="pref_bell_notification_summary">Send påmindelse når en terminal, der kører i baggrunden, ringer med klokken.</string> + <string name="list_keymode_right">Anvend taster på højre side</string> + <string name="list_keymode_left">Anvend taster på venstre side</string> + <string name="list_keymode_none">Slå fra</string> + <string name="list_pubkeyids_none">Brug ikke tasterne</string> + <string name="list_pubkeyids_any">Anvend enhver ulåst nøgle</string> + <string name="hostpref_nickname_title">Kaldenavn</string> + <string name="hostpref_color_title">Farvekategori</string> + <string name="hostpref_fontsize_title">Skriftstørrelse (pt)</string> + <string name="hostpref_pubkeyid_title">Brug offentlig nøgleautorisering</string> + <string name="hostpref_authagent_title">Brug SSH godkendelses klient</string> + <string name="hostpref_postlogin_title">Post-login automatik</string> + <string name="hostpref_postlogin_summary">Udfør kommandoer på fjernserveren efter autorisering</string> + <string name="hostpref_compression_title">Komprimering</string> + <string name="hostpref_compression_summary">Dette kan hjælpe på langsomme netværk</string> + <string name="hostpref_wantsession_title">Start konsol session</string> + <string name="hostpref_wantsession_summary">Deaktiver denne præference og brug kun port-videresendelser</string> + <string name="hostpref_stayconnected_title">Oprethold forbindelse</string> + <string name="hostpref_stayconnected_summary">Forbind igen hvis forbindelsen tabes</string> + <string name="hostpref_delkey_title">Slet tast</string> + <string name="hostpref_delkey_summary">Tast som sendes når slet tasten benyttes</string> + <string name="hostpref_encoding_title">Tekstkodning</string> + <string name="hostpref_encoding_summary">Tekstkodning for værten</string> + <string name="hostpref_connection_category">Forbindelsesindstillinger</string> + <string name="hostpref_username_title">Brugernavn</string> + <string name="hostpref_hostname_title">Vært</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Aldrig forbundet</string> + <string name="bind_minutes">%1$s minutter siden</string> + <string name="bind_hours">%1$s timer siden</string> + <string name="bind_days">%1$s dage siden</string> + <string name="console_copy_done">Kopier %1$d byte til udklipsholder</string> + <string name="console_copy_start">Berør og flyt\neller brug piletasterne\nfor at vælge område at kopiere</string> + <string name="console_menu_close">Luk</string> + <string name="console_menu_copy">Kopier</string> + <string name="console_menu_paste">Indsæt</string> + <string name="console_menu_portforwards">Port-videresendelser</string> + <string name="console_menu_resize">Tving størrelse</string> + <string name="console_menu_urlscan">URL skan</string> + <string name="button_yes">Ja</string> + <string name="button_no">Nej</string> + <string name="portforward_local">Lokal</string> + <string name="portforward_remote">Fjern</string> + <string name="portforward_dynamic">Dynamisk (SOCKS)</string> + <string name="portforward_pos">Opret port-videresendelse</string> + <string name="portforward_done">Port-videresendelse oprettet</string> + <string name="portforward_problem">Der opstod problemer ved oprettelsen af port-videresendelse. Måske bruger du en port lavere end 1025, eller en port som allerede er i brug.</string> + <string name="portforward_menu_add">Tilføj port-videresendelse</string> + <string name="hint_userhost">brugernavn\@vært</string> + <string name="list_format_error">Anvend formatet %1$s</string> + <string name="format_username">brugernavn</string> + <string name="format_hostname">værtsnavn</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Administrer offentlige nøgler</string> + <string name="list_menu_sortcolor">Sortér efter farve</string> + <string name="list_menu_sortname">Sortér efter navn</string> + <string name="list_menu_settings">Indstillinger</string> + <string name="list_host_disconnect">Afbryd forbindelse</string> + <string name="list_host_edit">Redigér vært</string> + <string name="list_host_portforwards">Redigér port-videresendelser</string> + <string name="list_host_delete">Slet vært</string> + <string name="list_host_empty">Anvend hurtig boksen nedenfor\nfor at oprette forbindelse\ntil en vært.</string> + <string name="list_rotation_default">Standard</string> + <string name="list_rotation_land">Fremtving liggende</string> + <string name="list_rotation_port">Fremtving stående</string> + <string name="list_rotation_auto">Automatisk</string> + <string name="list_camera_ctrlaspace">Ctrl+A derefter mellemrum</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Ingen</string> + <string name="list_delkey_backspace">Slet tilbage</string> + <string name="list_delkey_del">Slet</string> + <string name="delete_message">Er du sikker på at du vil slette \'%1$s\'?</string> + <string name="delete_pos">Ja, slet</string> + <string name="delete_neg">Afbryd</string> + <string name="wizard_agree">Godkend</string> + <string name="wizard_next">Næste</string> + <string name="wizard_back">Tilbage</string> + <string name="terminal_no_hosts_connected">Ingen forbindelser aktive i øjeblikket</string> + <string name="terminal_connecting">Forbinder til %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Verificeret vært \'%1$s\' nøgle: %2$s</string> + <string name="terminal_failed">Verifikation af værtsnøgle mislykkedes.</string> + <string name="terminal_using_s2c_algorithm">Server-til-klient algoritme: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Klient-til-server algoritme: %1$s %2$s</string> + <string name="terminal_using_algorithm">Bruger algoritme: %1$s %2$s</string> + <string name="terminal_auth">Forsøger at autorisere</string> + <string name="terminal_auth_pass">Forsøger adgangskode-autorisering</string> + <string name="terminal_auth_pass_fail">Autoriseringsmetoden adgangskode mislykkedes</string> + <string name="terminal_auth_pubkey_any">Forsøger \'publickey\'-autorisering med enhver offentlig nøgle i hukommelsen</string> + <string name="terminal_auth_pubkey_invalid">Valgte offentlige nøgle er ugyldig, prøv at vælge nøglen igen</string> + <string name="terminal_auth_pubkey_specific">Forsøger \'publickey\'-autorisering med valgte offentlige nøgle</string> + <string name="terminal_auth_pubkey_fail">Autentifikationsmetoden \'publickey\' med nøglen \'%1$s\' fejlede</string> + <string name="terminal_auth_ki">Forsøger \'keyboard-interactive\' autorisering</string> + <string name="terminal_auth_ki_fail">\'keyboard-interactive\'-autorisering fejlede</string> + <string name="terminal_auth_fail">[Din vært understøtter ikke \'password\' eller \'keyboard-interactive\' autorisering.]</string> + <string name="terminal_no_session">Sessionen kunne ikke startes pga. værtens preferencer</string> + <string name="terminal_enable_portfoward">Aktiver port-videresendelse: %1$s</string> + <string name="local_shell_unavailable">Fejl! Lokal konsol er ikke tilgængelig på denne telefon.</string> + <string name="notification_text">%1$s vil have din opmærksomhed.</string> + <string name="no">Nej</string> + <string name="with_confirmation">Med godkendelse</string> + <string name="yes">Ja</string> + <string name="exceptions_submit_message">ConnectBot havde tilsyneladende problemer sidste gang det blev brugt. Send fejlrapport til ConnectBot udviklerne?</string> + <string name="menu_colors_reset">Nulstil</string> + <string name="app_is_running">ConnectBot kører</string> + <string name="color_red">rød</string> + <string name="color_green">grøn</string> + <string name="color_blue">blå</string> + <string name="color_gray">grå</string> +</resources> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..a9422d7 --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,218 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Einfacher, mächtiger, open-source SSH client.</string> + <string name="service_desc">Hält SSH-Verbindungen und geladene öffentliche Schlüssel aufrecht</string> + <string name="title_hosts_list">Hosts</string> + <string name="title_pubkey_list">Öffentliche Schlüssel</string> + <string name="title_port_forwards_list">Port-Weiterleitungen</string> + <string name="title_host_editor">Host bearbeiten</string> + <string name="title_help">Hilfe</string> + <string name="title_colors">Farben</string> + <string name="title_color_picker">Farbe wählen</string> + <string name="resolve_connect">Verbinden</string> + <string name="resolve_entropy">Sammle Entropie</string> + <string name="menu_insert">Host hinzufügen</string> + <string name="menu_delete">Host löschen</string> + <string name="menu_preferences">Einstellungen</string> + <string name="help_intro">Für mehr Informationen wählen Sie eine Überschrift!</string> + <string name="help_about">Über ConnectBot</string> + <string name="help_keyboard">Tastatur</string> + <string name="pubkey_generate">Erzeugen</string> + <string name="pubkey_import">Importieren</string> + <string name="pubkey_delete">Schlüssel löschen</string> + <string name="pubkey_gather_entropy">Sammle Entropie</string> + <string name="pubkey_touch_prompt">Berühren Sie diese Box, um Zufallszahlen zu erzeugen: %1$d%% fertig</string> + <string name="pubkey_touch_hint">Um eine gute Zufallsverteilung zu gewährleisten, bewegen Sie Ihren Finger zufällig über die Box unten!</string> + <string name="pubkey_generating">Erzeuge Schlüsselpaar...</string> + <string name="pubkey_copy_private">Privaten Schlüssel kopieren</string> + <string name="pubkey_copy_public">Öffentlichen Schlüssel kopieren</string> + <string name="pubkey_list_empty">\"Menu\" drücken, um Schlüsselpaare\nzu erzeugen oder zu importieren.</string> + <string name="pubkey_unknown_format">Unbekanntes Format</string> + <string name="pubkey_change_password">Passwort ändern</string> + <string name="pubkey_list_pick">Wähle von /sdcard</string> + <string name="pubkey_import_parse_problem">Fehler beim Parsen des importierten, geheimen Schlüssels</string> + <string name="pubkey_unlock">Schlüssel entsperren</string> + <string name="pubkey_failed_add">Falsches Passwort für Schlüssel \'%1$s\'. Legitimation fehlgeschlagen.</string> + <string name="pubkey_memory_load">In Speicher laden</string> + <string name="pubkey_memory_unload">Aus Speicher entfernen</string> + <string name="pubkey_load_on_start">Schlüssel beim Start laden</string> + <string name="pubkey_confirm_use">Bestätigen vor Benutzung</string> + <string name="portforward_list_empty">Auf \"Menu\" tippen um Port-\nWeiterleitungen zu erstellen.</string> + <string name="portforward_edit">Port-Weiterleitung bearbeiten.</string> + <string name="portforward_delete">Port-Weiterleitung löschen.</string> + <string name="prompt_nickname">Spitzname:</string> + <string name="prompt_nickname_hint_pubkey">Mein Schlüssel</string> + <string name="prompt_source_port">Quellport</string> + <string name="prompt_destination">Ziel:</string> + <string name="prompt_old_password">Altes Passwort:</string> + <string name="prompt_password">Passwort:</string> + <string name="prompt_again">(nochmal)</string> + <string name="prompt_type">Typ:</string> + <string name="prompt_password_can_be_blank">Hinweis: Passwort kann leer bleiben</string> + <string name="prompt_bits">Bits:</string> + <string name="prompt_pubkey_password">Passwort für Schlüssel \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Remote Host die Benutzung\ndes Schlüssels \'%1$s\' erlauben?</string> + <string name="host_verification_failure_warning_header">WARNUNG: IDENTIFIKATION DES ENTFERNTEN HOSTS HAT SICH GEÄNDERT!</string> + <string name="host_verification_failure_warning">MÖGLICHERWEISE TUT JEMAND ETWAS BÖSES!\nJemand könnte Sie jetzt gerade belauschen (man-in-the-middle attack)!\nEs ist auch möglich, dass nur der Host-Key geändert wurde.</string> + <string name="prompt_host_disconnected">Der Host hat die Verbindung beendet.\nSitzung schließen?</string> + <string name="prompt_continue_connecting">Sind Sie sicher, dass Sie\neine Verbindung herstellen wollen?</string> + <string name="host_authenticity_warning">Die Authentizität von Host \'%1$s\' kann nicht garantiert werden.</string> + <string name="host_fingerprint">Host %1$ss Schlüssel Fingerabdruck ist %2$s</string> + <string name="alert_passwords_do_not_match_msg">Passwörter stimmen nicht überein!</string> + <string name="alert_wrong_password_msg">Falsches Passwort!</string> + <string name="alert_key_corrupted_msg">Geheimer Schlüssel scheint korrupt!</string> + <string name="alert_sdcard_absent">SD-Karte nicht eingesteckt!</string> + <string name="button_add">Hinzufügen</string> + <string name="button_change">Ändern</string> + <string name="button_generate">Schlüssel erzeugen</string> + <string name="button_resize">Größe ändern</string> + <string name="alert_disconnect_msg">Die Verbindung wurde getrennt.</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminalemulation</string> + <string name="pref_emulation_title">Emulationsmodus</string> + <string name="pref_emulation_summary">Terminalemulationsmodus für PTY Verbindungen</string> + <string name="pref_scrollback_title">Bildlaufpuffergröße</string> + <string name="pref_scrollback_summary">Größe des Bildlaufpuffers, der für jede Konsole im Speicher gehalten wird</string> + <string name="pref_ui_category">Benutzungsschnittstelle</string> + <string name="pref_rotation_title">Rotationsmodus</string> + <string name="pref_rotation_summary">Wie soll die Rotation beim Ein-/Ausklappen der Tastatur verändert werden</string> + <string name="pref_fullscreen_title">Vollbild</string> + <string name="pref_fullscreen_summary">Statuszeile in der Konsole verbergen</string> + <string name="pref_keyboard_category">Tastatur</string> + <string name="pref_memkeys_title">Schlüssel im Speicher behalten</string> + <string name="pref_memkeys_summary">Entsperrte Schlüssel im Speicher halten, bis der Hintergrunddienst beendet wurde</string> + <string name="pref_conn_persist_title">Persistente Verbindungen</string> + <string name="pref_conn_persist_summary">Erhalte Verbindungen auch im Hintergrund</string> + <string name="pref_keymode_title">Verzeichniskürzel</string> + <string name="pref_keymode_summary">Einstellen, wie Alt für \'/\' und Hochstellen für \'Tab\' verwendet werden sollen</string> + <string name="pref_camera_title">Kamerakürzel</string> + <string name="pref_camera_summary">Welches Kürzel soll durch Drücken des Kamerauslösers aktiviert werden</string> + <string name="pref_keepalive_title">Bildschirm anlassen</string> + <string name="pref_keepalive_summary">Verhindern, dass beim Arbeiten an der Konsole der Bildschirm abgeschaltet wird</string> + <string name="pref_wifilock_title">WLAN aktiviert lassen</string> + <string name="pref_wifilock_summary">Verhindern, dass während einer aktiven Sitzung WLAN deaktiviert wird</string> + <string name="pref_bumpyarrows_title">Vibrierende Pfeiltasten</string> + <string name="pref_bumpyarrows_summary">Vibirieren, wenn Pfeiltasten vom Trackball gesendet werden; nützlich bei laggenden Verbindungen</string> + <string name="pref_bell_category">Terminalglocke</string> + <string name="pref_bell_title">Hörbare Glocke</string> + <string name="pref_bell_volume_title">Glockenlautstärke</string> + <string name="pref_bell_vibrate_title">Bei Glocke vibrieren</string> + <string name="pref_bell_notification_title">Hintergrundnotiz</string> + <string name="pref_bell_notification_summary">Benachrichtigen, wenn ein im Hintergrund laufendendes Terminal eine Glocke erklingen lässt.</string> + <string name="list_keymode_right">Tasten rechts verwenden</string> + <string name="list_keymode_left">Tasten links verwenden</string> + <string name="list_keymode_none">Ausschalten</string> + <string name="list_pubkeyids_none">Keine Schlüssel verwenden</string> + <string name="list_pubkeyids_any">Beliebigen entsperrten Schlüssel verwenden</string> + <string name="hostpref_nickname_title">Nutzername</string> + <string name="hostpref_color_title">Farbkategorie</string> + <string name="hostpref_fontsize_title">Fontgröße (pt)</string> + <string name="hostpref_pubkeyid_title">Verwende pubkey Authentisierung</string> + <string name="hostpref_authagent_title">SSH auth agent benutzen?</string> + <string name="hostpref_postlogin_title">Post-Login Automatisierung</string> + <string name="hostpref_postlogin_summary">Befehle, die auf dem entfernten Host nach der Anmeldung gestartet werden sollen.</string> + <string name="hostpref_compression_title">Kompression</string> + <string name="hostpref_compression_summary">Dies könnte bei langsamen Netzwerken helfen</string> + <string name="hostpref_wantsession_title">Kommandozeilensitzung starten</string> + <string name="hostpref_wantsession_summary">Diese Einstellung abwählen, um ausschließlich Port-Weiterleitungen zu verwenden</string> + <string name="hostpref_stayconnected_title">Verbindung halten</string> + <string name="hostpref_stayconnected_summary">Verbindung bei Verbindungsabbruch wiederherstellen</string> + <string name="hostpref_delkey_title">DEL Taste</string> + <string name="hostpref_delkey_summary">Zu sendender Tastencode wenn DEL gedrückt wird</string> + <string name="hostpref_encoding_title">Zeichensatz</string> + <string name="hostpref_encoding_summary">Zeichen-Encoding für den Host</string> + <string name="hostpref_connection_category">Verbindungseinstellungen</string> + <string name="hostpref_username_title">Benutzername</string> + <string name="hostpref_hostname_title">Host</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Nie verbunden</string> + <string name="bind_minutes">vor %1$s Minuten</string> + <string name="bind_hours">vor %1$s Stunden</string> + <string name="bind_days">vor %1$s Tagen</string> + <string name="console_copy_done">%1$d Byte in die Zwischenablage kopiert</string> + <string name="console_copy_start">Berühren und ziehen\noder Trackball verwenden\num zu kopierenden Bereich auszuwählen</string> + <string name="console_menu_close">Schließen</string> + <string name="console_menu_copy">Kopieren</string> + <string name="console_menu_paste">Einfügen</string> + <string name="console_menu_portforwards">Port-Weiterleitungen</string> + <string name="console_menu_resize">Größe erzwingen</string> + <string name="console_menu_urlscan">URL Scan</string> + <string name="button_yes">Ja</string> + <string name="button_no">Nein</string> + <string name="portforward_local">Lokal</string> + <string name="portforward_remote">Entfernt</string> + <string name="portforward_dynamic">Dynamisch (SOCKS)</string> + <string name="portforward_pos">Port-Weiterleitung anlegen</string> + <string name="portforward_done">Port-Weiterleitung angelegt.</string> + <string name="portforward_problem">Konnte Port-Weiterleitung nicht anlegen. Verwenden Sie Ports kleiner als 1024, oder wird der Port bereits verwendet?</string> + <string name="portforward_menu_add">Port-Weiterleitung hinzufügen</string> + <string name="hint_userhost">benutzer\@hostname</string> + <string name="list_format_error">Benutze das Format %1$s</string> + <string name="format_username">Benutzername</string> + <string name="format_hostname">Hostname</string> + <string name="format_port">Anschluss</string> + <string name="list_menu_pubkeys">Pubkeys verwalten</string> + <string name="list_menu_sortcolor">Nach Farbe sortieren</string> + <string name="list_menu_sortname">Nach Name sortieren</string> + <string name="list_menu_settings">Einstellungen</string> + <string name="list_host_disconnect">Verbindung trennen</string> + <string name="list_host_edit">Host editieren</string> + <string name="list_host_portforwards">Port-Weiterleitungen editieren</string> + <string name="list_host_delete">Host löschen</string> + <string name="list_host_empty">Benutzen Sie die quick-connect box\nunten um sich mit einem Host zu verbinden.</string> + <string name="list_rotation_default">Vorgabe</string> + <string name="list_rotation_land">Querformat erzwingen</string> + <string name="list_rotation_port">Hochformat erzwingen</string> + <string name="list_rotation_auto">Automatisch</string> + <string name="list_camera_ctrlaspace">Strg+A dann Space</string> + <string name="list_camera_ctrla">Strg+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Nichts</string> + <string name="list_delkey_backspace">Rücktaste</string> + <string name="list_delkey_del">Entfernen</string> + <string name="delete_message">Soll \'%1$s\' wirklich gelöscht werden?</string> + <string name="delete_pos">Ja, löschen</string> + <string name="delete_neg">Abbrechen</string> + <string name="wizard_agree">Zustimmung</string> + <string name="wizard_next">Weiter</string> + <string name="wizard_back">Zurück</string> + <string name="terminal_no_hosts_connected">Zur Zeit keine Hosts verbunden</string> + <string name="terminal_connecting">Verbinde mit %1$s:%2$d mittels %3$s</string> + <string name="terminal_sucess">Host \'%1$s\' Schlüssel: %2$s bestätigt</string> + <string name="terminal_failed">Überprüfung des Host-Schlüssels fehlgeschlagen.</string> + <string name="terminal_using_s2c_algorithm">Server zu Client Algorithmus: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Client zu Server Algorithmus: %1$s %2$s</string> + <string name="terminal_using_algorithm">Benutze Algorithmus: %1$s %2$s</string> + <string name="terminal_auth">Versuche zu authentisieren.</string> + <string name="terminal_auth_pass">Versuche Authentisierung mit Passwort.</string> + <string name="terminal_auth_pass_fail">Authentisierungsmethode \'Passwort\' fehlgeschlagen.</string> + <string name="terminal_auth_pubkey_any">Versuche Public Key Authentisierung mit allen öffentlichen Schlüsseln im Speicher</string> + <string name="terminal_auth_pubkey_invalid">Der ausgewählte öffentliche Schlüssel ist ungültig, versuchen Sie den Schlüssel im Host-Editor neu auszuwählen</string> + <string name="terminal_auth_pubkey_specific">Versuche Public Key Authentisierung mit einem bestimmten öffentlichen Schlüssel</string> + <string name="terminal_auth_pubkey_fail">Authentifizierungs Methode \'publickey\' mit Schlüssel \'%1$s\' fehlgeschlagen</string> + <string name="terminal_auth_ki">Versuche \'keyboard-interactive\' Authentisierung.</string> + <string name="terminal_auth_ki_fail">Authentisierungsmethode \'keyboard-interactive\' fehlgeschlagen</string> + <string name="terminal_auth_fail">[Ihr Host unterstützt weder \'password\' noch \'keyboard-interactive\' Authentisierung.]</string> + <string name="terminal_no_session">Sitzung wird wegen Host Präferenz nicht gestartet</string> + <string name="terminal_enable_portfoward">Aktiviere Port Forwarding: %1$s</string> + <string name="local_shell_unavailable">Fehler! Lokale Kommandozeile ist auf diesem Telefon nicht verfügbar.</string> + <string name="notification_text">%1$s will ihre Aufmerksamkeit.</string> + <string name="no">Nein</string> + <string name="with_confirmation">Mit Bestätigung</string> + <string name="yes">Ja</string> + <string name="exceptions_submit_message">Scheinbar hatte ConnectBot Probleme bei der letzten Benutzung. Fehlermeldung an die ConnectBot übermitteln?</string> + <string name="menu_colors_reset">Zurücksetzen</string> + <string name="app_is_running">ConnectBot läuft</string> + <string name="color_red">rot</string> + <string name="color_green">grün</string> + <string name="color_blue">blau</string> + <string name="color_gray">grau</string> + <string name="colors_fg_label">SF: %1$d</string> + <string name="color_bg_label">HF: %1$d</string> + <string name="image_description_connected">Verbunden.</string> + <string name="image_description_key_is_locked">Schlüssel ist gesperrt.</string> + <string name="image_description_toggle_control_character">Steuerzeichen umschalten</string> + <string name="image_description_send_escape_character">Escape-Zeichen senden.</string> + <string name="image_description_show_keyboard">Tastatur anzeigen.</string> +</resources> diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml new file mode 100644 index 0000000..5b0e837 --- /dev/null +++ b/app/src/main/res/values-el/strings.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="title_help">Βοήθεια</string> + <string name="title_colors">Χρώματα</string> + <string name="resolve_connect">Σύνδεση</string> +</resources> diff --git a/app/src/main/res/values-en-rCA/strings.xml b/app/src/main/res/values-en-rCA/strings.xml new file mode 100644 index 0000000..534a588 --- /dev/null +++ b/app/src/main/res/values-en-rCA/strings.xml @@ -0,0 +1,10 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="title_colors">Colours</string> + <string name="pubkey_list_empty">Tap Menu to create\nor import key pairs.</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="hostpref_color_title">Colour category</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="list_menu_sortcolor">Sort by colour</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> +</resources> diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml new file mode 100644 index 0000000..fabe684 --- /dev/null +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="title_colors">Colours</string> + <string name="hostpref_color_title">Colour category</string> + <string name="list_menu_sortcolor">Sort by colour</string> + <string name="color_gray">grey</string> +</resources> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..87bb50f --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,211 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Cliente de SSH sencillo, potente y open-source</string> + <string name="service_desc">Mantiene conexiones SSH y las claves públicas guardadas</string> + <string name="title_hosts_list">Máquinas</string> + <string name="title_pubkey_list">Claves Publicas</string> + <string name="title_port_forwards_list">Traducciones de puerto</string> + <string name="title_host_editor">Editar equipo remoto</string> + <string name="title_help">Ayuda</string> + <string name="title_colors">Colores</string> + <string name="resolve_connect">Conectar</string> + <string name="resolve_entropy">Recolectar entropía</string> + <string name="menu_insert">Añadir equipo remoto</string> + <string name="menu_delete">Eliminar máquina</string> + <string name="menu_preferences">Configuración</string> + <string name="help_intro">Por favor seleccione un tema a continuación para más información sobre una materia en particular.</string> + <string name="help_about">Acerca de ConnectBot</string> + <string name="help_keyboard">Teclado</string> + <string name="pubkey_generate">Generar</string> + <string name="pubkey_import">Importar</string> + <string name="pubkey_delete">Eliminar clave</string> + <string name="pubkey_gather_entropy">Haciendo acopio de entropía</string> + <string name="pubkey_touch_prompt">Toque esta caja para conseguir aleatoriedad: %1$d%% hecho</string> + <string name="pubkey_touch_hint">Para poder conseguir aleatoriedad durante la generacion de la clave, mueva su dedo aleatoriamente sobre la caja a continuación.</string> + <string name="pubkey_generating">Generar llavero de claves</string> + <string name="pubkey_copy_private">Copiar clave privada</string> + <string name="pubkey_copy_public">Copiar clave pública</string> + <string name="pubkey_list_empty">Presione menu para crear\nó importar un llavero de claves</string> + <string name="pubkey_unknown_format">Formato desconocido</string> + <string name="pubkey_change_password">Cambiar contraseña</string> + <string name="pubkey_list_pick">Escoger desde la tarjeta sd</string> + <string name="pubkey_import_parse_problem">Problema analizando clave privada importada</string> + <string name="pubkey_unlock">Desbloquear clave</string> + <string name="pubkey_failed_add">Contraseña incorrecta para la clave \'%1$s\'. Autenticación fallida.</string> + <string name="pubkey_memory_load">Cargar en memoria</string> + <string name="pubkey_memory_unload">Descargar de memoria</string> + <string name="pubkey_load_on_start">Cargar clave al inicio</string> + <string name="pubkey_confirm_use">Confirme antes de usar</string> + <string name="portforward_list_empty">Presione Menu para crear\nó importar redireccionamientos.</string> + <string name="portforward_edit">Editar redireccionamiento de puerto</string> + <string name="portforward_delete">Eliminar redireccionamiento de puerto</string> + <string name="prompt_nickname">Nombre de usuario:</string> + <string name="prompt_nickname_hint_pubkey">Mi clave de trabajo</string> + <string name="prompt_source_port">Puerto fuente:</string> + <string name="prompt_destination">Destino:</string> + <string name="prompt_old_password">Contraseña antigua:</string> + <string name="prompt_password">Contraseña:</string> + <string name="prompt_again">(de nuevo)</string> + <string name="prompt_type">Tipo:</string> + <string name="prompt_password_can_be_blank">Nota: la contraseña puede ser dejada en blanco</string> + <string name="prompt_bits">Bits:</string> + <string name="prompt_pubkey_password">Contraseña para clave \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">¿Autorizar maquina remota\nde usar clave \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">ATENCIÓN: ¡LA IDENTIFICACIÓN DE LA MAQUINA REMOTA HA CAMBIADO!</string> + <string name="host_verification_failure_warning">¡ES POSIBLE QUE ALGUIEN ESTE HACIENDO ALGO INAPROPIADO!\nAlguien puede estar interviniendo en estos momentos (ataque man-in-the-middle)!\nTambién es posible que la clave haya sido cambiada.</string> + <string name="prompt_host_disconnected">La máquina ha sido desconectada.\n¿Desea cerrar sesión?</string> + <string name="prompt_continue_connecting">¿Esta seguro de que desea\nseguir conectándose?</string> + <string name="host_authenticity_warning">La autenticidad de la máquina \'%1$s\' no puede ser establecida.</string> + <string name="host_fingerprint">La clave huella de la maquina %1$s es %2$s</string> + <string name="alert_passwords_do_not_match_msg">¡Las contraseñas no coinciden!</string> + <string name="alert_wrong_password_msg">¡Contraseña errónea!</string> + <string name="alert_key_corrupted_msg">¡La clave privada parece corrupta!</string> + <string name="alert_sdcard_absent">¡Tarjeta SD no insertada!</string> + <string name="button_add">Añadir</string> + <string name="button_change">Modificar</string> + <string name="button_generate">Generar clave</string> + <string name="button_resize">Redimensionar</string> + <string name="alert_disconnect_msg">Conexión perdida</string> + <string name="msg_copyright">Todos los derechos reservados © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulación del terminal</string> + <string name="pref_emulation_title">Modo emulación</string> + <string name="pref_emulation_summary">Modo emulación para usar en conexiones PTY</string> + <string name="pref_scrollback_title">Tamaño de navegación</string> + <string name="pref_scrollback_summary">Tamaño de búfer que quedara en memoria para cada consola.</string> + <string name="pref_ui_category">Interfaz de usuario</string> + <string name="pref_rotation_title">Modo de rotación</string> + <string name="pref_rotation_summary">Como cambiar la rotación cuando se abra el teclado.</string> + <string name="pref_fullscreen_title">Pantalla completa</string> + <string name="pref_fullscreen_summary">Esconder barra de estatus cuando este en consola</string> + <string name="pref_memkeys_title">Recordar claves en memoria</string> + <string name="pref_memkeys_summary">Recordar claves en memoria hasta que el servicio en segundo plano termine</string> + <string name="pref_conn_persist_title">Conexiones persistentes</string> + <string name="pref_conn_persist_summary">Mantener conexiones activas en segundo plano</string> + <string name="pref_keymode_title">Secuencia de teclas para directorios</string> + <string name="pref_keymode_summary">Especifique como usar Alt para \'/\' y Shit para Tab</string> + <string name="pref_camera_title">Boton de la Cámara</string> + <string name="pref_camera_summary">Especifique que secuencia de teclas activar cuando presione el botón de la cámara</string> + <string name="pref_keepalive_title">Mantener pantalla activa</string> + <string name="pref_keepalive_summary">Prevenir que se apague la pantalla cuando se este trabajando en consola</string> + <string name="pref_wifilock_title">Mantener la red inálambrica activa</string> + <string name="pref_wifilock_summary">Prevenir que la red inálambrica se apague mientras haya una conexión activa</string> + <string name="pref_bumpyarrows_title">Cursor con vibración</string> + <string name="pref_bumpyarrows_summary">Vibrar al recibir las teclas del cursor desde el trackball; útil para conexiones con retardos</string> + <string name="pref_bell_category">Timbre del terminal</string> + <string name="pref_bell_title">Timbre audible</string> + <string name="pref_bell_volume_title">Volúmen del timbre</string> + <string name="pref_bell_vibrate_title">Vibrar con el timbre</string> + <string name="pref_bell_notification_title">Notificaciones de fondo</string> + <string name="pref_bell_notification_summary">Activar notificacion cuando algun terminal corriendo en el fondo active un timbre</string> + <string name="list_keymode_right">Usar teclas del lado derecho</string> + <string name="list_keymode_left">Usar teclas del lado izquierdo</string> + <string name="list_keymode_none">Desactivar</string> + <string name="list_pubkeyids_none">No usar teclas</string> + <string name="list_pubkeyids_any">Utilizar cualquier clave desbloqueada</string> + <string name="hostpref_nickname_title">Apodo</string> + <string name="hostpref_color_title">Categoria de colores</string> + <string name="hostpref_fontsize_title">Tamaño de la fuente (pt)</string> + <string name="hostpref_pubkeyid_title">Usar clave de autenticación pública</string> + <string name="hostpref_authagent_title">Utilizar agente de autenticación SSH</string> + <string name="hostpref_postlogin_title">Inicio de sesión después de la automatización</string> + <string name="hostpref_postlogin_summary">Comandos para ejecutar en el servidor remoto, una vez autenticado</string> + <string name="hostpref_compression_title">Compresión</string> + <string name="hostpref_compression_summary">Esto puede ayudar con las redes más lentas</string> + <string name="hostpref_wantsession_title">Inciar sesión en shell</string> + <string name="hostpref_wantsession_summary">Desactive esta preferencia para usar sólo reenvíos por puerto</string> + <string name="hostpref_stayconnected_title">Mantenerse conectado</string> + <string name="hostpref_stayconnected_summary">Intentar reconectar al equipo si se desconecta</string> + <string name="hostpref_delkey_title">Tecla DEL</string> + <string name="hostpref_delkey_summary">El código clave que se envía cuando se pulsa tecla DEL</string> + <string name="hostpref_encoding_title">Codificando</string> + <string name="hostpref_encoding_summary">Codificación de caracteres para la maquina</string> + <string name="hostpref_connection_category">Opciones de Conexión</string> + <string name="hostpref_username_title">Nombre de usuario</string> + <string name="hostpref_hostname_title">Host (equipo anfitrión)</string> + <string name="hostpref_port_title">Puerto</string> + <string name="bind_never">Nunca fué conectado</string> + <string name="bind_minutes">Hace %1$s minutos</string> + <string name="bind_hours">Hace %1$s horas</string> + <string name="bind_days">Hace %1$s días</string> + <string name="console_copy_done">%1$d bytes copiados al portapaleles</string> + <string name="console_copy_start">Toque y arrastre\no use la almohadilla direccional\npara seleccionar la zona a copiar</string> + <string name="console_menu_close">Cerrar</string> + <string name="console_menu_copy">Copiar</string> + <string name="console_menu_paste">Pegar</string> + <string name="console_menu_portforwards">Traducciones de puerto</string> + <string name="console_menu_resize">Forzar un tamaño</string> + <string name="console_menu_urlscan">Escaneo de direcciones</string> + <string name="button_yes">Sí</string> + <string name="button_no">Denegar</string> + <string name="portforward_local">Local</string> + <string name="portforward_remote">Remoto</string> + <string name="portforward_dynamic">Dinámicos (SOCKS)</string> + <string name="portforward_pos">Crear reenvio de puerto</string> + <string name="portforward_done">Creacion satisfactoria del reenvio de puerto</string> + <string name="portforward_problem">Problema al crear el puerto de reenvio, tal vez usted está usando puertos menores de 1024 o el puerto ya está siendo utilizado?</string> + <string name="portforward_menu_add">Añadir redirección de puertos</string> + <string name="hint_userhost">usuario\@equipo</string> + <string name="list_format_error">Usar formato %1$s</string> + <string name="format_username">Usuario</string> + <string name="format_hostname">nombre del host</string> + <string name="format_port">puerto</string> + <string name="list_menu_pubkeys">Administrar clave públicas</string> + <string name="list_menu_sortcolor">Ordenar por color</string> + <string name="list_menu_sortname">Ordenar por nombre</string> + <string name="list_menu_settings">Configuración</string> + <string name="list_host_disconnect">Desconectar</string> + <string name="list_host_edit">Editar equipo</string> + <string name="list_host_portforwards">Editar redirección de puertos</string> + <string name="list_host_delete">Eliminar equipo</string> + <string name="list_host_empty">Usar la casilla de conexión rápida\na continuación para conectarse a una máquina</string> + <string name="list_rotation_default">Predeterminado</string> + <string name="list_rotation_land">Forzar paisaje</string> + <string name="list_rotation_port">Forzar retrato</string> + <string name="list_rotation_auto">Automático</string> + <string name="list_camera_ctrlaspace">Ctrl+A después espacio</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Ninguno</string> + <string name="list_delkey_backspace">Borrar</string> + <string name="list_delkey_del">Eliminar</string> + <string name="delete_message">¿Está seguro de que quiere eliminar \'%1$s\'?</string> + <string name="delete_pos">Sí, eliminar</string> + <string name="delete_neg">Cancelar</string> + <string name="wizard_agree">De acuerdo</string> + <string name="wizard_next">Siguiente</string> + <string name="wizard_back">Atrás</string> + <string name="terminal_no_hosts_connected">No hay equipos conectados ahora</string> + <string name="terminal_connecting">Canectando a %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Host (equipo anfitrion) \'%1$s\' clave: %2$s verifiado.</string> + <string name="terminal_failed">Verificación del host (equipo anfitrion) fallida</string> + <string name="terminal_using_s2c_algorithm">Algoritmo servidor-cliente: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algoritmo cliente-servidor: %1$s %2$s</string> + <string name="terminal_using_algorithm">Algoritmo que se esta usando: %1$s %2$s</string> + <string name="terminal_auth">Intentando autenticar</string> + <string name="terminal_auth_pass">Intentando autenticación de la contraseña</string> + <string name="terminal_auth_pass_fail">Método de autenticación \'password\' fallido</string> + <string name="terminal_auth_pubkey_any">Intentando autenticacion de la clave pública usando las claves públicas en memoria</string> + <string name="terminal_auth_pubkey_invalid">La clave pública selecciona es invalida, intenta reseleccional la clave en el editor de host (equipo anfitrion)</string> + <string name="terminal_auth_pubkey_specific">Intentar autenticación de la clave pública con una clave pública determinada</string> + <string name="terminal_auth_pubkey_fail">El método de autenticación \'publickey\' con clave \'%1$s\' ha fallado.</string> + <string name="terminal_auth_ki">Intentando autenticación \'interactiva por teclado\'</string> + <string name="terminal_auth_ki_fail">El método de autenticación \'interactiva por teclado\' ha fallado</string> + <string name="terminal_auth_fail">[Su equipo no soporta el método de autenticación por \'contraseña\' o por \'interacción por teclado\'.]</string> + <string name="terminal_no_session">La sesión no se iniciará debido a las preferencias del equipo.</string> + <string name="terminal_enable_portfoward">Activar redirección de puerto: %1$s</string> + <string name="local_shell_unavailable">¡Fallo! Su intérprete de órdenes local no está disponible en este teléfono.</string> + <string name="notification_text">%1$s requiere de tu atención.</string> + <string name="no">Denegar</string> + <string name="with_confirmation">Con confirmación</string> + <string name="yes">Sí</string> + <string name="exceptions_submit_message">Parece que ConnectBot tuvo un problema en su última ejecución. Enviar reporte de error a los desarrolladores de ConnectBot?</string> + <string name="menu_colors_reset">Restablecer</string> + <string name="app_is_running">ConnectBot está ejecutándose</string> + <string name="color_red">rojo</string> + <string name="color_green">verde</string> + <string name="color_blue">azul</string> + <string name="color_gray">gris</string> + <string name="image_description_connected">Conectado</string> + <string name="image_description_show_keyboard">Mostrar teclado.</string> +</resources> diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml new file mode 100644 index 0000000..6cf248b --- /dev/null +++ b/app/src/main/res/values-eu/strings.xml @@ -0,0 +1,214 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">SSH bezero soil, ahaltsu eta librea</string> + <string name="service_desc">SSH konexio eta kargatutako gako publikoak mantentzen ditu</string> + <string name="title_hosts_list">Ostalariak</string> + <string name="title_pubkey_list">Gako publikoak</string> + <string name="title_port_forwards_list">Ataka birbidalketak</string> + <string name="title_host_editor">Editatu ostalaria</string> + <string name="title_help">Laguntza</string> + <string name="title_colors">Koloreak</string> + <string name="resolve_connect">Konektatu</string> + <string name="resolve_entropy">Bildu entropia</string> + <string name="menu_insert">Gehitu ostalaria</string> + <string name="menu_delete">Ezabatu ostalaria</string> + <string name="menu_preferences">Hobespenak</string> + <string name="help_intro">Mesedez, hautatu beheko gaietako bat informazio gehiago nahi izanez gero.</string> + <string name="help_about">ConnectBot-i buruz</string> + <string name="help_keyboard">Teklatua</string> + <string name="pubkey_generate">Sortu</string> + <string name="pubkey_import">Inportatu</string> + <string name="pubkey_delete">Ezabatu gakoa</string> + <string name="pubkey_gather_entropy">Entropia biltzen</string> + <string name="pubkey_touch_prompt">Ukitu kaxa hau ausazkotasuna lortzeko: %1$d%% eginda</string> + <string name="pubkey_touch_hint">Gakoa sortzeko beharrezko den ausazkotasuna lortzeko, mugitu hatza ausaz azpiko kaxaren gainean.</string> + <string name="pubkey_generating">Gako parea sortzen...</string> + <string name="pubkey_copy_private">Kopiatu gako pribatua</string> + <string name="pubkey_copy_public">Kopiatu gako publikoa</string> + <string name="pubkey_list_empty">Sakatu \"Menu\" gako pareak\nsortu edo inportatzeko</string> + <string name="pubkey_unknown_format">Formatu ezezaguna</string> + <string name="pubkey_change_password">Aldatu pasahitza</string> + <string name="pubkey_list_pick">Hautatu /sdcard-etik</string> + <string name="pubkey_import_parse_problem">Arazoa inportatutako gako pribatua aztertzean</string> + <string name="pubkey_unlock">Desblokeatu gakoa</string> + <string name="pubkey_failed_add">Pasahitz okerra \'%1$s\' gakoarentzat. Autentifikazioak huts egin du.</string> + <string name="pubkey_memory_load">Kargatu memoriara</string> + <string name="pubkey_memory_unload">Deskargatu memoriatik</string> + <string name="pubkey_load_on_start">Kargatu gakoa abiaraztean</string> + <string name="pubkey_confirm_use">Baieztatu erabili aurretik</string> + <string name="portforward_list_empty">Sakatu \"Menua\" ataka\nbirbidalketak sortzeko.</string> + <string name="portforward_edit">Editatu ataka birbidalketa</string> + <string name="portforward_delete">Ezabatu ataka birbidalketa</string> + <string name="prompt_nickname">Goitizena:</string> + <string name="prompt_nickname_hint_pubkey">Nire laneko gakoa</string> + <string name="prompt_source_port">Iturburuko ataka:</string> + <string name="prompt_destination">Helburua:</string> + <string name="prompt_old_password">Pasahitz zaharra:</string> + <string name="prompt_password">Pasahitza:</string> + <string name="prompt_again">(berriro)</string> + <string name="prompt_type">Mota:</string> + <string name="prompt_password_can_be_blank">Oharra: pasahitza hutsik utz daiteke</string> + <string name="prompt_bits">Bitak:</string> + <string name="prompt_pubkey_password">\'%1$s\' gakoaren pasahitza</string> + <string name="prompt_allow_agent_to_use_key">Urruneko ostalariak \'%1$s\'\ngakoa erabil dezan onartu?</string> + <string name="host_verification_failure_warning_header">KONTUZ: URRUNEKO OSTALARIAREN IDENTIFIKAZIOA ALDATU DA!</string> + <string name="host_verification_failure_warning">POSIBLE DA NORBAIT ZERBAIT MALTZURRA EGITEN ARITZEA!\nNorbait ezkutuan entzuten aritu daiteke(man-in-the-middle erasoa)!\nBeste aukera ostalariaren gakoa aldatu izana da.</string> + <string name="prompt_host_disconnected">Ostalaria deskonektatu egin da.\nSaioa itxi nahi duzu?</string> + <string name="prompt_continue_connecting">Ziur zaude konektatzen\njarraitu nahi duzula?</string> + <string name="host_authenticity_warning">\'%1$s\' ostalariaren autentifikazioa ezin da ziurtatu.</string> + <string name="host_fingerprint">%1$s ostalariaren hatz-marka %2$s da</string> + <string name="alert_passwords_do_not_match_msg">Pasahitzak ez datoz bat!</string> + <string name="alert_wrong_password_msg">Pasahitz okerra!</string> + <string name="alert_key_corrupted_msg">Gako pribatua hondatuta dagoela dirudi!</string> + <string name="alert_sdcard_absent">Ez da aurkitu SD txartelik!</string> + <string name="button_add">Gehitu</string> + <string name="button_change">Aldatu</string> + <string name="button_generate">Sortu gakoa</string> + <string name="button_resize">Aldatu tamaina</string> + <string name="alert_disconnect_msg">Konexioa galdu da</string> + <string name="msg_copyright">Copyright-a © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminal emulatzailea</string> + <string name="pref_emulation_title">Emulazio modua</string> + <string name="pref_emulation_summary">PTY konexioetarako terminal emulazio modua</string> + <string name="pref_scrollback_title">Korritze-barraren tamaina</string> + <string name="pref_scrollback_summary">Kontsola bakoitzarentzat memorian gordeko den korritze-barraren buffer tamaina.</string> + <string name="pref_ui_category">Erabiltzaile-interfazea</string> + <string name="pref_rotation_title">Biratze modua</string> + <string name="pref_rotation_summary">Nola aldatu biratzea teklatua bistaratzean/izkutatzean.</string> + <string name="pref_fullscreen_title">Pantaila osoa</string> + <string name="pref_fullscreen_summary">Ezkutatu egoera-barra kontsola erabiltzean</string> + <string name="pref_memkeys_title">Gorde gakoak memorian</string> + <string name="pref_memkeys_summary">Gogoratu desblokeatutako gakoak memorian zerbitzua amaitu arte</string> + <string name="pref_conn_persist_title">Konexio iraunkorrak</string> + <string name="pref_conn_persist_summary">Behartu konexioak aktibo mantentzera atzeko planoko daudenean</string> + <string name="pref_keymode_title">Direktorien laster-teklak</string> + <string name="pref_keymode_summary">Zehaztu nola erabili Alt \'/\'-entzat eta Shift Tab-entzat</string> + <string name="pref_camera_title">Kameraren laster-tekla</string> + <string name="pref_camera_summary">Zehaztu kameraren botoia sakatzean aktibatu nahi duzun laster-tekla</string> + <string name="pref_keepalive_title">Mantendu pantaila aktibo</string> + <string name="pref_keepalive_summary">Galarazi pantaila itzaltzea kontsola moduan lanean aritzean</string> + <string name="pref_wifilock_title">Mantendu WiFi-a aktibo</string> + <string name="pref_wifilock_summary">Galarazi WiFi-a itzaltzea saio aktibo bat dagoen bitartean</string> + <string name="pref_bumpyarrows_title">Bibraziodun geziak</string> + <string name="pref_bumpyarrows_summary">Bibratu trackball bidez erakusle-geziak bidaltzean; atzerapendun konexioentzat egokia</string> + <string name="pref_bell_category">Terminalaren txirrina</string> + <string name="pref_bell_title">Txirrin entzungarria</string> + <string name="pref_bell_volume_title">Txirrinaren bolumena</string> + <string name="pref_bell_vibrate_title">Bibratu txirrinarekin</string> + <string name="pref_bell_notification_title">Atzeko planoko jakinerazpenak</string> + <string name="pref_bell_notification_summary">Bidali jakinerazpena atzeko planoan dagoen terminal batek txirrina jotzean.</string> + <string name="list_keymode_right">Erabili eskuinaldeko teklak</string> + <string name="list_keymode_left">Erabili ezkerraldeko teklak</string> + <string name="list_keymode_none">Desgaitu</string> + <string name="list_pubkeyids_none">Ez erabili gakorik</string> + <string name="list_pubkeyids_any">Erabili desblokeatutako edozein gako</string> + <string name="hostpref_nickname_title">Goitizena</string> + <string name="hostpref_color_title">Kolore kategoria</string> + <string name="hostpref_fontsize_title">Letra-tamaina (pt)</string> + <string name="hostpref_pubkeyid_title">Erabili gako publiko bidezko autentifikazioa</string> + <string name="hostpref_authagent_title">Erabili SSH autentifikazio-eragilea</string> + <string name="hostpref_postlogin_title">Saio hasiera ondoko automatizazioa</string> + <string name="hostpref_postlogin_summary">Autentifikatu ondoren urruneko zerbitzarian exekutatu beharreko komandoak</string> + <string name="hostpref_compression_title">Konpresioa</string> + <string name="hostpref_compression_summary">Hau lagungarri izan daiteke konexio mantsoekin</string> + <string name="hostpref_wantsession_title">Hasi shell saioa</string> + <string name="hostpref_wantsession_summary">Desgaitu aukera hau ataka birbidalketak bakarrik erabiltzeko</string> + <string name="hostpref_stayconnected_title">Konektatuta mantendu</string> + <string name="hostpref_stayconnected_summary">Saiatu ostalarira birkonektatzen deskonektatzean</string> + <string name="hostpref_delkey_title">DEL tekla</string> + <string name="hostpref_delkey_summary">DEL tekla sakatzean bidaltzen den tekla-kodea</string> + <string name="hostpref_encoding_title">Kodeketa</string> + <string name="hostpref_encoding_summary">Ostalariarentzako karaktere kodeketa</string> + <string name="hostpref_connection_category">Konexio hobespenak</string> + <string name="hostpref_username_title">Erabiltzaile-izena</string> + <string name="hostpref_hostname_title">Ostalaria</string> + <string name="hostpref_port_title">Ataka</string> + <string name="bind_never">Inoiz ez da konexiorik ezarri</string> + <string name="bind_minutes">Duela %1$s minutu</string> + <string name="bind_hours">Duela %1$s ordu</string> + <string name="bind_days">Duela %1$s egun</string> + <string name="console_copy_done">%1$d byte kopiatuta arbelera</string> + <string name="console_copy_start">Ukitu eta arrastatu\nedo erabili norabide kuxina\nkopiatu beharreko eremua hautatzeko</string> + <string name="console_menu_close">Itxi</string> + <string name="console_menu_copy">Kopiatu</string> + <string name="console_menu_paste">Itsatsi</string> + <string name="console_menu_portforwards">Ataka birbidalketak</string> + <string name="console_menu_resize">Behartu tamaina</string> + <string name="console_menu_urlscan">URL eskaneatzea</string> + <string name="button_yes">Bai</string> + <string name="button_no">Ez</string> + <string name="portforward_local">Lokala</string> + <string name="portforward_remote">Urrunekoa</string> + <string name="portforward_dynamic">Dinamikoa (SOCKS)</string> + <string name="portforward_pos">Sortu ataka birbidalketa</string> + <string name="portforward_done">Ataka birbidalketa ongi sortu da</string> + <string name="portforward_problem">Arazoa ataka birbidalketa sortzean, behar bada erabilitako ataka 1024 baino txikiagoa da edo dagoeneko erabiltzen den ataka bat da?</string> + <string name="portforward_menu_add">Gehitu ataka birbidalketa</string> + <string name="hint_userhost">erabiltzailea\@ostalaria</string> + <string name="list_format_error">Erabili %1$s formatua</string> + <string name="format_username">erabiltzaile-izena</string> + <string name="format_hostname">ostalaria</string> + <string name="format_port">ataka</string> + <string name="list_menu_pubkeys">Kudeatu gako publikoak</string> + <string name="list_menu_sortcolor">Antolatu kolorearen arabera</string> + <string name="list_menu_sortname">Antolatu izenaren arabera</string> + <string name="list_menu_settings">Ezarpenak</string> + <string name="list_host_disconnect">Deskonektatu</string> + <string name="list_host_edit">Editatu ostalaria</string> + <string name="list_host_portforwards">Editatu ataka birbidalketak</string> + <string name="list_host_delete">Ezabatu ostalaria</string> + <string name="list_host_empty">Erabili azpiko konexio-azkarreko kaxa\nostalari batetara konektatzeko</string> + <string name="list_rotation_default">Lehenetsia</string> + <string name="list_rotation_land">Behartu horizontala</string> + <string name="list_rotation_port">Behartu bertikala</string> + <string name="list_rotation_auto">Automatikoa</string> + <string name="list_camera_ctrlaspace">Ktrl+A ondoren zuriunea</string> + <string name="list_camera_ctrla">Ktrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Ezer ez</string> + <string name="list_delkey_backspace">Atzera tekla</string> + <string name="list_delkey_del">Ezabatu</string> + <string name="delete_message">Ziur zaude \'%1$s\' ezabatu nahi duzula?</string> + <string name="delete_pos">Bai, ezabatu</string> + <string name="delete_neg">Utzi</string> + <string name="wizard_agree">Ados</string> + <string name="wizard_next">Hurrengoa</string> + <string name="wizard_back">Atzera</string> + <string name="terminal_no_hosts_connected">Ez dago ostalaririk konektatuta une honetan</string> + <string name="terminal_connecting">%1$s:%2$d-ra konektatzen %3$s bidez</string> + <string name="terminal_sucess">Egiaztatutako ostalaria \'%1$s\' gakoa: %2$s</string> + <string name="terminal_failed">Ostalariaren gakoaren egiaztapenak huts egin du</string> + <string name="terminal_using_s2c_algorithm">Zerbitzari-bezero algoritmoa: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Bezero-zerbitzari algoritmoa: %1$s %2$s</string> + <string name="terminal_using_algorithm">Uneko algoritmoa: %1$s %2$s</string> + <string name="terminal_auth">Autentifikatzen saiatzen</string> + <string name="terminal_auth_pass">\'Pasahitz bidez\' autentifikatzen saiatzen</string> + <string name="terminal_auth_pass_fail">\'Pasahitz bidezko\' autentifikatzeak huts egin du</string> + <string name="terminal_auth_pubkey_any">\'Gako publiko bidezko\' autentifikatze saiakera memoriako gako publikoak erabiliz</string> + <string name="terminal_auth_pubkey_invalid">Hautatutako gako publikoa baliogabea da, saiatu berriz hautatzen ostalari editorean</string> + <string name="terminal_auth_pubkey_specific">Saiatu \'gako publiko bidez\' autentifikatzen gako publiko zehatz batekin</string> + <string name="terminal_auth_pubkey_fail">\'%1$s\' gakoa erabiliz egindako \'gako publiko bidezko\' autenfikazioak huts egin du</string> + <string name="terminal_auth_ki">\'Teklatu bidezko autentifikazio elkarreragilea\' saiatzen</string> + <string name="terminal_auth_ki_fail">\'Teklatu bidezko autentifikazio elkarreragileak\' huts egin du</string> + <string name="terminal_auth_fail">[Zure ostalariak ez du onartzen \'pasahitz bidezko\' edo \'teklatu bidezko autentifikazio elkarreragilerik\'.]</string> + <string name="terminal_no_session">Saiorik ez da hasiko ostalariaren hobespenak direla eta.</string> + <string name="terminal_enable_portfoward">Gaitu ataka birbidalketa: %1$s</string> + <string name="local_shell_unavailable">Errorea! Shell lokala ez dago eskuragarri telefono honetan.</string> + <string name="notification_text">%1$s-k zure arreta behar du.</string> + <string name="no">Ez</string> + <string name="with_confirmation">Berrespenarekin</string> + <string name="yes">Bai</string> + <string name="exceptions_submit_message">Dirudienez ConnectBot-ek arazo bat izan zuen azken exekuzioan. ConnectBot-en garatzaileei errore-txostena bidali?</string> + <string name="menu_colors_reset">Berrezarri</string> + <string name="app_is_running">ConnectBot martxan dago</string> + <string name="color_red">gorria</string> + <string name="color_green">berdea</string> + <string name="color_blue">urdina</string> + <string name="color_gray">grisa</string> + <string name="image_description_connected">Konektatuta.</string> + <string name="image_description_key_is_locked">Gakoa blokeatuta dago.</string> + <string name="image_description_toggle_control_character">Txandakatu kontrol karakterea.</string> + <string name="image_description_send_escape_character">Bidali ihes karakterea.</string> + <string name="image_description_show_keyboard">Erakutsi teklatua.</string> +</resources> diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml new file mode 100644 index 0000000..94edc23 --- /dev/null +++ b/app/src/main/res/values-fa/strings.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources/> diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000..1e35e66 --- /dev/null +++ b/app/src/main/res/values-fi/strings.xml @@ -0,0 +1,211 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Yksinkertainen ja tehokas avoimen lähdekoodin SSH-asiakasohjelma.</string> + <string name="service_desc">Ylläpitää SSH-yhteydet ja ladatut julkiset avaimet</string> + <string name="title_hosts_list">Palvelimet</string> + <string name="title_pubkey_list">Julkiset avaimet</string> + <string name="title_port_forwards_list">Porttiohjaukset</string> + <string name="title_host_editor">Muokaa palvelinta</string> + <string name="title_help">Ohje</string> + <string name="title_colors">Värit</string> + <string name="resolve_connect">Yhdistä</string> + <string name="resolve_entropy">Satunnaisdatan keräys</string> + <string name="menu_insert">Lisää palvelin</string> + <string name="menu_delete">Poista palvelin</string> + <string name="menu_preferences">Asetukset</string> + <string name="help_intro">Valitse alta aihe, josta haluat lisätietoja.</string> + <string name="help_about">Tietoja ConnectBotista</string> + <string name="help_keyboard">Näppäimistö</string> + <string name="pubkey_generate">Luo</string> + <string name="pubkey_import">Tuo</string> + <string name="pubkey_delete">Poista avain</string> + <string name="pubkey_gather_entropy">Kerätään satunnaisdataa</string> + <string name="pubkey_touch_prompt">Kosketa tätä laatikkoa kerätäksesi satunnaisdataa: %1$d%% valmiina</string> + <string name="pubkey_touch_hint">Liikuta sormeasi alla olevan laatikon kohdalla sattumanvaraisesti varmistaaksesi avaimen luonnin satunnaisuuden.</string> + <string name="pubkey_generating">Luodaan avainparia...</string> + <string name="pubkey_copy_private">Kopio yksityinen avain</string> + <string name="pubkey_copy_public">Kopioi julkinen avain</string> + <string name="pubkey_list_empty">Paina valikkopainiketta\nluodaksesi tai tuodaksesi\navainpareja.</string> + <string name="pubkey_unknown_format">Tuntematon tyyppi</string> + <string name="pubkey_change_password">Vaihda salasana</string> + <string name="pubkey_list_pick">Hae sijainnista /sdcard</string> + <string name="pubkey_import_parse_problem">Ongelma käsiteltäessä tuotua avainparia</string> + <string name="pubkey_unlock">Poista avaimen lukitus</string> + <string name="pubkey_failed_add">Väärä salasana avaimelle \'%1$s\'. Tunnistus epäonnistui.</string> + <string name="pubkey_memory_load">Lataa muistiin</string> + <string name="pubkey_memory_unload">Poista muistista</string> + <string name="pubkey_load_on_start">Lataa avain käynnistyksen yhteydessä</string> + <string name="pubkey_confirm_use">Vahvista ennen käyttöä</string> + <string name="portforward_list_empty">Paina valikkopainiketta\nluodaksesi porttiohjauksia.</string> + <string name="portforward_edit">Muokkaa porttiohjausta</string> + <string name="portforward_delete">Poista porttiohjaus</string> + <string name="prompt_nickname">Lempinimi:</string> + <string name="prompt_nickname_hint_pubkey">Työavain</string> + <string name="prompt_source_port">Lähdeportti:</string> + <string name="prompt_destination">Kohde:</string> + <string name="prompt_old_password">Vanha salasana:</string> + <string name="prompt_password">Salasana:</string> + <string name="prompt_again">(uudelleen)</string> + <string name="prompt_type">Tyyppi:</string> + <string name="prompt_password_can_be_blank">Huomio: salasana voi olla sisältämättä merkkejä</string> + <string name="prompt_bits">Bittejä:</string> + <string name="prompt_pubkey_password">Salasana avaimelle \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Hyväksy etäyhteys:\nkäytä avainta \'%1$s\'</string> + <string name="host_verification_failure_warning_header">VAROITUS: ETÄPÄÄN KONEEN TUNNISTE ON MUUTTUNUT!</string> + <string name="host_verification_failure_warning">JOKU SAATTAA OLLA TEKEMÄSSÄ PAHOJAAN!\nOn mahdollista, että yhteyttäsi salakuunnellaan (niin kutsuttu man-in-the-middle -hyökkäys)!\nOn toki myös mahdollista, että palvelimen avainta vain on muutettu.</string> + <string name="prompt_host_disconnected">Palvelin katkaisi yhteyden.\nSuljetaanko istunto?</string> + <string name="prompt_continue_connecting">Haluatko varmasti\njatkaa yhdistämistä?</string> + <string name="host_authenticity_warning">Palvelimen \'%1$s\' aitoutta ei voida varmentaa</string> + <string name="host_fingerprint">Palvelimen %1$s avaimen sormenjälki: %2$s</string> + <string name="alert_passwords_do_not_match_msg">Salasanat eivät täsmää!</string> + <string name="alert_wrong_password_msg">Väärä salasana!</string> + <string name="alert_key_corrupted_msg">Yksityinen avain vaikuttaa korruptoituneelta.</string> + <string name="alert_sdcard_absent">Laitteessa ei ole muistikorttia!</string> + <string name="button_add">Lisää</string> + <string name="button_change">Muokkaa</string> + <string name="button_generate">Luo avain</string> + <string name="button_resize">Muuta kokoa</string> + <string name="alert_disconnect_msg">Yhteys katkesi</string> + <string name="msg_copyright">Tekijänoikeudet © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Pääte-emulaattori</string> + <string name="pref_emulation_title">Emulointitila</string> + <string name="pref_emulation_summary">PTY-yhteyksissä käytettävä emulointitila</string> + <string name="pref_scrollback_title">Vierityspuskurin koko</string> + <string name="pref_scrollback_summary">Konsolikohtainen koko vierityspuskurille, jossa pidetään konsolin historiaa</string> + <string name="pref_ui_category">Käyttöliittymä</string> + <string name="pref_rotation_title">Näytön pyöritys</string> + <string name="pref_rotation_summary">Miten näyttöä pyöritetään kun näppäimistö tulee näkyviin/poistuu näkyvistä</string> + <string name="pref_fullscreen_title">Kokoruutu</string> + <string name="pref_fullscreen_summary">Piilota ilmoitusalue konsolikäytössä</string> + <string name="pref_memkeys_title">Pidä avaimet muistissa</string> + <string name="pref_memkeys_summary">Pitää avatut avaimet muistissa kunnes taustapalvelu suljetaan</string> + <string name="pref_conn_persist_title">Ylläpidä yhteys</string> + <string name="pref_conn_persist_summary">Pakottaa yhteyden pysymään aktiivisena ohjelman ollessa taustalla</string> + <string name="pref_keymode_title">Pikanäppäimet</string> + <string name="pref_camera_title">Kamera-pikanäppäin</string> + <string name="pref_camera_summary">Valitse kamerapainikkeesta suoritettava toiminto</string> + <string name="pref_keepalive_title">Pidä näyttö päällä</string> + <string name="pref_keepalive_summary">Estää näytön sammumisen työskenneltäessä konsolissa</string> + <string name="pref_wifilock_title">Pidä Wi-Fi-yhteys päällä</string> + <string name="pref_wifilock_summary">Estää Wi-Fi-yhteyden katkaisemisen kun istunto on aktiivinen</string> + <string name="pref_bumpyarrows_title">Värinä nuolista</string> + <string name="pref_bumpyarrows_summary">Värinäefekti aina kun nuolinäppäinten painalluksia lähetetään palvelimelle. Kätevä hitaille yhteyksille.</string> + <string name="pref_bell_category">Päätteen kello</string> + <string name="pref_bell_title">Kellon äänet käytössä</string> + <string name="pref_bell_volume_title">Kellon äänenvoimakkuus</string> + <string name="pref_bell_vibrate_title">Värinäefekti kellosta</string> + <string name="pref_bell_notification_title">Taustailmoitukset</string> + <string name="pref_bell_notification_summary">Näyttää ilmoituksen kun taustalla olevassa päätteessä soitetaan kelloa.</string> + <string name="list_keymode_right">Käytä oikean reunan erikoispainikkeita</string> + <string name="list_keymode_left">Käytä vasemman reunan erikoispainikkeita</string> + <string name="list_keymode_none">Pois käytöstä</string> + <string name="list_pubkeyids_none">Älä käytä avaimia</string> + <string name="list_pubkeyids_any">Käytä mitä tahansa avattua avainta</string> + <string name="hostpref_nickname_title">Lempinimi</string> + <string name="hostpref_color_title">Väri</string> + <string name="hostpref_fontsize_title">Kirjasimen koko (pistettä)</string> + <string name="hostpref_pubkeyid_title">Kirjaudu avainparilla</string> + <string name="hostpref_authagent_title">Käytä SSH-kirjautumisagenttia</string> + <string name="hostpref_postlogin_title">Kirjautumisen jälkeiset automaattiset toimenpiteet</string> + <string name="hostpref_postlogin_summary">Palvelimella kirjautumisen jälkeen ajettavat komennot</string> + <string name="hostpref_compression_title">Pakkaus</string> + <string name="hostpref_compression_summary">Tämä voi helpottaa hitaiden yhteyksien käyttämistä</string> + <string name="hostpref_wantsession_title">Käynnistä komentorivi-istunto</string> + <string name="hostpref_wantsession_summary">Ota tämä pois käytöstä jos käytät vain porttiohjausta</string> + <string name="hostpref_stayconnected_title">Pysy yhteydessä</string> + <string name="hostpref_stayconnected_summary">Yritä yhdistää palvelimeen uudelleen yhteyden katkeamisen jälkeen</string> + <string name="hostpref_delkey_title">Del-näppäin</string> + <string name="hostpref_delkey_summary">Del-näppäintä painettaessa lähetettävä näppäinkoodi</string> + <string name="hostpref_encoding_title">Merkistökoodaus</string> + <string name="hostpref_encoding_summary">Palvelimella käytettävä merkistökoodaus</string> + <string name="hostpref_connection_category">Yhteysasetukset</string> + <string name="hostpref_username_title">Käyttäjätunnus</string> + <string name="hostpref_hostname_title">Palvelin</string> + <string name="hostpref_port_title">Portti</string> + <string name="bind_never">Ei koskaan yhdistetty</string> + <string name="bind_minutes">%1$s minuuttia sitten</string> + <string name="bind_hours">%1$s tuntia sitten</string> + <string name="bind_days">%1$s päivää sitten</string> + <string name="console_copy_done">%1$d tavua kopioitu leikepöydälle</string> + <string name="console_copy_start">Maalaa koskettamalla\ntai käytä nuolinäppäimiä\nvalitaksesi kopioitavan alueen</string> + <string name="console_menu_close">Sulje</string> + <string name="console_menu_copy">Kopioi</string> + <string name="console_menu_paste">Liitä</string> + <string name="console_menu_portforwards">Porttiohjaukset</string> + <string name="console_menu_resize">Pakota koko</string> + <string name="console_menu_urlscan">URL-haku</string> + <string name="button_yes">Kyllä</string> + <string name="button_no">Ei</string> + <string name="portforward_local">Paikallinen</string> + <string name="portforward_remote">Etä</string> + <string name="portforward_dynamic">Dynaaminen (SOCKS)</string> + <string name="portforward_pos">Luo porttiohjaus</string> + <string name="portforward_done">Porttiohjaus luotu onnistuneesti</string> + <string name="portforward_problem">Ongelma luotaessa porttiohjausta. Ehkä käytät porttia joka on alle 1024 tai portti on jo käytössä?</string> + <string name="portforward_menu_add">Lisää porttiohjaus</string> + <string name="hint_userhost">tunnus\@palvelin</string> + <string name="list_format_error">Käytä muotoa %1$s</string> + <string name="format_username">käyttäjätunnus</string> + <string name="format_hostname">palvelin</string> + <string name="format_port">portti</string> + <string name="list_menu_pubkeys">Hallitse avaimia</string> + <string name="list_menu_sortcolor">Lajittele värin mukaan</string> + <string name="list_menu_sortname">Lajittele nimen mukaan</string> + <string name="list_menu_settings">Asetukset</string> + <string name="list_host_disconnect">Katkaise</string> + <string name="list_host_edit">Muokkaa palvelinta</string> + <string name="list_host_portforwards">Muokkaa porttiohjauksia</string> + <string name="list_host_delete">Poista palvelin</string> + <string name="list_host_empty">Käytä alla olevaa tekstikenttää\nyhdistääksesei palvelimeen.</string> + <string name="list_rotation_default">Oletus</string> + <string name="list_rotation_land">Pakota vaakatasoon</string> + <string name="list_rotation_port">Pakota pystysuuntaan</string> + <string name="list_rotation_auto">Automaattinen</string> + <string name="list_camera_ctrlaspace">Ctrl+A ja välilyönti</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Ei mitään</string> + <string name="list_delkey_backspace">Askelpalautin</string> + <string name="list_delkey_del">Delete</string> + <string name="delete_message">Oletko varma että haluat poistaa: \'%1$s\'?</string> + <string name="delete_pos">Kyllä, poista</string> + <string name="delete_neg">Peru</string> + <string name="wizard_agree">Hyväksy</string> + <string name="wizard_next">Seuraava</string> + <string name="wizard_back">Edellinen</string> + <string name="terminal_no_hosts_connected">Yhteenkään palvelimeen ei ole yhdistetty</string> + <string name="terminal_connecting">Muodostetaan %3$s-yhteys palvelimeen %1$s:%2$d</string> + <string name="terminal_sucess">Varmennettiin palvelin \'%1$s\' avain: %2$s</string> + <string name="terminal_failed">Palvelimen avaimen varmistaminen epäonnistui</string> + <string name="terminal_using_s2c_algorithm">Salausalgoritmi palvelimelta asiakkaalle: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Salausalgoritmi asiakkaalta palvelimelle: %1$s %2$s</string> + <string name="terminal_using_algorithm">Käytetään salausalgoritmia %1$s %2$s</string> + <string name="terminal_auth">Kirjaudutaan</string> + <string name="terminal_auth_pass">Yritetään \'salasana\' tunnistusta</string> + <string name="terminal_auth_pass_fail">Varmennusmenetelmä \'password\' epäonnistui</string> + <string name="terminal_auth_pubkey_any">Yritetään \'publickey\' varmennusta millä tahansa muistissa olevalla julkisella avaimella</string> + <string name="terminal_auth_pubkey_invalid">Valittu julkinen avain ei kelpaa, kokeile valita toinen avain palvelimen asetuksista</string> + <string name="terminal_auth_pubkey_specific">Yritetään \'publickey\' varmennusta tietyllä julkisella avaimella</string> + <string name="terminal_auth_pubkey_fail">Varmennusmenetelmä \'publickey\' avaimella \'%1$s\' epäonnistui</string> + <string name="terminal_auth_ki">Yritetään \'keyboard-interactive\' varmennusta</string> + <string name="terminal_auth_ki_fail">Varmennusmenetelmä \'keyboard-interactive\' epäonnistui</string> + <string name="terminal_auth_fail">[Palvelin ei tue \'password\' tai \'keyboard-interactive\' varmennusmenetelmiä.]</string> + <string name="terminal_no_session">Istuntoa ei aloiteta palvelimen asetuksista johtuen</string> + <string name="terminal_enable_portfoward">Ota käyttöön porttiohjaus %1$s</string> + <string name="local_shell_unavailable">Virhe! Paikallista komentoriviä ei ole käytettävissä</string> + <string name="notification_text">%1$s kaipaa huomiotasi.</string> + <string name="no">Ei</string> + <string name="with_confirmation">Kysy varmistus</string> + <string name="yes">Kyllä</string> + <string name="exceptions_submit_message">ConnectBotilla vaikutti olevan ongelmia kun sitä käytettiin edellisen kerran. Lähetetäänkö virheraportti ohjelman kehittäjille?</string> + <string name="menu_colors_reset">Palauta</string> + <string name="app_is_running">ConnectBot on käynnissä</string> + <string name="color_red">punainen</string> + <string name="color_green">vihreä</string> + <string name="color_blue">sininen</string> + <string name="color_gray">harmaa</string> + <string name="image_description_connected">Yhdistetty.</string> + <string name="image_description_send_escape_character">Lähetä escape.</string> + <string name="image_description_show_keyboard">Näytä näppäimistö</string> +</resources> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..86d7f29 --- /dev/null +++ b/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,224 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Un client SSH simple, open-source et puissant.</string> + <string name="service_desc">Maintient les connexions SSH et les clefs publiques chargées</string> + <string name="title_hosts_list">Serveurs</string> + <string name="title_pubkey_list">Clés publiques</string> + <string name="title_port_forwards_list">Redirections de ports</string> + <string name="title_host_editor">Modifier le serveur</string> + <string name="title_help">Aide</string> + <string name="title_colors">Couleurs</string> + <string name="resolve_connect">Connexion</string> + <string name="resolve_entropy">Générer de l\'entropie</string> + <string name="menu_insert">Ajouter un serveur</string> + <string name="menu_delete">Supprimer le serveur</string> + <string name="menu_preferences">Préférences</string> + <string name="help_intro">Sélectionnez un sujet ci-dessous pour plus d\'informations.</string> + <string name="help_about">À propos de ConnectBot</string> + <string name="help_keyboard">Clavier</string> + <string name="pubkey_generate">Générer</string> + <string name="pubkey_import">Importer</string> + <string name="pubkey_delete">Supprimer la clé</string> + <string name="pubkey_gather_entropy">Génération d\'entropie</string> + <string name="pubkey_touch_prompt">Touchez cette boîte pour générer de l\'entropie : %1$d%% effectué</string> + <string name="pubkey_touch_hint">Afin d\'assurer la qualité des nombres aléatoires utilisés pendant la génération de la clé, merci de bouger votre doigt de façon aléatoire sur la boîte ci-dessous.</string> + <string name="pubkey_generating">Génération de la paire de clés en cours...</string> + <string name="pubkey_copy_private">Copier la clé privée</string> + <string name="pubkey_copy_public">Copier la clé publique</string> + <string name="pubkey_list_empty">Cliquer sur le Menu pour créer\nou importer des paires de clés.</string> + <string name="pubkey_unknown_format">Format inconnu</string> + <string name="pubkey_change_password">Changer le mot de passe</string> + <string name="pubkey_list_pick">Récupérer depuis /sdcard</string> + <string name="pubkey_import_parse_problem">Problème lors du parsage de la clé privée importée</string> + <string name="pubkey_unlock">Déverrouiller la clé</string> + <string name="pubkey_failed_add">Mot de passe incorrect pour la clé \'%1$s\'. L\'authentification a échoué.</string> + <string name="pubkey_memory_load">Charger en mémoire</string> + <string name="pubkey_memory_unload">Décharger de la mémoire</string> + <string name="pubkey_load_on_start">Charger la clé au démarrage</string> + <string name="pubkey_confirm_use">Confirmer avant utilisation</string> + <string name="portforward_list_empty">Appuyez sur Menu pour créer\ndes redirections de port.</string> + <string name="portforward_edit">Éditer la redirection de port</string> + <string name="portforward_delete">Effacer la redirection de port</string> + <string name="prompt_nickname">Pseudonyme :</string> + <string name="prompt_nickname_hint_pubkey">Ma clé de travail</string> + <string name="prompt_source_port">Port source :</string> + <string name="prompt_destination">Destination :</string> + <string name="prompt_old_password">Ancien mot de passe :</string> + <string name="prompt_password">Mot de passe :</string> + <string name="prompt_again">(encore)</string> + <string name="prompt_type">Type :</string> + <string name="prompt_password_can_be_blank">Note : le mot de passe peut être vide</string> + <string name="prompt_bits">Bits :</string> + <string name="prompt_pubkey_password">Mot de passe pour la clé \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Autoriser l\'hôte distant à\nutiliser la clé \'%1$s\' ?</string> + <string name="host_verification_failure_warning_header">ATTENTION : L\'IDENTIFICATION DE L\'HÔTE DISTANT A CHANGÉ !</string> + <string name="host_verification_failure_warning">IL EST POSSIBLE QUE QUELQU\'UN FASSE QUELQUE CHOSE DE NUISIBLE !\nQuelqu\'un pourrait vous écouter en ce moment (attaque de l\'homme du milieu) !\nIl est aussi possible que la clé du serveur ait juste changée.</string> + <string name="prompt_host_disconnected">L\'hôte s\'est déconnecté.\nFermer la session ?</string> + <string name="prompt_continue_connecting">Êtes-vous sûr de vouloir\ncontinuer à vous connecter ?</string> + <string name="host_authenticity_warning">L\'authenticité du serveur \'%1$s\' ne peut être établie</string> + <string name="host_fingerprint">L\'empreinte de la clé du serveur %1$s est %2$s</string> + <string name="alert_passwords_do_not_match_msg">Les mots de passe sont différents !</string> + <string name="alert_wrong_password_msg">Mauvais mot de passe !</string> + <string name="alert_key_corrupted_msg">La clé privé semble corrompue !</string> + <string name="alert_sdcard_absent">Pas de carte SD dans le lecteur !</string> + <string name="button_add">Ajouter</string> + <string name="button_change">Changer</string> + <string name="button_generate">Générer une clé</string> + <string name="button_resize">Retailler</string> + <string name="alert_disconnect_msg">Connexion perdue</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Émulation du mode terminal</string> + <string name="pref_emulation_title">Mode d\'émulation</string> + <string name="pref_emulation_summary">Mode d\'émulation de terminal à utiliser pour les connexions PTY</string> + <string name="pref_scrollback_title">Taille de l\'historique</string> + <string name="pref_scrollback_summary">Taille de l\'historique à garder en mémoire pour chaque console</string> + <string name="pref_ui_category">Interface utilisateur</string> + <string name="pref_rotation_title">Mode de rotation</string> + <string name="pref_rotation_summary">Comment changer l\'orientation de l\'écran suivant si le clavier est déplié</string> + <string name="pref_titlebarhide_title">Masquer automatiquement la barre de titre</string> + <string name="pref_titlebarhide_summary">Appuyez sur console pour montrer la barre de titre et accèder au menu</string> + <string name="pref_fullscreen_title">Plein écran</string> + <string name="pref_fullscreen_summary">Cacher la barre de notifications lorsque la console est active</string> + <string name="pref_shiftfkeys_title">\"Shift+Numéro\" sont les touches de \"F1-F10\"</string> + <string name="pref_shiftfkeys_summary">Sur les claviers matériels, les touches des chiffres envoient F1-F10 avec la touche Shift</string> + <string name="pref_ctrlfkeys_title">Ctrl+Numéro sont les touches de \"F1-F10\"</string> + <string name="pref_ctrlfkeys_summary">Sur les claviers logiciels, les touches des chiffres envoient F1-F10 avec la touche Ctrl</string> + <string name="pref_volumefont_title">Les touches volume changent la taille du texte</string> + <string name="pref_volumefont_summary">La taille du texte peu aussi être changé dans les paramètres par hôte</string> + <string name="pref_memkeys_title">Garder les clés en mémoire</string> + <string name="pref_memkeys_summary">Garder les clés débloquées en mémoire tant que le service tourne en arrière-plan</string> + <string name="pref_conn_persist_title">Connexions persistantes</string> + <string name="pref_conn_persist_summary">Forcer les connexions à rester connectées en arrière-plan</string> + <string name="pref_keymode_title">Raccourcis de navigation</string> + <string name="pref_keymode_summary">Définir quel couple de touches Alt et Shift utiliser pour \'/\' et Tab</string> + <string name="pref_camera_title">Raccourci du bouton Appareil photo</string> + <string name="pref_camera_summary">Sélectionnez le raccourci à exécuter quand le bouton caméra est pressé</string> + <string name="pref_keepalive_title">Garder l\'écran allumé</string> + <string name="pref_keepalive_summary">Empêcher la mise en veille de l\'écran pendant une session console</string> + <string name="pref_wifilock_title">Garder le Wi-Fi actif</string> + <string name="pref_wifilock_summary">Empêcher la désactivation du Wi-Fi quand une session est active</string> + <string name="pref_bumpyarrows_title">Flêches vibrantes</string> + <string name="pref_bumpyarrows_summary">Vibrer lors de l\'envoi d\'une touche directionnelle à partir du trackball ; intéressant pour les connexions lentes</string> + <string name="pref_bell_category">Cloche du terminal</string> + <string name="pref_bell_title">Alerte sonore</string> + <string name="pref_bell_volume_title">Volume de l\'alerte</string> + <string name="pref_bell_vibrate_title">Vibrer en cas d\'alerte</string> + <string name="pref_bell_notification_title">Notifications en arrière-plan</string> + <string name="pref_bell_notification_summary">Envoyer une notification lorsqu\'un terminal en arrière-plan génère une alerte</string> + <string name="list_keymode_right">Utiliser les touches du côté droit du clavier</string> + <string name="list_keymode_left">Utiliser les touches du côté gauche du clavier</string> + <string name="list_keymode_none">Désactivé</string> + <string name="list_pubkeyids_none">Ne pas utiliser les clés publiques</string> + <string name="list_pubkeyids_any">Utiliser une clé pour l\'authentification</string> + <string name="hostpref_nickname_title">Surnom</string> + <string name="hostpref_color_title">Catégorie de couleur</string> + <string name="hostpref_fontsize_title">Taille de police (pt)</string> + <string name="hostpref_pubkeyid_title">Utiliser l\'authentification par clé publique</string> + <string name="hostpref_authagent_title">Utiliser l\'agent d\'authentification SSH</string> + <string name="hostpref_postlogin_title">Commande à exécuter automatiquement après l\'authentification</string> + <string name="hostpref_postlogin_summary">Commandes à lancer sur le serveur distant après s\'être authentifié</string> + <string name="hostpref_compression_title">Compression</string> + <string name="hostpref_compression_summary">Cela peut aider pour les réseaux lents</string> + <string name="hostpref_wantsession_title">Démarrer une session en ligne de commande</string> + <string name="hostpref_wantsession_summary">Décochez cette case pour n\'utiliser que les redirections de port</string> + <string name="hostpref_stayconnected_title">Rester connecté</string> + <string name="hostpref_stayconnected_summary">Essayer de se reconnecter à l\'hôte si on est déconnecté</string> + <string name="hostpref_delkey_title">Touche SUPPR</string> + <string name="hostpref_delkey_summary">Le code de touche envoyé quand la touche SUPPR est appuyée.</string> + <string name="hostpref_encoding_title">Encodage</string> + <string name="hostpref_encoding_summary">Jeu de caractères à utiliser pour le serveur</string> + <string name="hostpref_connection_category">Paramètres de connexion</string> + <string name="hostpref_username_title">Nom d\'utilisateur</string> + <string name="hostpref_hostname_title">Serveur</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Jamais connecté</string> + <string name="bind_minutes">Il y a %1$s minutes</string> + <string name="bind_hours">Il y a %1$s heures</string> + <string name="bind_days">Il y a %1$s jours</string> + <string name="console_copy_done">%1$d octets copiés dans le presse-papier</string> + <string name="console_copy_start">Touchez et tirez, ou utilisez\nles touches directionnelles\npour sélectionner la région\nà copier</string> + <string name="console_menu_close">Fermer</string> + <string name="console_menu_copy">Copier</string> + <string name="console_menu_paste">Coller</string> + <string name="console_menu_portforwards">Redirections de port</string> + <string name="console_menu_resize">Forcer la taille</string> + <string name="console_menu_urlscan">Lister les URL</string> + <string name="button_yes">Oui</string> + <string name="button_no">Non</string> + <string name="portforward_local">Local</string> + <string name="portforward_remote">Distant</string> + <string name="portforward_dynamic">Dynamique (SOCKS)</string> + <string name="portforward_pos">Créer la redirection de port</string> + <string name="portforward_done">Redirection de port créée avec succès</string> + <string name="portforward_problem">Problème de création de la redirection de port, peut-être utilisez-vous un port sous le 1024 ou un déjà utilisé ?</string> + <string name="portforward_menu_add">Ajouter une redirection de port</string> + <string name="hint_userhost">utilisateur\@serveur</string> + <string name="list_format_error">Utilisez le format \"%1$s\"</string> + <string name="format_username">utilisateur</string> + <string name="format_hostname">nom d\'hôte</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Gérer les clés publiques</string> + <string name="list_menu_sortcolor">Trier par couleur</string> + <string name="list_menu_sortname">Trier par nom</string> + <string name="list_menu_settings">Réglages</string> + <string name="list_host_disconnect">Déconnexion</string> + <string name="list_host_edit">Éditer le serveur</string> + <string name="list_host_portforwards">Éditer les redirections de port</string> + <string name="list_host_delete">Effacer le serveur</string> + <string name="list_host_empty">Utilisez la boîte de connexion rapide en bas\npour vous connecter à un serveur</string> + <string name="list_rotation_default">Par défaut</string> + <string name="list_rotation_land">Forcer le mode paysage</string> + <string name="list_rotation_port">Forcer le mode portrait</string> + <string name="list_rotation_auto">Automatique</string> + <string name="list_camera_ctrlaspace">Ctrl+A puis Espace</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Échap</string> + <string name="list_camera_esc_a">Echap+A</string> + <string name="list_camera_none">Aucun</string> + <string name="list_delkey_backspace">Retour arrière</string> + <string name="list_delkey_del">Supprimer</string> + <string name="delete_message">Êtes-vous sûr de vouloir supprimer \'%1$s\' ?</string> + <string name="delete_pos">Oui, supprimer</string> + <string name="delete_neg">Annuler</string> + <string name="wizard_agree">Accepter</string> + <string name="wizard_next">Suivant</string> + <string name="wizard_back">Précédent</string> + <string name="terminal_no_hosts_connected">Aucun hôte actuellement connecté</string> + <string name="terminal_connecting">Connexion à %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Clé de vérification de l\'hôte \'%1$s\' : %2$s</string> + <string name="terminal_failed">Authentification par clé échouée</string> + <string name="terminal_using_s2c_algorithm">Algorithme serveur vers client : %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algorithme client vers serveur : %1$s %2$s</string> + <string name="terminal_using_algorithm">Utilisation de l\'algorithme de chiffrement : %1$s %2$s</string> + <string name="terminal_auth">Tentative d\'authentification</string> + <string name="terminal_auth_pass">Tentative d\'authentification par mot de passe</string> + <string name="terminal_auth_pass_fail">L\'authentification par mot de passe a échoué</string> + <string name="terminal_auth_pubkey_any">Tentative d\'authentification avec une clé publique en mémoire</string> + <string name="terminal_auth_pubkey_invalid">La clé publique sélectionnée est invalide, essayez de sélectionner une clé dans les préférences de l\'hôte</string> + <string name="terminal_auth_pubkey_specific">Tentative d\'authentification par clé publique avec une clé publique spécifique</string> + <string name="terminal_auth_pubkey_fail">La méthode d\'authentification par clé publique avec la clé \'%1$s\' a échoué.</string> + <string name="terminal_auth_ki">Tentative d\'authentification par clavier</string> + <string name="terminal_auth_ki_fail">La méthode d\'authentification par clavier a échoué</string> + <string name="terminal_auth_fail">[L\'hôte ne supporte pas l\'authentification par mot de passe ou par clavier.]</string> + <string name="terminal_no_session">La session ne sera pas démarrée à cause des préférences de l\'hôte.</string> + <string name="terminal_enable_portfoward">Activer la redirection de port : %1$s</string> + <string name="local_shell_unavailable">Échec ! Le shell local est indisponible pour ce téléphone.</string> + <string name="notification_text">%1$s requiert votre attention.</string> + <string name="no">Non</string> + <string name="with_confirmation">Après confirmation</string> + <string name="yes">Oui</string> + <string name="exceptions_submit_message">Il semblerait que ConnectBot ait eu un problème lors de sa dernière utilisation. Voulez-vous envoyer un rapport d\'erreur aux développeurs de ConnectBot ?</string> + <string name="menu_colors_reset">Réinitialiser</string> + <string name="app_is_running">ConnectBot est en cours</string> + <string name="color_red">rouge</string> + <string name="color_green">vert</string> + <string name="color_blue">bleu</string> + <string name="color_gray">gris</string> + <string name="colors_fg_label">PP: %1$d</string> + <string name="color_bg_label">AP: %1$d</string> + <string name="image_description_connected">Connecté</string> + <string name="image_description_key_is_locked">Clef verrouillée</string> + <string name="image_description_toggle_control_character">Activer/désactiver le caractère \"contrôle\"</string> + <string name="image_description_send_escape_character">Envoyer \"échap\"</string> + <string name="image_description_show_keyboard">Afficher le clavier.</string> +</resources> diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml new file mode 100644 index 0000000..c88fe6b --- /dev/null +++ b/app/src/main/res/values-gl/strings.xml @@ -0,0 +1,118 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Cliente SSH simple, potente e open-source</string> + <string name="title_hosts_list">Servidores</string> + <string name="title_pubkey_list">Chaves públicas</string> + <string name="title_port_forwards_list">Redirección de portos</string> + <string name="title_host_editor">Editar servidor</string> + <string name="title_help">Axuda</string> + <string name="title_colors">Cores</string> + <string name="resolve_connect">Conectar</string> + <string name="resolve_entropy">Xerar entropía</string> + <string name="menu_insert">Engadir un servidor</string> + <string name="menu_delete">Borrar servidor</string> + <string name="menu_preferences">Preferencias</string> + <string name="help_intro">Seleccione un apartado dos seguintes para máis información dun tema particular</string> + <string name="help_about">Acerca de ConnectBot</string> + <string name="help_keyboard">Teclado</string> + <string name="pubkey_generate">Xerar</string> + <string name="pubkey_import">Importar</string> + <string name="pubkey_delete">Borrar a chave</string> + <string name="pubkey_gather_entropy">Xerando Entropía</string> + <string name="pubkey_touch_prompt">Toca esta casiña para xerar aleatoriedade: %1$d%% feito</string> + <string name="pubkey_touch_hint">Para garantir aleatoriedade durante a xeración da chave, mova o dedo aleatoriamente na casiña a continuación</string> + <string name="pubkey_generating">Xerando par de chaves...</string> + <string name="pubkey_copy_private">Copiar chave privada</string> + <string name="pubkey_copy_public">Copiar chave pública</string> + <string name="pubkey_list_empty">Pulse Menu para crear\nou importar pares de chaves</string> + <string name="pubkey_unknown_format">Formato descoñecido</string> + <string name="pubkey_change_password">Cambiar o contrasinal</string> + <string name="pubkey_list_pick">Seleccionar da /sdcard</string> + <string name="pubkey_import_parse_problem">Problema analisando a chave privada importada</string> + <string name="pubkey_unlock">Desbloquear chave</string> + <string name="pubkey_memory_load">Cargar en memoria</string> + <string name="pubkey_memory_unload">Descargar de memoria</string> + <string name="pubkey_load_on_start">Cargar chave ao iniciar</string> + <string name="pubkey_confirm_use">Confirmar antes de usar</string> + <string name="portforward_list_empty">Pulsar Menu para crear\nredireccións de portos</string> + <string name="portforward_edit">Editar redirección de porto</string> + <string name="portforward_delete">Borrar redirección de porto</string> + <string name="prompt_nickname">Seudónimo:</string> + <string name="prompt_nickname_hint_pubkey">A miña chave do traballo</string> + <string name="prompt_source_port">Porto orixe:</string> + <string name="prompt_destination">Destino:</string> + <string name="prompt_old_password">Contrasinal antigo:</string> + <string name="prompt_password">Contrasinal:</string> + <string name="prompt_again">(de novo)</string> + <string name="prompt_type">Tipo:</string> + <string name="prompt_password_can_be_blank">Nota: o contrasinal pode quedar baleiro</string> + <string name="host_verification_failure_warning_header">¡ATENCIÓN: A IDENTIFICACIÓN DO EQUIPO REMOTO CAMBIOU!</string> + <string name="host_verification_failure_warning">¡É POSIBLE QUE HAXA ALGUÉN FACENDO ALGO INAXEITADO!\n¡Alguén podería estar intervindo neste momento (cun ataque man-in-the-middle)!\nTamén é posible que a chave do equipo simplemente cambiara.</string> + <string name="prompt_host_disconnected">O equipo desconectouse.\n¿Pechar a sesión?</string> + <string name="prompt_continue_connecting">Seguro que quere\ncontinuar conectándose?</string> + <string name="alert_passwords_do_not_match_msg">¡Non coinciden os contrasinais!</string> + <string name="alert_wrong_password_msg">¡Contrasinal incorrecto!</string> + <string name="alert_key_corrupted_msg">¡A chave privada parece corrupta!</string> + <string name="alert_sdcard_absent">¡A tarxeta SD non está inserida!</string> + <string name="button_add">Engadir</string> + <string name="button_change">Modificar</string> + <string name="button_generate">Xerar chave</string> + <string name="button_resize">Redimensionar</string> + <string name="alert_disconnect_msg">Conexión Perdida</string> + <string name="msg_copyright">Todos os dereitos reservados © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulación de terminal</string> + <string name="pref_emulation_title">Modo de emulación</string> + <string name="pref_emulation_summary">Modo de emulación de terminal para usar en conexións PTY</string> + <string name="pref_scrollback_title">Tamaño de navegación</string> + <string name="pref_scrollback_summary">Tamaño de búfer que quedará en memoria para cada consola</string> + <string name="pref_ui_category">Interface de usuario</string> + <string name="pref_rotation_title">Modo de rotación</string> + <string name="pref_rotation_summary">Como cambiar a rotación ao abrir/pechar o teclado</string> + <string name="pref_fullscreen_title">Pantalla completa</string> + <string name="pref_fullscreen_summary">Esconder a barra de estado cando estea na consola</string> + <string name="pref_memkeys_title">Recordar chaves en memoria</string> + <string name="pref_memkeys_summary">Manter chaves desbloqueadas en memoria ata que o servicio de backend teña terminado</string> + <string name="pref_conn_persist_title">Conexións persistentes</string> + <string name="pref_conn_persist_summary">Manter conexións activas en segundo plano</string> + <string name="pref_keymode_title">Atallos de teclado para directorios</string> + <string name="pref_camera_title">Atallo de teclado para a cámara</string> + <string name="pref_keepalive_title">Manter pantalla activa</string> + <string name="pref_keepalive_summary">Evitar que a pantalla se apague ao traballar nunha consola</string> + <string name="pref_wifilock_title">Manter a Wi-Fi activa</string> + <string name="pref_wifilock_summary">Evitar que a Wi-Fi se apague mentres a sesión está activa</string> + <string name="pref_bumpyarrows_summary">Vibrar ao enviar teclas de dirección dende o trackball; útil para conexións con retardos</string> + <string name="pref_bell_category">Timbre do terminal</string> + <string name="pref_bell_title">Timbre audible</string> + <string name="pref_bell_volume_title">Volume do timbre</string> + <string name="pref_bell_vibrate_title">Vibrar co timbre</string> + <string name="pref_bell_notification_title">Notificacións en segundo plano</string> + <string name="list_keymode_none">Deshabilitar</string> + <string name="list_pubkeyids_none">Non usar chaves</string> + <string name="list_pubkeyids_any">Usar calquera chave desbloqueada</string> + <string name="hostpref_fontsize_title">Tamaño da fonte (pt)</string> + <string name="hostpref_postlogin_summary">Comandos a executar no servidor remoto unha vez autenticado</string> + <string name="hostpref_compression_title">Compresión</string> + <string name="hostpref_compression_summary">Esto podería axudar en redes lentas</string> + <string name="hostpref_wantsession_title">Comezar sesión de shell</string> + <string name="hostpref_encoding_title">Codificación</string> + <string name="hostpref_encoding_summary">Codificación de caracteres para a máquina remota</string> + <string name="hostpref_connection_category">Configuracion da conexión</string> + <string name="hostpref_username_title">Nome de usuario</string> + <string name="hostpref_hostname_title">Máquina</string> + <string name="hostpref_port_title">Porto</string> + <string name="bind_never">Nunca conectado</string> + <string name="bind_minutes">Hai %1$s minutos</string> + <string name="bind_hours">Hai %1$s horas</string> + <string name="bind_days">Hai %1$s días</string> + <string name="console_copy_done">Copiados %1$d bytes ao portapapeis</string> + <string name="console_copy_start">Tocar e arrastrar\nou usar o cursor direccional\npara seleccionar a área a copiar</string> + <string name="console_menu_close">Pechar</string> + <string name="console_menu_copy">Copiar</string> + <string name="console_menu_paste">Pegar</string> + <string name="console_menu_portforwards">Redirección de portos</string> + <string name="console_menu_resize">Forzar tamaño</string> + <string name="console_menu_urlscan">Escanear URLs</string> + <string name="portforward_remote">Remoto</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> +</resources> diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml new file mode 100644 index 0000000..9e55f47 --- /dev/null +++ b/app/src/main/res/values-he/strings.xml @@ -0,0 +1,217 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">לקוח SSH פשוט, עצמתי ובקוד פתוח.</string> + <string name="service_desc">מתפעל חיבורי SSH ומפתחות ציבוריים שנטענים</string> + <string name="title_hosts_list">מארחים</string> + <string name="title_pubkey_list">מפתחות ציבוריים</string> + <string name="title_port_forwards_list">הפניית פתחות</string> + <string name="title_host_editor">עריכת מארח</string> + <string name="title_help">עזרה</string> + <string name="title_colors">צבעים</string> + <string name="title_color_picker">בחירת צבע</string> + <string name="resolve_connect">התחברות</string> + <string name="resolve_entropy">נאסף גיבוב</string> + <string name="menu_insert">הוספת מארח</string> + <string name="menu_delete">מחיקת מארח</string> + <string name="menu_preferences">העדפות</string> + <string name="help_intro">נא לבחור נושא להלן לקבל פרטים נוספים בנוגע לנושא מסוים.</string> + <string name="help_about">על אודות ConnectBot</string> + <string name="help_keyboard">מקלדת</string> + <string name="pubkey_generate">יצירה</string> + <string name="pubkey_import">יבוא</string> + <string name="pubkey_delete">מחיקת מפתח</string> + <string name="pubkey_gather_entropy">נאסף גיבוב</string> + <string name="pubkey_touch_prompt">יש לגעת בתיבה זו כדי לאסוף נתונים אקראיים: %1$d%% הושלמו</string> + <string name="pubkey_touch_hint">כדי להבטיח את האקראיות במהלך יצירת המפתח, עליך להעביר את האצבע באקראיות על התיבה שלהלן.</string> + <string name="pubkey_generating">צמד המפתחות נוצר...</string> + <string name="pubkey_copy_private">העתקת המפתח הפרטי</string> + <string name="pubkey_copy_public">העתקת המפתח הציבורי</string> + <string name="pubkey_list_empty">יש לגעת ב„תפריט“ כדי ליצור\nאו לייבא צמדי מפתחות.</string> + <string name="pubkey_unknown_format">מבנה בלתי ידוע</string> + <string name="pubkey_change_password">החלפת הססמה</string> + <string name="pubkey_list_pick">בחירה מ־/sdcard</string> + <string name="pubkey_import_parse_problem">אירעה תקלה בעת ניתוח המפתח הפרטי שייובא</string> + <string name="pubkey_unlock">שחרור המפתח</string> + <string name="pubkey_failed_add">הססמה עבור המפתח \'%1$s\' שגויה. האימות נכשל.</string> + <string name="pubkey_memory_load">טעינה לזיכרון</string> + <string name="pubkey_memory_unload">פריקה מהזיכרון</string> + <string name="pubkey_load_on_start">טעינת המפתח עם ההפעלה</string> + <string name="pubkey_confirm_use">לאמת לפני השימוש</string> + <string name="portforward_list_empty">יש לגעת ב„תפריט“ כדי ליצור\nהפניית פתחות.</string> + <string name="portforward_edit">עריכת הפניית הפתחות</string> + <string name="portforward_delete">מחיקת הפניית הפתחות</string> + <string name="prompt_nickname">כינוי:</string> + <string name="prompt_nickname_hint_pubkey">המפתח בעבודה שלי</string> + <string name="prompt_source_port">פתחת המקור:</string> + <string name="prompt_destination">יעד:</string> + <string name="prompt_old_password">ססמה ישנה:</string> + <string name="prompt_password">ססמה:</string> + <string name="prompt_again">(שוב)</string> + <string name="prompt_type">סוג:</string> + <string name="prompt_password_can_be_blank">הערה: הססמה יכולה להישאר ריקה</string> + <string name="prompt_bits">סיביות:</string> + <string name="prompt_pubkey_password">ססמה עבור המפתח \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">האם לאפשר למארח המרוחק\nלהשתמש במפתח \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">אזהרה: זיהוי המארח המרוחק השתנה!</string> + <string name="host_verification_failure_warning">יתכן שמישהו עושה טריק מלוכלך!\nיכול להיות שמישהו מנסה לצוטט לך ברגעים אלה (מתקפת סוכן אמצע)!\nיכול גם להיות שמפתח המארח הוחלף.</string> + <string name="prompt_host_disconnected">המארח התנתק.\nלסגור את החלון?</string> + <string name="prompt_continue_connecting">האם אכן\nלהמשיך בהליך החיבור?</string> + <string name="host_authenticity_warning">לא ניתן לאמת את אותנטיות המארח\'%1$s\'.</string> + <string name="host_fingerprint">טביעת האצבע של המפתח שלהמארח %1$s היא %2$s</string> + <string name="alert_passwords_do_not_match_msg">הססמאות אינן תואמות!</string> + <string name="alert_wrong_password_msg">ססמה שגויה!</string> + <string name="alert_key_corrupted_msg">המפתח הפרטי נראה פגום!</string> + <string name="alert_sdcard_absent">לא הוכנס כרטיס SD!</string> + <string name="button_add">הוספה</string> + <string name="button_change">החלפה</string> + <string name="button_generate">יצירת מפתח</string> + <string name="button_resize">שינוי גודל</string> + <string name="alert_disconnect_msg">החיבור אבד</string> + <string name="msg_copyright">כל הזכויות שמורות © 2007-2008 ל־Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">הדמיית מסוף</string> + <string name="pref_emulation_title">מצב הדמיה</string> + <string name="pref_emulation_summary">מצב הדמיית מסוף לשימוש בחיבורי PTY</string> + <string name="pref_scrollback_title">גודל גלילה אחורה</string> + <string name="pref_scrollback_summary">גודל מטמון הגלילה שיישאר בזיכרון בכל מסוף</string> + <string name="pref_ui_category">מנשק משתמש</string> + <string name="pref_rotation_title">מצב סיבוב</string> + <string name="pref_rotation_summary">כיצד לשנות את הסיבוב כאשר המקלדת עולה/יורדת</string> + <string name="pref_fullscreen_title">מסך מלא</string> + <string name="pref_fullscreen_summary">להסתיר את שורת המצב בעת השימוש במסוף</string> + <string name="pref_memkeys_title">לשמור את המפתחות בזיכרון</string> + <string name="pref_memkeys_summary">להשאיר את המפתחות לאחר השחרור בזיכרון עד ששירות המנגנון מושבת</string> + <string name="pref_conn_persist_title">חיבורים עיקשים</string> + <string name="pref_conn_persist_summary">לשמור על החיבורים פעילים ברקע</string> + <string name="pref_keymode_title">קיצורי ספריות</string> + <string name="pref_keymode_summary">ניתן לבחור כיצד להשתמש ב־Alt לטובת \'/\' וב־Shift לטובת Tab</string> + <string name="pref_camera_title">קיצור דרך מצלמה</string> + <string name="pref_camera_summary">ניתן לבחור איזה קיצור דרך יופעל בעת לחיצה על לחצן המצלמה</string> + <string name="pref_keepalive_title">להשאיר את המסך פעיל</string> + <string name="pref_keepalive_summary">למנוע את כיבוי המסך בעת העבודה עם המסוף</string> + <string name="pref_wifilock_title">להשאיר את הרשת האלחוטית פעילה</string> + <string name="pref_wifilock_summary">למנוע את כיבוי מתאם הרשת האלחוטית כאשר ישנו חלון פעיל</string> + <string name="pref_bumpyarrows_title">חצים עם רטט</string> + <string name="pref_bumpyarrows_summary">המכשיר ירטוט בעת שליחת מקשי חצים מכדור הגלילה; שימוש לחיבורים אטיים</string> + <string name="pref_bell_category">פעמון מסוף</string> + <string name="pref_bell_title">פעמון עם צליל</string> + <string name="pref_bell_volume_title">עצמת הפעמון</string> + <string name="pref_bell_vibrate_title">רטט עם הפעמון</string> + <string name="pref_bell_notification_title">התרעות ברקע</string> + <string name="pref_bell_notification_summary">שליחת התרעה כאשר מסוף שפעיל ברקע מצלצל בפעמון.</string> + <string name="list_keymode_right">שימוש במקשים מימין</string> + <string name="list_keymode_left">שימוש במקשים משמאל</string> + <string name="list_keymode_none">נטרול</string> + <string name="list_pubkeyids_none">לא להשתמש במפתחות</string> + <string name="list_pubkeyids_any">שימוש במפתח כלשהו שאינו נעול</string> + <string name="hostpref_nickname_title">כינוי</string> + <string name="hostpref_color_title">קטגוריית הצבע</string> + <string name="hostpref_fontsize_title">גודל הגופן (נק׳)</string> + <string name="hostpref_pubkeyid_title">שימוש באימות מפתח ציבורי</string> + <string name="hostpref_authagent_title">שימוש בסוכן אימות SSH</string> + <string name="hostpref_postlogin_title">אוטומציה שלאחר הכניסה</string> + <string name="hostpref_postlogin_summary">פקודות להפעלה בשרת המרוחק לאחר האימות</string> + <string name="hostpref_compression_title">דחיסה</string> + <string name="hostpref_compression_summary">אפשרות זו עשויה להועיל במקרה של רשתות אטיות</string> + <string name="hostpref_wantsession_title">הפעלת חלון מעטפת</string> + <string name="hostpref_wantsession_summary">ניתן לנטרל אפשרות זו כדי להשתמש בהפניית פתחות בלבד</string> + <string name="hostpref_stayconnected_title">להשאיר את החיבור פעיל</string> + <string name="hostpref_stayconnected_summary">לנסות להתחבר שוב למארח לאחר ניתוק</string> + <string name="hostpref_delkey_title">מקש DEL</string> + <string name="hostpref_delkey_summary">קוד המקש בעת לחיצה על מקש ה־DEL</string> + <string name="hostpref_encoding_title">קידוד</string> + <string name="hostpref_encoding_summary">קידוד תווים בצד המארח</string> + <string name="hostpref_connection_category">הגדרות חיבור</string> + <string name="hostpref_username_title">שם משתמש</string> + <string name="hostpref_hostname_title">מארח</string> + <string name="hostpref_port_title">פתחה</string> + <string name="bind_never">לא חובר מעולם</string> + <string name="bind_minutes">לפני %1$s דקות</string> + <string name="bind_hours">לפני %1$s שעות</string> + <string name="bind_days">לפני %1$s ימים</string> + <string name="console_copy_done">הועתקו %1$d בתים ללוח הגזירים</string> + <string name="console_copy_start">יש לגעת ולגרור\nאו להשתמש בלוח כיוונים\nכדי לבחור אזור להעתקה</string> + <string name="console_menu_close">סגירה</string> + <string name="console_menu_copy">העתקה</string> + <string name="console_menu_paste">הדבקה</string> + <string name="console_menu_portforwards">הפניית פתחות</string> + <string name="console_menu_resize">אילוץ גודל</string> + <string name="console_menu_urlscan">סריקת כתובות</string> + <string name="button_yes">כן</string> + <string name="button_no">לא</string> + <string name="portforward_local">מקומי</string> + <string name="portforward_remote">מרוחק</string> + <string name="portforward_dynamic">דינמי (SOCKS)</string> + <string name="portforward_pos">יצירת הפניית פתחות</string> + <string name="portforward_done">הפניית הפתחות נוצרה בהצלחה</string> + <string name="portforward_problem">אירעה שגיאה ביצירת הפניית פתחות, יתכן שניסית להשתמש בפתחות מתחת ל־1024 או שהפתחה כבר בשימוש?</string> + <string name="portforward_menu_add">הוספת הפניית פתחות</string> + <string name="hint_userhost">user\@hostname</string> + <string name="list_format_error">יש להשתמש בצורה „%1$s“</string> + <string name="format_username">username</string> + <string name="format_hostname">hostname</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">ניהול מפתחות ציבוריים</string> + <string name="list_menu_sortcolor">סידור לפי צבע</string> + <string name="list_menu_sortname">סידור לפי שם</string> + <string name="list_menu_settings">הגדרות</string> + <string name="list_host_disconnect">ניתוק</string> + <string name="list_host_edit">עריכת מארח</string> + <string name="list_host_portforwards">עריכת הפניית פתחות</string> + <string name="list_host_delete">מחיקת מארח</string> + <string name="list_host_empty">יש להשתמש בתיבת ההתחברות המהירה\nשלהלן כדי להתחבר למארח.</string> + <string name="list_rotation_default">בררת מחדל</string> + <string name="list_rotation_land">אילוץ מצב אופקי</string> + <string name="list_rotation_port">אילוץ מצב אנכי</string> + <string name="list_rotation_auto">אוטומטי</string> + <string name="list_camera_ctrlaspace">Ctrl+A ואז רווח</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">ללא</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Delete</string> + <string name="delete_message">האם אכן למחוק את \'%1$s\'?</string> + <string name="delete_pos">כן, למחוק</string> + <string name="delete_neg">ביטול</string> + <string name="wizard_agree">מוסכם</string> + <string name="wizard_next">הבא</string> + <string name="wizard_back">הקודם</string> + <string name="terminal_no_hosts_connected">אין מארחים מחוברים כרגע</string> + <string name="terminal_connecting">מתבצעת התחברות אל %1$s:%2$d דרך %3$s</string> + <string name="terminal_sucess">מפתח של המארח המאומת \'%1$s\': %2$s</string> + <string name="terminal_failed">אימות מפתח המארח נכשל.</string> + <string name="terminal_using_s2c_algorithm">אלגוריתם שרת ללקוח: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">אלגוריתם לקוח לשרת: %1$s %2$s</string> + <string name="terminal_using_algorithm">אלגוריתם בשימוש: %1$s %2$s</string> + <string name="terminal_auth">מתבצע ניסיון אימות</string> + <string name="terminal_auth_pass">מתבצע ניסיון לאימות \'ססמה\'</string> + <string name="terminal_auth_pass_fail">שיטת האימות \'ססמה\' נכשלה</string> + <string name="terminal_auth_pubkey_any">מתבצע ניסיון לאימות \'מפתח ציבורי\' עם מפתחות ציבוריים כלשהם מהזיכרון</string> + <string name="terminal_auth_pubkey_invalid">המפתח הציבורי שנבחר שגוי, כדאי לנסות לבחור מחדש מפתח בעורך המארחים</string> + <string name="terminal_auth_pubkey_specific">מתבצע ניסיון לאימות \'מפתח ציבורי\' עם מפתח ציבורי מסוים</string> + <string name="terminal_auth_pubkey_fail">שיטת האימות \'מפתח ציבורי\' עם המפתח \'%1$s\' נכשלה</string> + <string name="terminal_auth_ki">מתבצע ניסיון לאימות \'פעילות-מקלדת\'</string> + <string name="terminal_auth_ki_fail">שיטת האימות \'פעילות-מקלדת\' נכשלה</string> + <string name="terminal_auth_fail">[המארח שלך אינו תומך באימות \'ססמה\' או \'פעילות-מקלדת\'.]</string> + <string name="terminal_no_session">ההפעלה לא תתחיל עקב העדפות המארח.</string> + <string name="terminal_enable_portfoward">הפעלת הפניית פתחות: %1$s</string> + <string name="local_shell_unavailable">תקלה! המעטפת המקומית אינה זמינה במכשיר זה.</string> + <string name="notification_text">ל־%1$s נדרשת תשומת הלב שלך.</string> + <string name="no">לא</string> + <string name="with_confirmation">עם אימות</string> + <string name="yes">כן</string> + <string name="exceptions_submit_message">מתברר כי חלה תקלה ב־ConnectBot במהלך ההפעלה האחרונה. האם לשלוח את הדיווח למפתחים של ConnectBot?</string> + <string name="menu_colors_reset">איפוס</string> + <string name="app_is_running">ConnectBot פעיל</string> + <string name="color_red">אדום</string> + <string name="color_green">ירוק</string> + <string name="color_blue">כחול</string> + <string name="color_gray">אפור</string> + <string name="colors_fg_label">קדמה:%1$d</string> + <string name="color_bg_label">רקע:%1$d</string> + <string name="image_description_connected">יש חיבור.</string> + <string name="image_description_key_is_locked">המפתח נעול.</string> + <string name="image_description_toggle_control_character">החלפת מצב תווי בקרה.</string> + <string name="image_description_send_escape_character">שליחת תו סליקה.</string> + <string name="image_description_show_keyboard">הצגת מקלדת.</string> +</resources> diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml new file mode 100644 index 0000000..269a58b --- /dev/null +++ b/app/src/main/res/values-hr/strings.xml @@ -0,0 +1,92 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Jednostavan, moćan, open.source SSH klijent.</string> + <string name="service_desc">Pamti SSH konekcije i učitane javne ključeve</string> + <string name="title_hosts_list">Računala</string> + <string name="title_pubkey_list">Javni ključevi</string> + <string name="title_port_forwards_list">Port forwards</string> + <string name="title_host_editor">Uredi Računalo</string> + <string name="title_help">Pomoć</string> + <string name="title_colors">Boje</string> + <string name="resolve_connect">Spoji se</string> + <string name="resolve_entropy">Očitaj Entropiju</string> + <string name="menu_insert">Dodaj računalo</string> + <string name="menu_delete">Obriši računalo</string> + <string name="menu_preferences">Postavke</string> + <string name="help_intro">Molim Vas za više informacija odaberite temu ispod.</string> + <string name="help_about">O ConnectBot-u</string> + <string name="help_keyboard">Tipkovnica</string> + <string name="pubkey_generate">Stvori</string> + <string name="pubkey_import">Uvoz</string> + <string name="pubkey_delete">Obriši ključ</string> + <string name="pubkey_gather_entropy">Sakupljanje Entropije</string> + <string name="pubkey_touch_prompt">Dotakni ovaj prozor za prikupljanje slučajnosti: %1$d%% gotovo</string> + <string name="pubkey_touch_hint">Da bi osigurali slučajnost, pomičite prst u različitim smjerovima unutar prozora dolje.</string> + <string name="pubkey_generating">Izrada para ključeva</string> + <string name="pubkey_copy_private">Kopiraj privatni ključ</string> + <string name="pubkey_copy_public">Kopiraj javni ključ</string> + <string name="pubkey_list_empty">Dotakni \"Meni\" za izradu\nili uvoz parova ključeva</string> + <string name="pubkey_unknown_format">Nepoznati format</string> + <string name="pubkey_change_password">Promijeni zaporku</string> + <string name="pubkey_list_pick">Odaberi iz /sdcard</string> + <string name="pubkey_import_parse_problem">Problem prilikom tumačenja uvezenog privatnog ključa</string> + <string name="pubkey_unlock">Otključaj ključ</string> + <string name="pubkey_failed_add">Pogrešna zaporka za ključ \'%1$s\'. Neuspješna autentikacija.</string> + <string name="pubkey_memory_load">Učitaj u memoriju</string> + <string name="pubkey_memory_unload">Isprazni iz memorije</string> + <string name="pubkey_load_on_start">Učitaj kluč na početku</string> + <string name="pubkey_confirm_use">Potvrda prije upotrebe</string> + <string name="portforward_list_empty">Dotakni \"Meni\" za izradu\nport forward</string> + <string name="portforward_edit">Uredi port forward</string> + <string name="portforward_delete">Obriši port forward</string> + <string name="prompt_nickname">Nadimak:</string> + <string name="prompt_nickname_hint_pubkey">Moj poslovni ključ</string> + <string name="prompt_source_port">Source port:</string> + <string name="prompt_destination">Destination:</string> + <string name="prompt_old_password">Stara zaporka:</string> + <string name="prompt_password">Zaporka</string> + <string name="prompt_again">(ponovo)</string> + <string name="prompt_type">Type:</string> + <string name="prompt_password_can_be_blank">Uputa: zaporka može biti prazna</string> + <string name="prompt_bits">Bits:</string> + <string name="prompt_pubkey_password">Zaporka za ključ \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Dopusti udaljenom računalu da\nkoristi ključ \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">UPOZORENJE: IDENTIFIKACIJA UDALJENOG RAČUNALA SE PROMIJENILA!</string> + <string name="host_verification_failure_warning">MOGUĆE JE DA NETKO RADI NEŠTO JAKO OPASNO!\nNetko vas možda prisluškuje upravo sada (man-in-the-middle attack!)\nTakođer je moguće da se ključ računala upravo promijenio.</string> + <string name="prompt_host_disconnected">Računalo se odspojilo.\nZatvori sesiju?</string> + <string name="prompt_continue_connecting">Jeste li sigurni da želite\nnastaviti sa spajanjem?</string> + <string name="host_authenticity_warning">Autentičnost računala \'%1$s\' se ne može provjeriti.</string> + <string name="host_fingerprint">%1$s ključ računala je %2$s</string> + <string name="alert_passwords_do_not_match_msg">Zaporke nisu iste!</string> + <string name="alert_wrong_password_msg">Kriva zaporka!</string> + <string name="alert_key_corrupted_msg">Privatni ključ je oštećen!</string> + <string name="alert_sdcard_absent">SD kartica nije umetnuta!</string> + <string name="button_add">Dodaj</string> + <string name="button_change">Promijeni</string> + <string name="button_generate">Izradi ključ</string> + <string name="button_resize">Promijeni veličinu</string> + <string name="alert_disconnect_msg">Veza prekinuta</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulacija Terminala</string> + <string name="pref_emulation_title">Način emulacije</string> + <string name="pref_emulation_summary">Način emulacije terminala korišten za PTY veze</string> + <string name="pref_scrollback_title">Veličina ScrollBack-a</string> + <string name="pref_scrollback_summary">Veličina ScrollBack-a koja se čuva u memoriji za svaku konzolu</string> + <string name="pref_ui_category">Korisničko sučelje</string> + <string name="pref_rotation_title">Način rotacije</string> + <string name="pref_rotation_summary">Kako promijeniti rotaciju kada se tipkovnica pojavi/nestane</string> + <string name="pref_fullscreen_title">Cijeli zaslon</string> + <string name="pref_fullscreen_summary">Sakrij statusnu liniju u konzoli</string> + <string name="pref_memkeys_title">Zapamti ključeve u memoriji</string> + <string name="pref_memkeys_summary">Čuvaj otključane ključeve u memoriji dok se ne zatvori pozadinski servis</string> + <string name="pref_conn_persist_title">Održavaj konekcije</string> + <string name="pref_conn_persist_summary">Prisili konekcije da ostanu spojene u pozadini</string> + <string name="pref_keymode_title">Prečaci direktorija</string> + <string name="pref_keymode_summary">Izaberi kako koristiti Alt kao \'/\' i Shift kao Tab</string> + <string name="pref_camera_title">Prečac kamere</string> + <string name="pref_camera_summary">Izaberi koji prečac pokrenuti kada se stisne dugme kamere</string> + <string name="pref_keepalive_title">Održi zaslon budnim</string> + <string name="pref_keepalive_summary">Spriječi gašenje zaslona dok se koristi konzola</string> + <string name="pref_wifilock_title">Održi Wi-Fi aktivnim</string> + <string name="pref_wifilock_summary">Spriječi gašenje Wi-Fi-a dok je aktivna sesija</string> +</resources> diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000..5a41a02 --- /dev/null +++ b/app/src/main/res/values-hu/strings.xml @@ -0,0 +1,214 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Egyszerű, hatékony, nyílt-forráskódú SSH kliens.</string> + <string name="service_desc">Fenntartja az SSH kapcsolatokat és a betöltött nyilvános kulcsokat</string> + <string name="title_hosts_list">Kiszolgálók</string> + <string name="title_pubkey_list">Publikus kulcsok</string> + <string name="title_port_forwards_list">Port átiránytások</string> + <string name="title_host_editor">Kiszolgáló szerkesztése</string> + <string name="title_help">Súgó</string> + <string name="title_colors">Színek</string> + <string name="resolve_connect">Csatlakozás</string> + <string name="resolve_entropy">Entrópia gyűjtés</string> + <string name="menu_insert">Kiszolgáló hozzáadása</string> + <string name="menu_delete">Kiszolgáló törlése</string> + <string name="menu_preferences">Beállítások</string> + <string name="help_intro">Válaszon témakört az alábbiak közül egy adott téma részletes leírásához.</string> + <string name="help_about">A ConnectBot névjegye</string> + <string name="help_keyboard">Billentyűzet</string> + <string name="pubkey_generate">Generálás</string> + <string name="pubkey_import">Importálás</string> + <string name="pubkey_delete">Kulcs törlése</string> + <string name="pubkey_gather_entropy">Entrópia gyűjtés</string> + <string name="pubkey_touch_prompt">Érintse meg a dobozt a véletlenszerű generálásához: %1$d%% kész</string> + <string name="pubkey_touch_hint">A kulcs egyediségének biztosítása érdekében mozgassa ujját véletlenszerűen az alábbi dobozban.</string> + <string name="pubkey_generating">Kulcspár generálása...</string> + <string name="pubkey_copy_private">Privát kulcs másolása</string> + <string name="pubkey_copy_public">Nyilvános kulcs másolása</string> + <string name="pubkey_list_empty">Nyomjon Menu-t, kulcspár\n létrehozáshoz/importáláshoz.</string> + <string name="pubkey_unknown_format">Ismeretlen formátum</string> + <string name="pubkey_change_password">Jelszócsere</string> + <string name="pubkey_list_pick">Kiválaszt /sdcard -ról</string> + <string name="pubkey_import_parse_problem">Probléma a privát kulcspár feldolgozása során</string> + <string name="pubkey_unlock">Kulcs feloldása</string> + <string name="pubkey_failed_add">Hibás jelszó a \'%1$s\' kulcshoz. Sikertelen azonosítás.</string> + <string name="pubkey_memory_load">Betöltés a memóriába</string> + <string name="pubkey_memory_unload">Mermória ürítése</string> + <string name="pubkey_load_on_start">Kulcs betöltése induláskor</string> + <string name="pubkey_confirm_use">Jóváhagyás használat előtt</string> + <string name="portforward_list_empty">Nyomj Menu-t a port\nátirányításhoz.</string> + <string name="portforward_edit">Port átirányítás módosítása</string> + <string name="portforward_delete">Port átirányítás törlése</string> + <string name="prompt_nickname">Becenév:</string> + <string name="prompt_nickname_hint_pubkey">A munkahelyi kulcsom</string> + <string name="prompt_source_port">Helyi port:</string> + <string name="prompt_destination">Cél:</string> + <string name="prompt_old_password">Régi jelszó:</string> + <string name="prompt_password">Jelszó:</string> + <string name="prompt_again">(újra)</string> + <string name="prompt_type">Típus:</string> + <string name="prompt_password_can_be_blank">Tipp: a jelszóadás nem kötelező</string> + <string name="prompt_bits">Bitek:</string> + <string name="prompt_pubkey_password">A \'%1$s\' kulcs jelszava</string> + <string name="prompt_allow_agent_to_use_key">A \'%1$s\' kulcs engedélyezése\na távoli gépnek?</string> + <string name="host_verification_failure_warning_header">FIGYELEM: A TÁVOLI GÉP AZONOSÍTÓJA MEGVÁLTOZOTT!</string> + <string name="host_verification_failure_warning">FELTEHETŐEN VALAKI GYANÚS DOLGOKAT MŰVEL! \nValaki feltehetően megpróbál lehallgatni (man-in-the-middle attack) téged! \\ nDe az is lehetséges, hogy a távoli fél kulcsa közben megváltozott.</string> + <string name="prompt_host_disconnected">A kapcsolat megszakadt.\nBezárja a folyamatot?</string> + <string name="prompt_continue_connecting">Biztosan folytatja a csatlakozást?</string> + <string name="host_authenticity_warning">A \'%1$s\' kiszolgáló azonosítása sikertelen.</string> + <string name="host_fingerprint">A %1$s kulcs újlenyomata a(-z) %2$s</string> + <string name="alert_passwords_do_not_match_msg">Nem egyezik a jelszó!</string> + <string name="alert_wrong_password_msg">Hibás jelszó!</string> + <string name="alert_key_corrupted_msg">Sérült privát kulcs!</string> + <string name="alert_sdcard_absent">Nincs SD kártya!</string> + <string name="button_add">Hozzáadás</string> + <string name="button_change">Módosítás</string> + <string name="button_generate">Kulcs generálása</string> + <string name="button_resize">Átméretezés</string> + <string name="alert_disconnect_msg">A kapcsolat megszakadt</string> + <string name="msg_copyright">Minden jog fentartva © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminál emulálás</string> + <string name="pref_emulation_title">Emulálási mód</string> + <string name="pref_emulation_summary">Terminál emulálási mód PTY kapcsolatokhoz</string> + <string name="pref_scrollback_title">Előzmények mérete</string> + <string name="pref_scrollback_summary">Az előzmények memóriában tartandó mérete konzolonként</string> + <string name="pref_ui_category">Felhasználói felület</string> + <string name="pref_rotation_title">Forgatási mód</string> + <string name="pref_rotation_summary">Hogyan forduljon a kijelző a billentyűzet ki/becsukása során</string> + <string name="pref_fullscreen_title">Teljes képernyő</string> + <string name="pref_fullscreen_summary">Rejtett állapotsor a konzol használata során</string> + <string name="pref_memkeys_title">Kulcsok megörzése a memóriában</string> + <string name="pref_memkeys_summary">Tartsa nyitva a memóriában lévő kulcsokat amíg a háttérszolgáltatás meg nem szűnik</string> + <string name="pref_conn_persist_title">Kapcsolatok fenntartása</string> + <string name="pref_conn_persist_summary">A háttérben tartsa fenn a kapcsolatokat</string> + <string name="pref_keymode_title">Könyvtár hivatkozások</string> + <string name="pref_keymode_summary">Válassza ki, hogyan kell használni az Alt-ot\n a \'/\' helyett és a Shift-et a Tab helyett</string> + <string name="pref_camera_title">Kamera gomb hivatkozás</string> + <string name="pref_camera_summary">Válassza ki a kamera gombhoz tartozó hivatkozást</string> + <string name="pref_keepalive_title">Tartsa ébren a képernyőt</string> + <string name="pref_keepalive_summary">Akadályozza meg a képernyővédő bekapcsolását konzolon végzett munka alatt</string> + <string name="pref_wifilock_title">Tartsa fenn a Wi-Fi kapcsolatot</string> + <string name="pref_wifilock_summary">Akadályozza meg a Wi-Fi kikapcsolását amíg a munka zajlik</string> + <string name="pref_bumpyarrows_title">Ugráló nyilak</string> + <string name="pref_bumpyarrows_summary">Vibráljon amikor a hanyattegérrel mozgatjuk a kurzort; hasznos lehet lassú kapcsolatoknál</string> + <string name="pref_bell_category">Terminál csengő</string> + <string name="pref_bell_title">Hallható csengő</string> + <string name="pref_bell_volume_title">Csengő hangereje</string> + <string name="pref_bell_vibrate_title">Vibráljon csengetéskor</string> + <string name="pref_bell_notification_title">Háttér figyelmeztetések</string> + <string name="pref_bell_notification_summary">Küldjön értesítőt amikor a háttérben futó terminál csenget.</string> + <string name="list_keymode_right">A jobb oldali gombok használata</string> + <string name="list_keymode_left">A baloldali gombok használata</string> + <string name="list_keymode_none">Tiltás</string> + <string name="list_pubkeyids_none">Kulcsok használatának tiltása</string> + <string name="list_pubkeyids_any">Bármely nyilvános kulcs használata</string> + <string name="hostpref_nickname_title">Becenév</string> + <string name="hostpref_color_title">Szín-kategória</string> + <string name="hostpref_fontsize_title">Betűtipus mérete (pt)</string> + <string name="hostpref_pubkeyid_title">Nyilvános kulcs használata azonosításhoz</string> + <string name="hostpref_authagent_title">Az SSH auth agent használata</string> + <string name="hostpref_postlogin_title">Bejelentkezést követő automatikus parancsok</string> + <string name="hostpref_postlogin_summary">Olyan parancsok amelyek a bejelentkezést követően kerülnek futtatásra</string> + <string name="hostpref_compression_title">Tömörítés</string> + <string name="hostpref_compression_summary">Jól jöhet lassú házatok esetén</string> + <string name="hostpref_wantsession_title">Parancssor indítása</string> + <string name="hostpref_wantsession_summary">Kapcsolja ki, ha csak port átirányitásokat akar</string> + <string name="hostpref_stayconnected_title">Maradjon csatlakozva</string> + <string name="hostpref_stayconnected_summary">Újracsatlakozás ha a kapcsolat megszakad</string> + <string name="hostpref_delkey_title">DEL billentyű</string> + <string name="hostpref_delkey_summary">Az a billenytű kód amit a DEL lenyomásakor küldünk</string> + <string name="hostpref_encoding_title">Karakterkódolás</string> + <string name="hostpref_encoding_summary">A kiszolgáló karakterkódolása</string> + <string name="hostpref_connection_category">Kapcsolat beállításai</string> + <string name="hostpref_username_title">Felhasználónév</string> + <string name="hostpref_hostname_title">Kiszolgáló</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Még nem csatlakozott</string> + <string name="bind_minutes">%1$s perce</string> + <string name="bind_hours">%1$s órája</string> + <string name="bind_days">%1$s napja</string> + <string name="console_copy_done">%1$d bájt a vágólapra másolva</string> + <string name="console_copy_start">Érintéssel és húzással\nvagy a hanyattegér használatával\njelölje ki a másolandó területet.</string> + <string name="console_menu_close">Bezárás</string> + <string name="console_menu_copy">Másolás</string> + <string name="console_menu_paste">Beillesztés</string> + <string name="console_menu_portforwards">Port átirányítások</string> + <string name="console_menu_resize">Méret kényszerítése</string> + <string name="console_menu_urlscan">URL Keresés</string> + <string name="button_yes">Igen</string> + <string name="button_no">Nem</string> + <string name="portforward_local">Helyi</string> + <string name="portforward_remote">Távoli</string> + <string name="portforward_dynamic">Dinamikus (SOCKS)</string> + <string name="portforward_pos">Port átirányítás létrehozása</string> + <string name="portforward_done">Sikeres port átirányítás</string> + <string name="portforward_problem">Probléma a port átirányítás létrehozása során, 1024 alatti vagy már használatban lévő portot választott?</string> + <string name="portforward_menu_add">Port átirányítás hozzáadása</string> + <string name="hint_userhost">felhasználó\@gépnév</string> + <string name="list_format_error">A %1$s formátum használata</string> + <string name="format_username">felhasználónév</string> + <string name="format_hostname">gépnév</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Publikus kulcsok kezelése</string> + <string name="list_menu_sortcolor">Rendezés szín szerint</string> + <string name="list_menu_sortname">Rendezés név szerint</string> + <string name="list_menu_settings">Beállítások</string> + <string name="list_host_disconnect">A kapcsolat bontása</string> + <string name="list_host_edit">Kiszolgáló szerkesztése</string> + <string name="list_host_portforwards">Port átirányítások szerkesztése</string> + <string name="list_host_delete">Kiszolgáló törlése</string> + <string name="list_host_empty">Használja a gyors csatlakozás dobozt\n a csatlakozáshoz.</string> + <string name="list_rotation_default">Alapbeállitás</string> + <string name="list_rotation_land">Fekvő mód kényszerítése</string> + <string name="list_rotation_port">Álló mód kényszerítése</string> + <string name="list_rotation_auto">Automatikus</string> + <string name="list_camera_ctrlaspace">Ctrl+A majd Space</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Semmit</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Delete</string> + <string name="delete_message">Biztosan törli a \'%1$s\'-t?</string> + <string name="delete_pos">Igen, törlés</string> + <string name="delete_neg">Mégsem</string> + <string name="wizard_agree">Elfogad</string> + <string name="wizard_next">Következő</string> + <string name="wizard_back">Vissza</string> + <string name="terminal_no_hosts_connected">Nincs csatlakozva</string> + <string name="terminal_connecting">Csatlakozás %1$s:%2$d a %3$s protokollon</string> + <string name="terminal_sucess">Azonosított \'%1$s\' gépnév kulcsa: %2$s</string> + <string name="terminal_failed">Távoli gép, kulcs azonosítása sikertelen.</string> + <string name="terminal_using_s2c_algorithm">Szerver-kliens algoritmus: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Kliens-szerver algoritmus: %1$s %2$s</string> + <string name="terminal_using_algorithm">Használt algoritmus: %1$s %2$s</string> + <string name="terminal_auth">Azonosítási probálkozás</string> + <string name="terminal_auth_pass">Próbálkozás \'password\' azonosítással</string> + <string name="terminal_auth_pass_fail">A \'password\' azonosítás sikertelen.</string> + <string name="terminal_auth_pubkey_any">Próbálkozás \'publickey\' azonosítással, bármely memóriában található nyilvános kulccsal</string> + <string name="terminal_auth_pubkey_invalid">A nyilvános kulcs hibás, próbálja újra kiválasztani az állomás kezelőből</string> + <string name="terminal_auth_pubkey_specific">Próbálkozás \'publickey\' azonosítással, választott nyilvános kulccsal</string> + <string name="terminal_auth_pubkey_fail">A \'%1$s\' kulccsal történt \'publickey\' azonosítás sikertelen.</string> + <string name="terminal_auth_ki">Próbálkozás \'keyboard-interactive\' azonosítással</string> + <string name="terminal_auth_ki_fail">\'keyboard-interactive\' azonosítás sikertelen</string> + <string name="terminal_auth_fail">[A gépnév nem támogatja a \'password\' vagy \'keyboard-interactive\' azonosítást.]</string> + <string name="terminal_no_session">A munkamenet nem kezdhető el a gép beállítások miatt.</string> + <string name="terminal_enable_portfoward">Port átirányítás engedélyezése: %1$s</string> + <string name="local_shell_unavailable">Hiba! Nincs lokális shell ezen a telefonon.</string> + <string name="notification_text">%1$s a figyelmére szorul.</string> + <string name="no">Nem</string> + <string name="with_confirmation">Csak beleegyezéssel</string> + <string name="yes">Igen</string> + <string name="exceptions_submit_message">Úgy tűnik a ConnectBot a legutóbbi futtatáskor összeomlott. Elküldi a hibanaplót a ConnectBot fejleszőinek?</string> + <string name="menu_colors_reset">Visszaállít</string> + <string name="app_is_running">ConnectBot már fut</string> + <string name="color_red">piros</string> + <string name="color_green">zöld</string> + <string name="color_blue">kék</string> + <string name="color_gray">szürke</string> + <string name="image_description_connected">Kapcsolódva</string> + <string name="image_description_key_is_locked">A Kulcs zárolva.</string> + <string name="image_description_toggle_control_character">Vezérlő jel i/n</string> + <string name="image_description_send_escape_character">Kilépő jel küldés</string> + <string name="image_description_show_keyboard">Billentyűzet megmutatása</string> +</resources> diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml new file mode 100644 index 0000000..4e98a79 --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,215 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Sederhana, tangguh, klien SSH dengan kode terbuka.</string> + <string name="service_desc">"Mengelola koneksi SSH dan kunci publik yang terrekam"</string> + <string name="title_hosts_list">Host</string> + <string name="title_pubkey_list">Kunci Publik</string> + <string name="title_host_editor">Ubah Host</string> + <string name="title_help">Bantuan</string> + <string name="title_colors">Warna</string> + <string name="title_color_picker">Pilih warna</string> + <string name="resolve_connect">Hubungi</string> + <string name="resolve_entropy">Kumpulkan entropy</string> + <string name="menu_insert">Tambahkan host</string> + <string name="menu_delete">Hapus Host</string> + <string name="menu_preferences">Kesukaan</string> + <string name="help_intro">Silahkan pilih sebuah topik dibawah untuk informasi lebih lanjut di suatu subjek tertentu.</string> + <string name="help_about">Mengenai ConnectBot</string> + <string name="help_keyboard">Papan Ketik</string> + <string name="pubkey_generate">Buat</string> + <string name="pubkey_import">Impor</string> + <string name="pubkey_delete">Tombol Hapus</string> + <string name="pubkey_gather_entropy">Kumpulkan Entropy</string> + <string name="pubkey_touch_prompt">Sentuh boks ini untuk mengumpulkan ke tidak tentuan: %1$d%% selesai</string> + <string name="pubkey_touch_hint">Untuk memastikan ketidaktentuan selama pembuatan kunci, gerakkan tangan anda secara acak diatas kotak dibawah ini.</string> + <string name="pubkey_generating">Membuat pasangan kunci...</string> + <string name="pubkey_copy_private">Salin kunci pribadi</string> + <string name="pubkey_copy_public">Salin kunci publik</string> + <string name="pubkey_list_empty">Sentuh Menu untuk membuat/atau mengimpor pasangan kunci.</string> + <string name="pubkey_unknown_format">Format tidak diketahui</string> + <string name="pubkey_change_password">Ubah kata sandi</string> + <string name="pubkey_list_pick">Ambil dari /sdcard</string> + <string name="pubkey_import_parse_problem">Masalah dalam mengambil kunci pribadi yang terimpor</string> + <string name="pubkey_unlock">Buka kunci</string> + <string name="pubkey_failed_add">Kata sandi salah untuk kunci \'%1$s\'. Authentifikasi gagal.</string> + <string name="pubkey_memory_load">Muat ke memori</string> + <string name="pubkey_memory_unload">Hapus dari memori</string> + <string name="pubkey_load_on_start">Muat kunci pada saat memulai</string> + <string name="pubkey_confirm_use">Konfirmasi sebelum digunakan</string> + <string name="portforward_list_empty">Sentu Menu untuk membuat\nport forwards.</string> + <string name="portforward_edit">Ubah port forward</string> + <string name="portforward_delete">Hapus port forward</string> + <string name="prompt_nickname">Nama panggilan:</string> + <string name="prompt_nickname_hint_pubkey">Kunci kerja saya</string> + <string name="prompt_source_port">Port asal</string> + <string name="prompt_destination">Tujuan:</string> + <string name="prompt_old_password">Kata sandi sebelumnya:</string> + <string name="prompt_password">Kata sandi:</string> + <string name="prompt_again">(sekali lagi)</string> + <string name="prompt_type">Tipe:</string> + <string name="prompt_password_can_be_blank">Catatan: sandi dapat dikosongkan</string> + <string name="prompt_bits">Jumlah bit:</string> + <string name="prompt_pubkey_password">Kata sandi untuk kunci \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Ijinkan remote host untuk\nmenggunakan kunci \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">PERINGATAN: IDENTIFIKASI REMOTE HOST TELAH BERUBAH!</string> + <string name="host_verification_failure_warning">KEMUNGKINAN SESEORANG SEDANG BERBUAT KOTOR!\nSeseorang mungkin tengah menyadap anda saat ini (serangan man-in-the-middle)!\nMungkin juga kunci host baru saja diubah.</string> + <string name="prompt_host_disconnected">Sambungan dengan host telah terputus.\nTutup sesi ini?</string> + <string name="prompt_continue_connecting">Apakah Anda yakin ingin\nterus mencoba menyambungkan?</string> + <string name="host_authenticity_warning">Authentifikasi dari host \'%1$s\' tidak dapat dibuat.</string> + <string name="host_fingerprint">Host %1$s tanda tangan kunci adalah %2$s</string> + <string name="alert_passwords_do_not_match_msg">Kata sandi tidak cocok!</string> + <string name="alert_wrong_password_msg">Kata sandi salah!</string> + <string name="alert_key_corrupted_msg">Kunci pribadi nampaknya telah rusak!</string> + <string name="alert_sdcard_absent">SD card belum dimasukan!</string> + <string name="button_add">Tambahkan</string> + <string name="button_change">Ubah</string> + <string name="button_generate">Buat Kunci</string> + <string name="button_resize">Ubah ukuran</string> + <string name="alert_disconnect_msg">Koneksi terputus</string> + <string name="msg_copyright">Hak Cipta © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminal Emulasi</string> + <string name="pref_emulation_title">Mode Emulasi</string> + <string name="pref_emulation_summary">Mode terminal emulasi digunakan untuk koneksi PTY</string> + <string name="pref_scrollback_title">Ukuran Scrollback</string> + <string name="pref_scrollback_summary">Ukuran penyangga scrollback yang harus tersimpan di memori pada setiap konsol</string> + <string name="pref_ui_category">Antarmuka pengguna</string> + <string name="pref_rotation_title">Mode rotasi</string> + <string name="pref_rotation_summary">Bagaimana mengubah rotasi ketika papan ketik menyembul masuk/keluar</string> + <string name="pref_fullscreen_title">Layar penuh</string> + <string name="pref_fullscreen_summary">Sembunyikan bar status ketika dalam konsol</string> + <string name="pref_keyboard_category">Papan tik</string> + <string name="pref_memkeys_title">Ingat kunci dalam memori</string> + <string name="pref_memkeys_summary">Simpan kunci yang terbuka kedalam memori hingga layanan backend berakhir</string> + <string name="pref_conn_persist_title">Pertahankan koneksi</string> + <string name="pref_conn_persist_summary">Paksa koneksi untuk tetap terhubung ketika bekerja di belakang layar</string> + <string name="pref_keymode_title">Jalan pintas direktori</string> + <string name="pref_keymode_summary">Pilih bagaimana menggunakan Alt untuk \'/\' dan Shift untuk Tab</string> + <string name="pref_camera_title">Jalan pintas kamera</string> + <string name="pref_camera_summary">Pilih jalan pintas yang akan diaktifkan saat tombol kamera ditekan</string> + <string name="pref_keepalive_title">Jaga layar tetap terbuka</string> + <string name="pref_keepalive_summary">Mencegah layar menjadi padam ketika bekerja dalam sebuah konsol</string> + <string name="pref_wifilock_title">Wi-Fi selalu aktif</string> + <string name="pref_wifilock_summary">Jaga Wi-Fi agar tidak padam ketika sesi aktif</string> + <string name="pref_bumpyarrows_title">Panah belok</string> + <string name="pref_bumpyarrows_summary">Getarkan saat mengirim tombol panah dari trackball, berguna untuk koneksi lambat</string> + <string name="pref_bell_category">Bel terminal</string> + <string name="pref_bell_title">Bel berdering</string> + <string name="pref_bell_volume_title">Volume dering</string> + <string name="pref_bell_vibrate_title">Getarkan saat berdering</string> + <string name="pref_bell_notification_title">Pemberitahuan di belakang layar</string> + <string name="pref_bell_notification_summary">Kirim pemberitahuan ketika sebuah terminal berjalan di belakang dengan membunyikan sebuah nada dering</string> + <string name="list_keymode_right">Gunakan tombol sisi kanan</string> + <string name="list_keymode_left">Gunakan tombol sisi-kiri</string> + <string name="list_keymode_none">Tidak aktif</string> + <string name="list_pubkeyids_none">Jangan gunakan kunci</string> + <string name="list_pubkeyids_any">Gunakan kunci apapun yang tidak terkunci</string> + <string name="hostpref_nickname_title">Nama panggilan</string> + <string name="hostpref_color_title">Kategori warna</string> + <string name="hostpref_fontsize_title">Ukuran huruf (pt)</string> + <string name="hostpref_pubkeyid_title">Gunakan otentifikasi pubkey</string> + <string name="hostpref_authagent_title">Gunakan SSH auth agent</string> + <string name="hostpref_postlogin_title">otomasi setelah login</string> + <string name="hostpref_postlogin_summary">Perintah yang dijalankan diremote server sekali terotentifikasi</string> + <string name="hostpref_compression_title">Kompresi</string> + <string name="hostpref_compression_summary">Ini mungkin akan membantu dengan jaringan lambat</string> + <string name="hostpref_wantsession_title">Jalankan sesi shell</string> + <string name="hostpref_wantsession_summary">Non-aktifkan kesukaan ini hanya untuk port forwards</string> + <string name="hostpref_stayconnected_summary">Coba menyambungkan kembali ke host jika terputus</string> + <string name="hostpref_delkey_title">Tombol DEL</string> + <string name="hostpref_delkey_summary">Kode kunci terkirim ketika tombol DEL ditekan</string> + <string name="hostpref_encoding_title">Pengkodean</string> + <string name="hostpref_encoding_summary">Karakter pengkodean untuk host</string> + <string name="hostpref_connection_category">Konfigurasi koneksi</string> + <string name="hostpref_username_title">Nama pengguna</string> + <string name="hostpref_hostname_title">Host</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Tidak pernah terhubung</string> + <string name="bind_minutes">%1$s menit yang lalu</string> + <string name="bind_hours">%1$s jam yang lalu</string> + <string name="bind_days">%1$s hari yang lalu</string> + <string name="console_copy_done">Tersalin %1$d byte ke papan-klip</string> + <string name="console_copy_start">Sentuh dan tarik\natau gunakan tombol panah\nuntuk memilih area yang akan disalin</string> + <string name="console_menu_close">Tutup</string> + <string name="console_menu_copy">Salin</string> + <string name="console_menu_paste">Tempel</string> + <string name="console_menu_portforwards">Penerusan port</string> + <string name="console_menu_resize">Paksakan ukuran</string> + <string name="console_menu_urlscan">Pindai URL</string> + <string name="button_yes">Ya</string> + <string name="button_no">Tidak</string> + <string name="portforward_local">Lokal</string> + <string name="portforward_remote">Remote</string> + <string name="portforward_dynamic">Dinamis (SOCKS)</string> + <string name="portforward_pos">Buat penerusan port</string> + <string name="portforward_done">Berhasil membuat penerusan port</string> + <string name="portforward_problem">Masalah dalam membuat penerusan port, mungkin Anda menggunakan nomor port di bawah 1024 atau nomor port telah digunakan?</string> + <string name="portforward_menu_add">Tambah penerusan port</string> + <string name="hint_userhost">pengguna\@namahost</string> + <string name="list_format_error">Gunakan bentuk \"%1$s\"</string> + <string name="format_username">pengguna</string> + <string name="format_hostname">namahost</string> + <string name="format_port">Port</string> + <string name="list_menu_pubkeys">Atur Pubkey</string> + <string name="list_menu_sortcolor">Urutkan berdasarkan warna</string> + <string name="list_menu_sortname">Urutkan berdasarkan nama</string> + <string name="list_menu_settings">Pengaturan</string> + <string name="list_host_disconnect">Putus koneksi</string> + <string name="list_host_edit">Ubah host</string> + <string name="list_host_portforwards">Ubah penerusan port</string> + <string name="list_host_delete">Hapus host</string> + <string name="list_host_empty">Gunakan kotak cepat-sambung\ndi bawah untuk tersambung dengan sebuah host</string> + <string name="list_rotation_default">Baku</string> + <string name="list_rotation_land">Paksakan bentuk datar</string> + <string name="list_rotation_port">Paksakan bentuk tegak</string> + <string name="list_rotation_auto">Otomatis</string> + <string name="list_camera_ctrlaspace">Ctrl+A kemudian spasi</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_none">Kosong</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Hapus</string> + <string name="delete_message">Apakah anda yakin ingin menghapus %1$s?</string> + <string name="delete_pos">Ya, hapus</string> + <string name="delete_neg">Batal</string> + <string name="wizard_agree">Setuju</string> + <string name="wizard_next">Selanjutnya</string> + <string name="wizard_back">Sebelumnya</string> + <string name="terminal_no_hosts_connected">Tidak ada host yang saat ini terhubung</string> + <string name="terminal_connecting">Menyambungkan ke %1$s:%2$d melalui %3$s</string> + <string name="terminal_sucess">Host terverifikasi \'%1$s\' kunci: %2$s</string> + <string name="terminal_failed">Verifikasi kunci host gagal.</string> + <string name="terminal_using_s2c_algorithm">Algoritma server-ke-klien: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algoritma klien-ke-server: %1$s %2$s</string> + <string name="terminal_using_algorithm">Menggunakan algoritma : %1$s %2$s</string> + <string name="terminal_auth">Mencoba untuk mengotentifikasi</string> + <string name="terminal_auth_pass">Mencoba otentifikasi \'kata sandi\'</string> + <string name="terminal_auth_pass_fail">Metoda otentifikasi \'kata sandi\' gagal</string> + <string name="terminal_auth_pubkey_any">Mencoba otentifikasi \'kunci publik\' dengan kunci publik apapun dalam memori</string> + <string name="terminal_auth_pubkey_invalid">Kunci publik yang dipilih tidak valid, coba pilih kembali kunci dalam editor host</string> + <string name="terminal_auth_pubkey_specific">Mencoba otentifikasi \'kunci publik\' dengan kunci publik spesifik</string> + <string name="terminal_auth_pubkey_fail">Metode otentifikasi \'kunci publik\' dengan kunci \'%1$s\' gagal</string> + <string name="terminal_auth_ki">Mencoba otentifikasi \'papan ketik interaktif\'</string> + <string name="terminal_auth_ki_fail">Metode otentifikasi \'papan ketik interaktif\' gagal</string> + <string name="terminal_auth_fail">[Host anda tidak mendukung otentifikasi \'kata sandi\' atau \'papan ketik interaktif\'.]</string> + <string name="terminal_no_session">Sesi tidak akan dimulai karena preferensi host</string> + <string name="terminal_enable_portfoward">Aktifkan port forward: %1$s</string> + <string name="local_shell_unavailable">Gagal! Shell lokal tidak tersedia di telepon ini.</string> + <string name="notification_text">%1$s memerlukan perhatian anda.</string> + <string name="no">Tidak</string> + <string name="with_confirmation">Dengan konfirmasi</string> + <string name="yes">Ya</string> + <string name="exceptions_submit_message">Sepertinya ConnectBot mengalami masalah saat berjalan sebelumnya. Kirim laporan galat ke para pengembang ConnectBot?</string> + <string name="menu_colors_reset">Atur ulang</string> + <string name="app_is_running">ConnectBot sedang berjalan</string> + <string name="color_red">merah</string> + <string name="color_green">hijau</string> + <string name="color_blue">biru</string> + <string name="color_gray">abu-abu</string> + <string name="colors_fg_label">WD: %1$d</string> + <string name="color_bg_label">WB: %1$d</string> + <string name="image_description_connected">Tersambung.</string> + <string name="image_description_key_is_locked">Pubkey terkunci.</string> + <string name="image_description_toggle_control_character">Ubah-ubah karakter control.</string> + <string name="image_description_send_escape_character">Kirim karakter escape.</string> + <string name="image_description_show_keyboard">Perlihatkan papan ketik.</string> +</resources> diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml new file mode 100644 index 0000000..a5fc71f --- /dev/null +++ b/app/src/main/res/values-is/strings.xml @@ -0,0 +1,176 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="service_desc">Heldur utan um SSH tengingar og geymda \"pubkeys\"</string> + <string name="title_hosts_list">Þjónar</string> + <string name="title_pubkey_list">Almennir lyklar</string> + <string name="title_port_forwards_list">Áframsending ports</string> + <string name="title_host_editor">Breyta þjónum</string> + <string name="title_help">Hjálp</string> + <string name="title_colors">Litir</string> + <string name="resolve_connect">Tengjast</string> + <string name="resolve_entropy">Safna bitum af handahófi</string> + <string name="menu_insert">Bæta við þjóni</string> + <string name="menu_delete">Eyða þjóni</string> + <string name="menu_preferences">Valmöguleikar</string> + <string name="help_intro">Vinsamlegast veldu atriði hér að neðan til þess að fá nánari upplýsingar um það.</string> + <string name="help_about">Um ConnectBot</string> + <string name="help_keyboard">Lyklaborð</string> + <string name="pubkey_generate">Búa til</string> + <string name="pubkey_import">Flytja inn</string> + <string name="pubkey_delete">Eyða lykli</string> + <string name="pubkey_gather_entropy">Safna bitum af handahófi</string> + <string name="pubkey_touch_prompt">Snertu kassann til þess að safna bitum af handahófi, %1$d%% búið.</string> + <string name="pubkey_touch_hint">Til þess að tryggja handahófs kenndan lykil, færðu fingurinn á þér handahófskennt yfir kassann að neðan.</string> + <string name="pubkey_generating">Framkalla lykla par...</string> + <string name="pubkey_copy_private">Afrita einka lykil</string> + <string name="pubkey_copy_public">Afrita almennan lykil</string> + <string name="pubkey_list_empty">Ýttu á \"Menu\" til þess að búa til\neða fluttu inn lykla par.</string> + <string name="pubkey_unknown_format">Óþekkt snið</string> + <string name="pubkey_change_password">Breyta lykilorði</string> + <string name="pubkey_list_pick">Velja af /sdcard</string> + <string name="pubkey_import_parse_problem">Ekki tókst að lesa einkalykilinn</string> + <string name="pubkey_unlock">Aflæsa lykli</string> + <string name="pubkey_failed_add">Rangt lykilorð fyrir lykilinn \'%1$s\'. Staðfesting mistókst</string> + <string name="pubkey_memory_load">Hlaða inn í minni</string> + <string name="pubkey_memory_unload">Hlaða úr minni</string> + <string name="pubkey_load_on_start">Hlaða lyklum við ræsingu</string> + <string name="pubkey_confirm_use">Staðfesta fyrir notkun</string> + <string name="portforward_list_empty">Ýttu á \"Menu\" til þess að\nbúa til port áframsendingu</string> + <string name="portforward_edit">Breyta áframsendingu ports</string> + <string name="portforward_delete">Eyða áframsendingu ports</string> + <string name="prompt_nickname">Gælunafn:</string> + <string name="prompt_nickname_hint_pubkey">Lykillinn minn</string> + <string name="prompt_source_port">Frá:</string> + <string name="prompt_destination">Til:</string> + <string name="prompt_old_password">Gamla lykilorðið</string> + <string name="prompt_password">Lykilorð:</string> + <string name="prompt_again">(aftur)</string> + <string name="prompt_type">Tegund:</string> + <string name="prompt_password_can_be_blank">ATH: lykilorðið getur verið tómt</string> + <string name="prompt_bits">Bitar:</string> + <string name="prompt_pubkey_password">Lykilorð fyrir lykilinn \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Leyfa þjóninum að\nnota lykilinn \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">VIÐVÖRUN: AUÐKENNING ÞJÓNSINS HEFUR BREYST!</string> + <string name="host_verification_failure_warning">ÞAÐ ER MÖGULEIKI Á AÐ EINHVER SÉ AÐ GERA EITTHVAÐ LJÓTT!\nÞað gæti verið að einhver sé að fylgjast með með þér (man-in-the-middle árás)!\nEn það gæti líka verið að lykill þjónsins hafi einfaldlega breyst.</string> + <string name="prompt_host_disconnected">Þjónninn hefur verið aftengdur.\nViltu loka?</string> + <string name="prompt_continue_connecting">Ertu viss um að þú\nviljir halda áfram að tengjast?</string> + <string name="host_authenticity_warning">Upprunaleiki lykilsins fyrir þjóninn \'%1$s\' getur ekki verið staðfestur.</string> + <string name="host_fingerprint">Þjónninn er %1$s fingrafar lykilsins er %2$s</string> + <string name="alert_passwords_do_not_match_msg">Lykilorðin passa ekki saman!</string> + <string name="alert_wrong_password_msg">Rangt lykilorð!</string> + <string name="alert_key_corrupted_msg">Einka lykillinn lítur út fyrir að vera skemmdur!</string> + <string name="alert_sdcard_absent">SD kort er ekki til staðar!</string> + <string name="button_add">Bæta við</string> + <string name="button_change">Breyta</string> + <string name="button_generate">Framkalla lykil</string> + <string name="button_resize">Breyta stærð</string> + <string name="alert_disconnect_msg">Tengingu tapað</string> + <string name="msg_copyright">Allur réttur áskilinn © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_ui_category">Notenda viðmót</string> + <string name="pref_rotation_title">Snúnings hamur</string> + <string name="pref_rotation_summary">Hvernig breyta skal snúningnum þegar lyklaborðið er sýnilegt/falið</string> + <string name="pref_fullscreen_title">Fylla skjáinn</string> + <string name="pref_fullscreen_summary">Fela stöðustiku þegar tenging er virk</string> + <string name="pref_memkeys_title">Geyma lykla í minni</string> + <string name="pref_memkeys_summary">Geyma ólæsta lykla í minni þangað til forritinu er lokað</string> + <string name="pref_conn_persist_title">Halda tengingum</string> + <string name="pref_conn_persist_summary">Halda tengingum í gangi þegar þær eru í bakgrunni</string> + <string name="pref_keymode_title">Orðabók</string> + <string name="pref_keymode_summary">Veldu hvernig nota skal Alt fyrir \'/\' og Shift fyrir Tab</string> + <string name="pref_camera_title">Myndavéla takkinn</string> + <string name="pref_camera_summary">Veldu hvað gerist þegar þrýst er á myndavéla takkann</string> + <string name="pref_keepalive_title">Halda skjánum í gangi</string> + <string name="pref_keepalive_summary">Banna skjánum að slokkna þegar tenging er virk</string> + <string name="pref_wifilock_title">Halda þráðlausu neti í gangi</string> + <string name="pref_wifilock_summary">Halda þráðlausu neti í gangi þegar tenging er virk</string> + <string name="pref_bumpyarrows_summary">Titra þegar örvar eru sendar með trackball; sniðugt fyrir óstöðugar tengingar</string> + <string name="pref_bell_category">Terminal bjalla</string> + <string name="pref_bell_title">Bjalla með hljóði</string> + <string name="pref_bell_volume_title">Hljóðstyrkur bjöllu</string> + <string name="pref_bell_vibrate_title">Titra þegar bjallan er virk</string> + <string name="pref_bell_notification_title">Boð frá bakgrunni</string> + <string name="pref_bell_notification_summary">Senda boð þegar þónn í bakgrunni sendir frá sé bjöllu.</string> + <string name="list_keymode_right">Nota hægri-hliðar lykla</string> + <string name="list_keymode_left">Nota vinstri-hliðar lykla</string> + <string name="list_keymode_none">Afvirkja</string> + <string name="list_pubkeyids_none">Ekki nota lykla</string> + <string name="list_pubkeyids_any">Nota hvaða ólæsta lykil sem er</string> + <string name="hostpref_nickname_title">Gælunafn</string> + <string name="hostpref_color_title">Lita flokk</string> + <string name="hostpref_fontsize_title">Leturstærð (pt)</string> + <string name="hostpref_pubkeyid_title">Innskráning með almennum lykli</string> + <string name="hostpref_postlogin_summary">Skipanir til þess að keyra á þjóni eftir innskráningu</string> + <string name="hostpref_compression_title">Þjöppun</string> + <string name="hostpref_compression_summary">Þetta getur hjálpað á hægu neti</string> + <string name="hostpref_stayconnected_title">Halda tengingu</string> + <string name="hostpref_stayconnected_summary">Reyna að endurtengjast ef aftenging verður</string> + <string name="hostpref_encoding_title">Kóðun</string> + <string name="hostpref_connection_category">Stillingar fyrir tengingu</string> + <string name="hostpref_username_title">Notendanafn</string> + <string name="hostpref_hostname_title">Þjónn</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Aldrei tengt</string> + <string name="bind_minutes">Fyrir %1$s mínútum síðan</string> + <string name="bind_hours">Fyrir %1$s tímum síðan</string> + <string name="bind_days">Fyrir %1$s dögum síðan</string> + <string name="console_copy_done">Afritaði %1$d bæti</string> + <string name="console_menu_close">Loka</string> + <string name="console_menu_copy">Afrita</string> + <string name="console_menu_paste">Líma</string> + <string name="console_menu_resize">Þvinga stærð</string> + <string name="console_menu_urlscan">Skanna fyrir slóðum</string> + <string name="button_yes">Já</string> + <string name="button_no">Nei</string> + <string name="portforward_dynamic">Breytilegt (SOCKS)</string> + <string name="portforward_pos">Áframsenda port</string> + <string name="portforward_done">Áframsendi port farsællega</string> + <string name="portforward_problem">Gat ekki áframsent portið, varstu að nota port minna en 1024? Eða er portið nú þegar áframsent?</string> + <string name="portforward_menu_add">Áframsenda port</string> + <string name="hint_userhost">notandi\@þjónn</string> + <string name="format_username">notendanafn</string> + <string name="format_hostname">þjónn</string> + <string name="format_port">port</string> + <string name="list_menu_sortcolor">Raða eftir lit</string> + <string name="list_menu_sortname">Raða eftir nafni</string> + <string name="list_menu_settings">Stillingar</string> + <string name="list_host_disconnect">Aftengjast</string> + <string name="list_host_edit">Breyta þjóni</string> + <string name="list_host_portforwards">Breyta áframsendingu ports</string> + <string name="list_host_delete">Eyða þjóni</string> + <string name="list_host_empty">Notaðu boxið hér að neðan\ntil þess að tengjast á þjón.</string> + <string name="list_rotation_default">Sjálfgefið</string> + <string name="list_rotation_land">þvinga á hlið</string> + <string name="list_rotation_port">þvinga beint</string> + <string name="list_rotation_auto">Sjálfgefið</string> + <string name="list_camera_ctrlaspace">Ctrl+A og Space</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Ekkert</string> + <string name="list_delkey_del">Eyða</string> + <string name="delete_message">Ert þú viss um að þú viljir eyða \'%1$s\'?</string> + <string name="delete_pos">Já, eyða</string> + <string name="delete_neg">Hætta við</string> + <string name="wizard_agree">Samþykkja</string> + <string name="wizard_next">Næsta</string> + <string name="wizard_back">Til baka</string> + <string name="terminal_no_hosts_connected">Engir þjónar tengdir í augnablikinu</string> + <string name="terminal_connecting">Tengist við %1$s:%2$d með %3$s</string> + <string name="terminal_failed">Ekki tókst að staðfesta lykil þjóns.</string> + <string name="terminal_using_algorithm">Nota algorithmann: %1$s %2$s</string> + <string name="terminal_auth">Reyni að staðfesta</string> + <string name="terminal_auth_pass">Reyni að tengjast með lykilorði</string> + <string name="terminal_auth_pass_fail">Ekki tókst að tengjast með lykilorði</string> + <string name="terminal_enable_portfoward">Áframsenda port: %1$s</string> + <string name="notification_text">%1$s krefst athygli þinnar.</string> + <string name="no">Nei</string> + <string name="with_confirmation">Með staðfestingu</string> + <string name="yes">Já</string> + <string name="exceptions_submit_message">Það lítur út fyrir að ConnectBot hafi verið í vandræðum síðast þegar það keyrði. Viltu senda villu greiningu á höfunda forritsins?</string> + <string name="menu_colors_reset">Endurstilla</string> + <string name="app_is_running">ConnectBot er í gangi</string> + <string name="color_red">rauður</string> + <string name="color_green">grænn</string> + <string name="color_blue">blár</string> + <string name="color_gray">grár</string> +</resources> diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..0b4e29b --- /dev/null +++ b/app/src/main/res/values-it/strings.xml @@ -0,0 +1,217 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Un client SSH semplice, potente ed open-source.</string> + <string name="service_desc">Mantieni le connessioni SSH e le chiavi pubbliche caricate</string> + <string name="title_hosts_list">Connessione</string> + <string name="title_pubkey_list">Chiavi pubbliche</string> + <string name="title_port_forwards_list">Inoltro porte</string> + <string name="title_host_editor">Modifica connessione</string> + <string name="title_help">Guida</string> + <string name="title_colors">Colori</string> + <string name="title_color_picker">Scelta colore</string> + <string name="resolve_connect">Connetti</string> + <string name="resolve_entropy">Raccolta disordinata</string> + <string name="menu_insert">Aggiungi connessione</string> + <string name="menu_delete">Elimina connessione</string> + <string name="menu_preferences">Preferenze</string> + <string name="help_intro">Seleziona un argomento dalla lista per ottenere maggiori informazioni.</string> + <string name="help_about">Informazioni su ConnectBot</string> + <string name="help_keyboard">Tastiera</string> + <string name="pubkey_generate">Genera</string> + <string name="pubkey_import">Importa</string> + <string name="pubkey_delete">Elimina chiave</string> + <string name="pubkey_gather_entropy">Raccolta entropia</string> + <string name="pubkey_touch_prompt">Tocca questo riquadro per raccogliere informazioni casuali: %1$d%% fatto</string> + <string name="pubkey_touch_hint">Per poter assicurare una sorgente di casualità durante la generazione della chiave, muovere le dita casualmente sul riquadro sottostante.</string> + <string name="pubkey_generating">La coppia di chiavi viene generata...</string> + <string name="pubkey_copy_private">Copia chiave privata</string> + <string name="pubkey_copy_public">Copia chiave pubblica</string> + <string name="pubkey_list_empty">Seleziona Menu per creare\no importare chiavi.</string> + <string name="pubkey_unknown_format">Formato non riconosciuto</string> + <string name="pubkey_change_password">Cambia password</string> + <string name="pubkey_list_pick">Leggi da /sdcard</string> + <string name="pubkey_import_parse_problem">Problemi durante l\'importazione della chiave privata</string> + <string name="pubkey_unlock">Chiave di sblocco</string> + <string name="pubkey_failed_add">Password errata per la chiave \'%1$s\'. Errore di autenticazione.</string> + <string name="pubkey_memory_load">Carica in memoria</string> + <string name="pubkey_memory_unload">Elimina dalla memoria</string> + <string name="pubkey_load_on_start">Carica la chiave all\'avvio</string> + <string name="pubkey_confirm_use">Conferma prima di usare</string> + <string name="portforward_list_empty">Seleziona Menu per creare\ninoltri delle porte.</string> + <string name="portforward_edit">Modifica inoltro porte</string> + <string name="portforward_delete">Elimina inoltro porta</string> + <string name="prompt_nickname">Nome utente:</string> + <string name="prompt_nickname_hint_pubkey">La mia chiave di lavoro</string> + <string name="prompt_source_port">Porta sorgente:</string> + <string name="prompt_destination">Destinazione:</string> + <string name="prompt_old_password">Vecchia password:</string> + <string name="prompt_password">Password:</string> + <string name="prompt_again">(ripeti)</string> + <string name="prompt_type">Tipo:</string> + <string name="prompt_password_can_be_blank">Nota: la password può essere vuota</string> + <string name="prompt_bits">Bits:</string> + <string name="prompt_pubkey_password">Password per la chiave \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Permetti all\'host remoto di\n usare la chiave \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">ATTENZIONE: L\'IDENTIFICAZIONE DELL\'HOST REMOTO E\' CAMBIATA!</string> + <string name="host_verification_failure_warning">E\' POSSIBILE CHE QUALCUNO STIA FACENDO QUALCOSA DI MALEVOLO!\nQualcuno potrebbe spiarti in questo momento (attacco man-in-the-middle)!\nMa è anche possibile che la chiave dell\'host sia cambiata.</string> + <string name="prompt_host_disconnected">L\'host si è disconnesso.\nChiudere la sessione?</string> + <string name="prompt_continue_connecting">Sei sicuro di voler\ncontinuare a connetterti?</string> + <string name="host_authenticity_warning">L\'autenticità dell\'host \'%1$s\' non può essere accertata.</string> + <string name="host_fingerprint">L\'impronta digitale della chiave dell\'Host %1$s è %2$s</string> + <string name="alert_passwords_do_not_match_msg">Le password non corrispondono!</string> + <string name="alert_wrong_password_msg">Password errata!</string> + <string name="alert_key_corrupted_msg">La chiave privata sembra corrotta!</string> + <string name="alert_sdcard_absent">Scheda SD non trovata!</string> + <string name="button_add">Aggiungi</string> + <string name="button_change">Modifica</string> + <string name="button_generate">Genera le chiavi</string> + <string name="button_resize">Ridimensiona</string> + <string name="alert_disconnect_msg">Connessione persa</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulazione terminale</string> + <string name="pref_emulation_title">Modalità emulazione</string> + <string name="pref_emulation_summary">Modalità di emulazione terminale da usare per connessioni PTY</string> + <string name="pref_scrollback_title">Dimensione scrollback</string> + <string name="pref_scrollback_summary">Dimensione del buffer scrollback da tenere in memoria per ciascuna console</string> + <string name="pref_ui_category">Interfaccia utente</string> + <string name="pref_rotation_title">Modalità rotazione</string> + <string name="pref_rotation_summary">Come cambiare rotazione quando la tastiera esce/rientra</string> + <string name="pref_fullscreen_title">Schermo intero</string> + <string name="pref_fullscreen_summary">Nascondi la barra di stato in console</string> + <string name="pref_keyboard_category">Tastiera</string> + <string name="pref_memkeys_title">Mantieni chiavi in memoria</string> + <string name="pref_memkeys_summary">Mantiene le chiavi in memoria finché il servizio non viene terminato</string> + <string name="pref_conn_persist_title">Connessioni permanenti</string> + <string name="pref_conn_persist_summary">Forza connessioni a rimanere collegate in sottofondo</string> + <string name="pref_keymode_title">Scorciatoie directory</string> + <string name="pref_keymode_summary">Controlla l\'uso di Alt per \'/\' e di Shift per Tab</string> + <string name="pref_camera_title">Scorciatoia camera</string> + <string name="pref_camera_summary">Seleziona quale scorciatoia azionare quando il bottone della camera viene premuto</string> + <string name="pref_keepalive_title">Impedisci lo spegnimento dello schermo</string> + <string name="pref_keepalive_summary">Impedisce lo spegnimento dello schermo quando si è collegati</string> + <string name="pref_wifilock_title">Mantieni Wi-Fi attivo</string> + <string name="pref_wifilock_summary">Non spegne la connessione Wi-Fi quando una sessione è attiva</string> + <string name="pref_bumpyarrows_title">Vibrazione frecce</string> + <string name="pref_bumpyarrows_summary">Vibra quando si inviano movimenti con la trackball; utile per connessioni lente</string> + <string name="pref_bell_category">Campanella del terminale</string> + <string name="pref_bell_title">Campanella udibile</string> + <string name="pref_bell_volume_title">Volume della campanella</string> + <string name="pref_bell_vibrate_title">Vibra alla campanella</string> + <string name="pref_bell_notification_title">Notifiche in sottofondo</string> + <string name="pref_bell_notification_summary">Invia una notifica quando un terminale eseguito in sottofondo suona una campanella.</string> + <string name="list_keymode_right">Usa tasti sul lato destro</string> + <string name="list_keymode_left">Usa tasti sul lato sinistro</string> + <string name="list_keymode_none">Disabilita</string> + <string name="list_pubkeyids_none">Non usare chiavi</string> + <string name="list_pubkeyids_any">Usa qualsiasi chiave sbloccata</string> + <string name="hostpref_nickname_title">Soprannome</string> + <string name="hostpref_color_title">Categoria colore</string> + <string name="hostpref_fontsize_title">Dimensione caratteri (pt)</string> + <string name="hostpref_pubkeyid_title">Usa autenticazione a chiave pubblica</string> + <string name="hostpref_authagent_title">Usa l\'agente di autorizzazione SSH</string> + <string name="hostpref_postlogin_title">Automazione post-accesso</string> + <string name="hostpref_postlogin_summary">Comandi da eseguire su un server remoto una volta autenticati</string> + <string name="hostpref_compression_title">Compressione</string> + <string name="hostpref_compression_summary">Può essere utile con connessioni lente</string> + <string name="hostpref_wantsession_title">Avvia sessione shell</string> + <string name="hostpref_wantsession_summary">Disabilita questa preferenza per usare solo port forward</string> + <string name="hostpref_stayconnected_title">Mantieni connessioni</string> + <string name="hostpref_stayconnected_summary">Tenta di riconnettersi in caso di disconnessione</string> + <string name="hostpref_delkey_title">Tasto CANC</string> + <string name="hostpref_delkey_summary">Il codice di tastiera inviato quando viene premuto il tasto CANC</string> + <string name="hostpref_encoding_title">Codifica</string> + <string name="hostpref_encoding_summary">Codifica dei caratteri per la connessione</string> + <string name="hostpref_connection_category">Impostazioni di connessione</string> + <string name="hostpref_username_title">Nome utente</string> + <string name="hostpref_hostname_title">Host</string> + <string name="hostpref_port_title">Porta</string> + <string name="bind_never">Mai connesso</string> + <string name="bind_minutes">%1$s minuti fa</string> + <string name="bind_hours">%1$s ore fa</string> + <string name="bind_days">%1$s giorni fa</string> + <string name="console_copy_done">%1$d byte copiati</string> + <string name="console_copy_start">Tocca e trascina\noppure usa il pad direzionale\nper selezionare un\'area da copiare</string> + <string name="console_menu_close">Chiudi</string> + <string name="console_menu_copy">Copia</string> + <string name="console_menu_paste">Incolla</string> + <string name="console_menu_portforwards">Inoltro porte</string> + <string name="console_menu_resize">Forza Dimensione</string> + <string name="console_menu_urlscan">Scansione URL</string> + <string name="button_yes">Sì</string> + <string name="button_no">No</string> + <string name="portforward_local">Locale</string> + <string name="portforward_remote">Remota</string> + <string name="portforward_dynamic">Dinamica (SOCKS)</string> + <string name="portforward_pos">Crea inoltro porta</string> + <string name="portforward_done">Inoltro della porta impostato correttamente</string> + <string name="portforward_problem">Problema configurando l\'inoltro della porta, forse si cerca di usare una porta più bassa di 1024 o la porta è già in uso?</string> + <string name="portforward_menu_add">Aggiungi inoltro di una porta</string> + <string name="hint_userhost">utente\@host</string> + <string name="list_format_error">Usa il formato %1$s</string> + <string name="format_username">nome utente</string> + <string name="format_hostname">nome host</string> + <string name="format_port">porta</string> + <string name="list_menu_pubkeys">Gestione chiavi pubbliche</string> + <string name="list_menu_sortcolor">Ordina per colore</string> + <string name="list_menu_sortname">Ordina per nome</string> + <string name="list_menu_settings">Impostazioni</string> + <string name="list_host_disconnect">Disconnetti</string> + <string name="list_host_edit">Modifica connessione</string> + <string name="list_host_portforwards">Modifica inoltri porta</string> + <string name="list_host_delete">Elimina connessione</string> + <string name="list_host_empty">Usa la casella connessione rapida\nqui sotto per connetterti ad un host.</string> + <string name="list_rotation_default">Predefinita</string> + <string name="list_rotation_land">Forza orizzontale</string> + <string name="list_rotation_port">Forza verticale</string> + <string name="list_rotation_auto">Automatica</string> + <string name="list_camera_ctrlaspace">Ctrl+A poi Spazio</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Niente</string> + <string name="list_delkey_backspace">Barra Spaziatrice</string> + <string name="list_delkey_del">Elimina</string> + <string name="delete_message">Sei sicuro di voler eliminare \'%1$s\'?</string> + <string name="delete_pos">Sì, elimina</string> + <string name="delete_neg">Annulla</string> + <string name="wizard_agree">Accetto</string> + <string name="wizard_next">Successiva</string> + <string name="wizard_back">Precedente</string> + <string name="terminal_no_hosts_connected">Nessun host attualmente connesso</string> + <string name="terminal_connecting">Connessione a %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Host verificato \'%1$s\' chiave: %2$s</string> + <string name="terminal_failed">Verifica della chiave dell\'host fallita.</string> + <string name="terminal_using_s2c_algorithm">Algoritmo server-to-client: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algoritmo client-to-server: %1$s %2$s</string> + <string name="terminal_using_algorithm">Algoritmo in uso: %1$s %2$s</string> + <string name="terminal_auth">Tentativo di autenticazione</string> + <string name="terminal_auth_pass">Provando l\'autenticazione \'password\'</string> + <string name="terminal_auth_pass_fail">Metodo di autenticazione \'password\' fallito</string> + <string name="terminal_auth_pubkey_any">Tentativo di utilizzo autenticazione a \'chiave pubblica\' con una delle chiavi pubbliche in memoria</string> + <string name="terminal_auth_pubkey_invalid">La chiave pubblica selezionata non è valida, provare a riselezionare la chiave nell\'host editor</string> + <string name="terminal_auth_pubkey_specific">Tentativo di autenticazione a \'chiave pubblica\' con una chiave pubblica specifica</string> + <string name="terminal_auth_pubkey_fail">Metodo di autenticazione a \'chiave pubblica\' con la chiave \'%1$s\' fallito</string> + <string name="terminal_auth_ki">Provando l\'autenticazione \'keyboard-interactive\'</string> + <string name="terminal_auth_ki_fail">Metodo di autenticazione \'keyboard-interactive\' fallito</string> + <string name="terminal_auth_fail">[Il tuo host non supporta l\'autenticazione \'password\' o \'keyboard-interactive\'</string> + <string name="terminal_no_session">La sessione non inizierà a causa delle preferenze dell\'host.</string> + <string name="terminal_enable_portfoward">Abilita il port forward: %1$s</string> + <string name="local_shell_unavailable">Fallimento! La shell locale non è disponibile su questo telefono.</string> + <string name="notification_text">%1$s richiede la tua attenzione.</string> + <string name="no">No</string> + <string name="with_confirmation">Richiedi conferma</string> + <string name="yes">Sì</string> + <string name="exceptions_submit_message">Sembrerebbe che ConnectBot abbia avuto un problema durante l\'ultima esecuzione. Inviare una segnalazione d\'errore agli sviluppatori di ConnectBot?</string> + <string name="menu_colors_reset">Reimposta</string> + <string name="app_is_running">ConnectBot è in esecuzione</string> + <string name="color_red">rosso</string> + <string name="color_green">verde</string> + <string name="color_blue">blu</string> + <string name="color_gray">grigio</string> + <string name="colors_fg_label">CP: %1$d</string> + <string name="color_bg_label">CS: %1$d</string> + <string name="image_description_connected">Connesso.</string> + <string name="image_description_key_is_locked">La chiave e\' bloccata.</string> + <string name="image_description_send_escape_character">Mandare un carattere escape.</string> + <string name="image_description_show_keyboard">Mostra la tastiera.</string> +</resources> diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..21d4fdc --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,226 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">シンプルでパワフルなオープンソースのSSHクライアント</string> + <string name="service_desc">SSH接続とロードされた公開鍵の管理</string> + <string name="title_hosts_list">ホスト</string> + <string name="title_pubkey_list">公開鍵</string> + <string name="title_port_forwards_list">ポート転送</string> + <string name="title_host_editor">ホストを編集</string> + <string name="title_help">ヘルプ</string> + <string name="title_colors">配色</string> + <string name="title_color_picker">色の取得</string> + <string name="resolve_connect">接続</string> + <string name="resolve_entropy">エントロピーの取得</string> + <string name="menu_insert">ホストを追加</string> + <string name="menu_delete">ホストを削除</string> + <string name="menu_preferences">設定</string> + <string name="help_intro">特定の情報について知りたいときは、以下のトピックを選択してください。</string> + <string name="help_about">ConnectBotについて</string> + <string name="help_keyboard">キーボード</string> + <string name="pubkey_generate">生成</string> + <string name="pubkey_import">インポート</string> + <string name="pubkey_delete">鍵を削除</string> + <string name="pubkey_gather_entropy">エントロピー収集中</string> + <string name="pubkey_touch_prompt">この四角内をランダムに触ってください: %1$d%% 完了</string> + <string name="pubkey_touch_hint">鍵作成時のランダムさを保証するため、下の四角内で指をランダムに動かしてください。</string> + <string name="pubkey_generating">鍵ペアを生成しています...</string> + <string name="pubkey_copy_private">秘密鍵をコピー</string> + <string name="pubkey_copy_public">公開鍵をコピー</string> + <string name="pubkey_list_empty">Menuボタンを押して鍵の作成またはインポートを行ってください。</string> + <string name="pubkey_unknown_format">不明なフォーマット</string> + <string name="pubkey_change_password">パスワードを変更</string> + <string name="pubkey_list_pick">SDカードから選ぶ</string> + <string name="pubkey_import_parse_problem">インポートした秘密鍵を解析時に問題発生</string> + <string name="pubkey_unlock">鍵をアンロック</string> + <string name="pubkey_failed_add">鍵 \'%1$s\' のパスワードが正しくありません。認証に失敗しました。</string> + <string name="pubkey_memory_load">メモリにロード</string> + <string name="pubkey_memory_unload">メモリからアンロード</string> + <string name="pubkey_load_on_start">起動時に鍵をロード</string> + <string name="pubkey_confirm_use">使用時に確認する</string> + <string name="portforward_list_empty">MENUをタップして\nポート転送を作成します.</string> + <string name="portforward_edit">ポート転送を編集</string> + <string name="portforward_delete">ポート転送を削除</string> + <string name="prompt_nickname">鍵の名前</string> + <string name="prompt_nickname_hint_pubkey">私の作業用の鍵</string> + <string name="prompt_source_port">ソースポート:</string> + <string name="prompt_destination">転送先:</string> + <string name="prompt_old_password">旧パスワード:</string> + <string name="prompt_password">パスワード:</string> + <string name="prompt_again">(再入力)</string> + <string name="prompt_type">タイプ:</string> + <string name="prompt_password_can_be_blank">備考: パスワードは空欄にできます。</string> + <string name="prompt_bits">ビット長:</string> + <string name="prompt_pubkey_password">鍵 \'%1$s\' のパスワード</string> + <string name="prompt_allow_agent_to_use_key">リモートホストが鍵 \'%1$s\' を使用することを許可しますか?</string> + <string name="host_verification_failure_warning_header">警告: リモートホストのIDが変わっています!</string> + <string name="host_verification_failure_warning">何者かが不正を行っている可能性があります!\n現在誰かに盗聴されているかもしれません (中間者攻撃)!\n単にホストキーが変更されただけの可能性もあります。</string> + <string name="prompt_host_disconnected">ホスト切断されました.\nセッションを閉じますか?</string> + <string name="prompt_continue_connecting">このまま接続しますか?</string> + <string name="host_authenticity_warning">ホスト \'%1$s\' の確認ができませんでした。</string> + <string name="host_fingerprint">ホストの%1$sキーフィンガープリントは%2$s</string> + <string name="alert_passwords_do_not_match_msg">パスワードが一致しません!</string> + <string name="alert_wrong_password_msg">間違ったパスワードです.</string> + <string name="alert_key_corrupted_msg">秘密鍵が壊れています!</string> + <string name="alert_sdcard_absent">SD カードが挿入されていません.</string> + <string name="button_add">追加</string> + <string name="button_change">変更</string> + <string name="button_generate">鍵生成</string> + <string name="button_resize">リサイズ</string> + <string name="alert_disconnect_msg">接続が失われました.</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">端末エミュレート</string> + <string name="pref_emulation_title">エミュレートモード</string> + <string name="pref_emulation_summary">PTYで使う端末エミュレーションモード</string> + <string name="pref_scrollback_title">スクロールサイズ</string> + <string name="pref_scrollback_summary">メモリに保持するスクロールバッファのサイズ</string> + <string name="pref_ui_category">ユーザーインターフェース</string> + <string name="pref_rotation_title">画面の向き</string> + <string name="pref_rotation_summary">キーボードのポップイン/アウト時の向きの変更</string> + <string name="pref_titlebarhide_title">タイトルバーを自動的に隠す</string> + <string name="pref_titlebarhide_summary">タイトルバーとアクセスメニューを表示するにはコンソールをタップ</string> + <string name="pref_fullscreen_title">全画面</string> + <string name="pref_fullscreen_summary">ステータスバーを隠蔽しコンソールとして利用</string> + <string name="pref_keyboard_category">キーボード</string> + <string name="pref_shiftfkeys_title">Shift+数字はFキー</string> + <string name="pref_shiftfkeys_summary">ハードウェアキーボードで、shiftと数字キーでF1-F10を送信します</string> + <string name="pref_ctrlfkeys_title">Ctrl+数字はFキー</string> + <string name="pref_ctrlfkeys_summary">ソフトウェアキーボードで、ctrlと数字キーでF1-F10を送信します</string> + <string name="pref_volumefont_title">音量ボタンでフォントサイズを変更する</string> + <string name="pref_volumefont_summary">フォントサイズはホストごとの設定でも変更することができます</string> + <string name="pref_memkeys_title">メモリに鍵を保持する</string> + <string name="pref_memkeys_summary">バックエンドのサービスが終了しない限り、アンロックした鍵をメモリに保持します</string> + <string name="pref_conn_persist_title">持続的接続</string> + <string name="pref_conn_persist_summary">バックグラウンドでの実行中に接続の持続を強制する</string> + <string name="pref_keymode_title">ディレクトリのショートカット</string> + <string name="pref_keymode_summary">Altを\'/\'、ShiftをTabに割り当てます</string> + <string name="pref_camera_title">カメラボタンショートカット</string> + <string name="pref_camera_summary">カメラボタンが押されたときのショートカットを選びます</string> + <string name="pref_keepalive_title">画面をスリープしない</string> + <string name="pref_keepalive_summary">コンソール作業中に画面がOFFになるのを防ぎます</string> + <string name="pref_wifilock_title">Wi-Fiをスリープしない</string> + <string name="pref_wifilock_summary">セッションが有効な間Wi-FiがOFFになるのを防ぎます</string> + <string name="pref_bumpyarrows_title">矢印で振動</string> + <string name="pref_bumpyarrows_summary">トラックボールで矢印キーを送信するときに振動します; ラグがある接続時に便利</string> + <string name="pref_bell_category">端末音設定</string> + <string name="pref_bell_title">ベル音</string> + <string name="pref_bell_volume_title">ボリューム設定</string> + <string name="pref_bell_vibrate_title">バイブレーション</string> + <string name="pref_bell_notification_title">バックグラウンド通知</string> + <string name="pref_bell_notification_summary">バックグランドで実行中の端末がベルを鳴らしたとき通知します</string> + <string name="list_keymode_right">右側のキーで使う</string> + <string name="list_keymode_left">左側のキーで使う</string> + <string name="list_keymode_none">無効</string> + <string name="list_pubkeyids_none">公開鍵を使わない</string> + <string name="list_pubkeyids_any">アンロックされた鍵のいずれかを使う</string> + <string name="hostpref_nickname_title">ニックネーム</string> + <string name="hostpref_color_title">色の系統</string> + <string name="hostpref_fontsize_title">フォントのサイズ (pt)</string> + <string name="hostpref_pubkeyid_title">公開鍵認証を使用</string> + <string name="hostpref_authagent_title">SSH認証エージェントを使う</string> + <string name="hostpref_postlogin_title">ログイン後の自動実行</string> + <string name="hostpref_postlogin_summary">認証後にリモートで実行するコマンド</string> + <string name="hostpref_compression_title">圧縮</string> + <string name="hostpref_compression_summary">遅いネットワークで有効かもしれません</string> + <string name="hostpref_wantsession_title">シェルセッションを使う</string> + <string name="hostpref_wantsession_summary">ポート転送のみ行う場合に無効にしてください</string> + <string name="hostpref_stayconnected_title">接続の維持</string> + <string name="hostpref_stayconnected_summary">接続が切れたら再接続を試みます</string> + <string name="hostpref_delkey_title">DELキー</string> + <string name="hostpref_delkey_summary">DELキーが押されたときに送るキーコード</string> + <string name="hostpref_encoding_title">エンコーディング</string> + <string name="hostpref_encoding_summary">ホスト側の文字エンコーディング</string> + <string name="hostpref_connection_category">接続設定</string> + <string name="hostpref_username_title">ユーザ名</string> + <string name="hostpref_hostname_title">ホスト</string> + <string name="hostpref_port_title">ポート</string> + <string name="bind_never">未接続</string> + <string name="bind_minutes">%1$s 分前</string> + <string name="bind_hours">%1$s 時間前</string> + <string name="bind_days">%1$s 日前</string> + <string name="console_copy_done">クリップボードに %1$d バイトコピーしました.</string> + <string name="console_copy_start">タッチしてドラッグ\nまたは方向パッドで\nコピー領域を選択</string> + <string name="console_menu_close">切断</string> + <string name="console_menu_copy">コピー</string> + <string name="console_menu_paste">貼り付け</string> + <string name="console_menu_portforwards">ポート転送</string> + <string name="console_menu_resize">サイズ強制</string> + <string name="console_menu_urlscan">URL スキャン</string> + <string name="button_yes">はい</string> + <string name="button_no">いいえ</string> + <string name="portforward_local">ローカル</string> + <string name="portforward_remote">リモート</string> + <string name="portforward_dynamic">動的 (SOCKS)</string> + <string name="portforward_pos">ポート転送の作成</string> + <string name="portforward_done">ポート転送の作成に成功しました.</string> + <string name="portforward_problem">ポート転送の作成ができません.おそらく1024ポート以下か既に利用されているポートを指定されています.</string> + <string name="portforward_menu_add">ポート転送の追加</string> + <string name="hint_userhost">ユーザ\@ホスト名</string> + <string name="list_format_error">%1$s 形式を使ってください</string> + <string name="format_username">ユーザ名</string> + <string name="format_hostname">ホスト名</string> + <string name="format_port">ポート</string> + <string name="list_menu_pubkeys">公開鍵管理</string> + <string name="list_menu_sortcolor">色でソート</string> + <string name="list_menu_sortname">名前順でソート</string> + <string name="list_menu_settings">設定</string> + <string name="list_host_disconnect">切断</string> + <string name="list_host_edit">接続ホストの編集</string> + <string name="list_host_portforwards">ポート転送の編集</string> + <string name="list_host_delete">接続ホストの削除</string> + <string name="list_host_empty">下のquick-connect box\nを使ってホストに接続できます.</string> + <string name="list_rotation_default">デフォルト</string> + <string name="list_rotation_land">横向き固定</string> + <string name="list_rotation_port">縦向き固定</string> + <string name="list_rotation_auto">自動判定</string> + <string name="list_camera_ctrlaspace">Ctrl+Aに続いてSpace</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Escキー</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">なし</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Delete</string> + <string name="delete_message">\'%1$s\'を削除してよろしいですか?</string> + <string name="delete_pos">はい、削除します</string> + <string name="delete_neg">キャンセル</string> + <string name="wizard_agree">同意します</string> + <string name="wizard_next">次へ</string> + <string name="wizard_back">戻る</string> + <string name="terminal_no_hosts_connected">接続済みのホストはありません.</string> + <string name="terminal_connecting">%1$s:%2$dに%3$sで接続しています</string> + <string name="terminal_sucess">ホスト\'%1$s\' キー%2$s を検証しました</string> + <string name="terminal_failed">ホストキーが検証できません</string> + <string name="terminal_using_s2c_algorithm">サーバtoクライアント アルゴリズム: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">クライアントからサーバ アルゴリズム: %1$s %2$s</string> + <string name="terminal_using_algorithm">使用アルゴリズム: %1$s %2$s</string> + <string name="terminal_auth">認証を試行中</string> + <string name="terminal_auth_pass">\'password\' 認証を試行</string> + <string name="terminal_auth_pass_fail">\'password\' 認証失敗</string> + <string name="terminal_auth_pubkey_any">メモリ上の公開鍵による \'publickey\' 認証を試行</string> + <string name="terminal_auth_pubkey_invalid">選択された公開鍵は無効です、ホストの編集で選び直してください</string> + <string name="terminal_auth_pubkey_specific">指定された公開鍵による \'publickey\' 認証を試行</string> + <string name="terminal_auth_pubkey_fail">鍵 \'%1$s\' での \'publickey\' 認証に失敗</string> + <string name="terminal_auth_ki">\'keyboard-interactive\' 認証を試行</string> + <string name="terminal_auth_ki_fail">\'keyboard-interactive\' 認証に失敗</string> + <string name="terminal_auth_fail">[ホストは \'password\' や \'keyboard-interactive\' 認証をサポートしていません.]</string> + <string name="terminal_no_session">ホスト設定によりセッションを開始しません</string> + <string name="terminal_enable_portfoward">ポート転送 %1$s を有効化</string> + <string name="local_shell_unavailable">エラー! この電話機にはローカルシェルがありません.</string> + <string name="notification_text">%1$s でベルが鳴りました</string> + <string name="no">いいえ</string> + <string name="with_confirmation">確認が必要</string> + <string name="yes">はい</string> + <string name="exceptions_submit_message">前回ConnectBot実行時に問題が発生したようです。ConnectBotの開発者にエラーレポートを送信しますか?</string> + <string name="menu_colors_reset">リセット</string> + <string name="app_is_running">ConnectBot起動中</string> + <string name="color_red">赤</string> + <string name="color_green">緑</string> + <string name="color_blue">青</string> + <string name="color_gray">グレー</string> + <string name="colors_fg_label">FG: %1$d</string> + <string name="color_bg_label">BG: %1$d</string> + <string name="image_description_connected">接続しました。</string> + <string name="image_description_key_is_locked">キーがロックされました。</string> + <string name="image_description_toggle_control_character">コントロール文字を切り替えます。</string> + <string name="image_description_send_escape_character">エスケープ文字を送信します。</string> + <string name="image_description_show_keyboard">キーボードを表示します。</string> +</resources> diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml new file mode 100644 index 0000000..d1343d7 --- /dev/null +++ b/app/src/main/res/values-ka/strings.xml @@ -0,0 +1,15 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="title_host_editor">ჰოსტის შეცვლა</string> + <string name="title_help">დახმარება</string> + <string name="title_colors">ფერი</string> + <string name="resolve_connect">დაკავშირება</string> + <string name="menu_insert">ჰოსტის დამატება</string> + <string name="menu_delete">ჰოსტის წაშლა</string> + <string name="menu_preferences">პარამეტრები</string> + <string name="help_about">ConnectBot-ის შესახებ</string> + <string name="help_keyboard">კლავიატურა</string> + <string name="pubkey_generate">გენერირება</string> + <string name="pubkey_import">იმპორტი</string> + <string name="pubkey_delete">გასაღების წაშლა</string> +</resources> diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..83313ef --- /dev/null +++ b/app/src/main/res/values-ko/strings.xml @@ -0,0 +1,187 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">간단하고, 강력한, 오픈소스 SSH 클라이언트.</string> + <string name="title_hosts_list">호스트</string> + <string name="title_pubkey_list">공개키</string> + <string name="title_port_forwards_list">포트 포워딩</string> + <string name="title_host_editor">호스트 편집</string> + <string name="title_help">도움말</string> + <string name="title_colors">색상</string> + <string name="resolve_connect">접속</string> + <string name="resolve_entropy">엔트로피 수집</string> + <string name="menu_insert">호스트 추가</string> + <string name="menu_delete">호스트 삭제</string> + <string name="menu_preferences">기본 설정</string> + <string name="help_intro">아래의 제목들을 선택하면 특정 주제의 정보를 더 알 수 있습니다.</string> + <string name="help_about">ConnectBot 정보</string> + <string name="help_keyboard">키보드</string> + <string name="pubkey_generate">생성</string> + <string name="pubkey_import">가져오기</string> + <string name="pubkey_delete">키 삭제</string> + <string name="pubkey_gather_entropy">엔트로피 수집 중</string> + <string name="pubkey_touch_prompt">이 사각형의 랜덤으로 터치하세요: %1$d%% 완료</string> + <string name="pubkey_touch_hint">키 생성시 랜덤을 보장하기 위해 아래의 사각형 안에서 손가락을 랜덤으로 이동하십시오.</string> + <string name="pubkey_generating">키 쌍 생성 중...</string> + <string name="pubkey_copy_private">개인 키 복사</string> + <string name="pubkey_copy_public">공개 키 복사</string> + <string name="pubkey_list_empty">메뉴 버튼을 눌러 키 쌍을\n만들거나 가져 옵니다.</string> + <string name="pubkey_unknown_format">알 수 없는 형식</string> + <string name="pubkey_change_password">암호 바꾸기</string> + <string name="pubkey_list_pick">/sdcard에서 선택</string> + <string name="pubkey_import_parse_problem">가져온 개인키 분석 중 문제 발생</string> + <string name="pubkey_unlock">키 잠금 해제</string> + <string name="pubkey_memory_load">메모리에 올리기</string> + <string name="pubkey_memory_unload">메모리에서 내리기</string> + <string name="pubkey_load_on_start">시작할 때 키를 올리기</string> + <string name="pubkey_confirm_use">사용 전에 확인</string> + <string name="portforward_list_empty">메뉴를 눌러\n포트 포워딩을 만듭니다.</string> + <string name="portforward_edit">포트 포워딩 편집</string> + <string name="portforward_delete">포트 포워딩을 삭제</string> + <string name="prompt_nickname">대화명:</string> + <string name="prompt_nickname_hint_pubkey">내 작업 키</string> + <string name="prompt_source_port">원본 포트:</string> + <string name="prompt_destination">설명:</string> + <string name="prompt_old_password">이전 암호:</string> + <string name="prompt_password">암호:</string> + <string name="prompt_again">(다시)</string> + <string name="prompt_type">종류:</string> + <string name="prompt_password_can_be_blank">참고: 암호는 비워둘 수 있습니다.</string> + <string name="prompt_bits">비트:</string> + <string name="host_verification_failure_warning_header">경고: 원격 호스트의 식별이 바뀌었습니다!</string> + <string name="host_verification_failure_warning">누군가가 나쁜 짓을 하고 있는 것일 수 있습니다!\n지금 누군가가 당신을 도청하고 있을 수도 있습니다 (중간자 공격)!\n또는, 단순히 호스트 키가 바뀐 것일 수도 있습니다.</string> + <string name="prompt_host_disconnected">호스트 연결이 끊겼습니다.\n세션을 닫을까요?</string> + <string name="prompt_continue_connecting">정말로 연결을 진행하기\n바랍니까?</string> + <string name="host_fingerprint">호스트 %1$s 키 지문은 %2$s</string> + <string name="alert_passwords_do_not_match_msg">암호가 맞지 않습니다!</string> + <string name="alert_wrong_password_msg">잘못된 암호입니다!</string> + <string name="alert_key_corrupted_msg">개인 키가 손상되었습니다!</string> + <string name="alert_sdcard_absent">SD 카드가 삽입되지 않습니다!</string> + <string name="button_add">추가</string> + <string name="button_change">바꾸기</string> + <string name="button_generate">키 생성</string> + <string name="button_resize">크기 조정</string> + <string name="alert_disconnect_msg">연결이 끊어짐</string> + <string name="pref_emulation_category">터미널 에뮬레이션</string> + <string name="pref_emulation_title">에뮬레이션 모드</string> + <string name="pref_emulation_summary">PTY 연결 용 터미널 에뮬레이션 모드</string> + <string name="pref_scrollback_title">스크롤 크기</string> + <string name="pref_scrollback_summary">각 콘솔별로 메모리에 담아 둘 스크롤 버퍼의 크기</string> + <string name="pref_ui_category">사용자 인터페이스</string> + <string name="pref_rotation_title">회전 모드</string> + <string name="pref_rotation_summary">키보드 팝 인/아웃시 회전 방법</string> + <string name="pref_fullscreen_title">전체 화면</string> + <string name="pref_fullscreen_summary">콘솔일 때 상태 표시줄을 숨김</string> + <string name="pref_memkeys_title">메모리에 키를 기억</string> + <string name="pref_memkeys_summary">백엔드 서비스가 종료되기 전까지 잠금이 풀린 키들을 메모리에 유지합니다</string> + <string name="pref_conn_persist_title">접속 유지</string> + <string name="pref_conn_persist_summary">백그라운드 동안에도 강제로 연결된 상태 유지</string> + <string name="pref_keymode_title">디렉터리 바로 가기</string> + <string name="pref_camera_title">카메라 바로 가기</string> + <string name="pref_camera_summary">카메라 버튼을 눌러 실행할 바로 가기 선택</string> + <string name="pref_keepalive_title">화면 켜짐 유지</string> + <string name="pref_keepalive_summary">콘솔 작업 중 화면이 꺼지는 것을 방지</string> + <string name="pref_wifilock_title">Wi-Fi 켜짐 유지</string> + <string name="pref_wifilock_summary">세션이 유효한 동안 Wi-Fi가 꺼지지 않도록 함</string> + <string name="pref_bumpyarrows_title">방향키 진동</string> + <string name="pref_bumpyarrows_summary">트랙볼로 방향키를 보낼 때 진동합니다; 연결이 느린 경우 유용</string> + <string name="pref_bell_category">터미널 벨</string> + <string name="pref_bell_title">벨소리</string> + <string name="pref_bell_volume_title">벨 볼륨</string> + <string name="pref_bell_vibrate_title">진동 벨</string> + <string name="pref_bell_notification_title">백그라운드 알림</string> + <string name="pref_bell_notification_summary">백그라운드에서 실행중인 터미널 벨을 울리 때 알립니다.</string> + <string name="list_keymode_right">오른쪽의 키들 사용</string> + <string name="list_keymode_left">왼쪽의 키들 사용</string> + <string name="list_keymode_none">사용 안 함</string> + <string name="list_pubkeyids_none">키 사용 안함</string> + <string name="list_pubkeyids_any">잠금키 사용</string> + <string name="hostpref_nickname_title">닉네임</string> + <string name="hostpref_color_title">색상 카테고리</string> + <string name="hostpref_fontsize_title">글꼴 크기 (pt)</string> + <string name="hostpref_pubkeyid_title">공개 키 인증 사용</string> + <string name="hostpref_authagent_title">SSH 인증 에이전트 사용</string> + <string name="hostpref_postlogin_title">로그인 후 자동 실행</string> + <string name="hostpref_postlogin_summary">인증 후 원격 서버에서 실행될 명령어들</string> + <string name="hostpref_compression_title">압축</string> + <string name="hostpref_compression_summary">네트워크가 느린 경우 유용</string> + <string name="hostpref_wantsession_title">쉘 세션 시작</string> + <string name="hostpref_wantsession_summary">포트 포워드만 사용할 경우 이 설정을 해제 하세요</string> + <string name="hostpref_stayconnected_title">연결 유지</string> + <string name="hostpref_stayconnected_summary">연결이 끊기면 다시 연결을 시도</string> + <string name="hostpref_delkey_title">삭제 키</string> + <string name="hostpref_delkey_summary">삭제 키를 눌렀을 때 보낼 키 코드</string> + <string name="hostpref_encoding_title">인코딩</string> + <string name="hostpref_encoding_summary">호스트의 문자 인코딩</string> + <string name="hostpref_connection_category">접속 설정</string> + <string name="hostpref_username_title">사용자 이름</string> + <string name="hostpref_hostname_title">호스트</string> + <string name="hostpref_port_title">포트</string> + <string name="bind_never">연결된 적 없음</string> + <string name="bind_minutes">%1$s 분 전</string> + <string name="bind_hours">%1$s 시간 전</string> + <string name="bind_days">%1$s 일 전</string> + <string name="console_copy_done">클립 보드에 %1$d 바이트가 복사되었습니다.</string> + <string name="console_copy_start">터치 후 드래그 또는\n방향 키로\n복사 영역을 선택</string> + <string name="console_menu_close">닫기</string> + <string name="console_menu_copy">복사</string> + <string name="console_menu_paste">붙여넣기</string> + <string name="console_menu_portforwards">포트 포워드</string> + <string name="console_menu_resize">크기 고정</string> + <string name="console_menu_urlscan">URL 검색</string> + <string name="button_yes">예</string> + <string name="button_no">아니오</string> + <string name="portforward_local">로컬</string> + <string name="portforward_remote">원격</string> + <string name="portforward_dynamic">동적 (SOCKS)</string> + <string name="portforward_pos">포트 포워딩 만들기</string> + <string name="portforward_done">포트 포워딩을 만드는 데 성공했습니다.</string> + <string name="portforward_menu_add">포트 포워딩 추가</string> + <string name="list_format_error">%1$s 형식을 사용</string> + <string name="format_username">사용자 이름</string> + <string name="format_hostname">호스트 이름</string> + <string name="format_port">포트</string> + <string name="list_menu_pubkeys">개인키 관리자</string> + <string name="list_menu_sortcolor">색상순 정렬</string> + <string name="list_menu_sortname">이름순 정렬</string> + <string name="list_menu_settings">설정</string> + <string name="list_host_disconnect">연결끊기</string> + <string name="list_host_edit">호스트 편집</string> + <string name="list_host_portforwards">포트 포워딩 편집</string> + <string name="list_host_delete">호스트 삭제</string> + <string name="list_host_empty">아래의 빠른-연결 상자로\n호스트에 연결할 수 있습니다.</string> + <string name="list_rotation_default">기본</string> + <string name="list_rotation_land">가로 고정</string> + <string name="list_rotation_port">세로 고정</string> + <string name="list_rotation_auto">자동</string> + <string name="list_camera_ctrlaspace">Ctrl+A 후 Space</string> + <string name="list_camera_none">없음</string> + <string name="list_delkey_backspace">백스페이스</string> + <string name="list_delkey_del">삭제</string> + <string name="delete_pos">예, 삭제</string> + <string name="delete_neg">취소</string> + <string name="wizard_agree">동의</string> + <string name="wizard_next">다음</string> + <string name="wizard_back">뒤로</string> + <string name="terminal_no_hosts_connected">현재 연결된 호스트가 없음</string> + <string name="terminal_connecting">%3$s(으)로 %1$s:%2$d에 연결 중</string> + <string name="terminal_failed">호스트 키를 확인할 수 없습니다.</string> + <string name="terminal_using_s2c_algorithm">서버->클라이언트 알고리즘: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">클라이언트->서버 알고리즘: %1$s %2$s</string> + <string name="terminal_using_algorithm">사용중인 알고리즘: %1$s %2$s</string> + <string name="terminal_auth">인증 시도 중</string> + <string name="terminal_auth_pubkey_invalid">선택된 공개 키가 잘못되었습니다. 호스트 편집에서 키를 다시 선택해 보세요</string> + <string name="terminal_no_session">호스트 설정 동안에는 세션이 시작되지 않을 것 입니다.</string> + <string name="terminal_enable_portfoward">포트 포워딩 사용: %1$s</string> + <string name="local_shell_unavailable">실패! 이 폰은 로컬 쉘을 사용할 수 없습니다.</string> + <string name="notification_text">%1$s에서 알림이 있습니다.</string> + <string name="no">아니오</string> + <string name="with_confirmation">확인 작업 수행</string> + <string name="yes">예</string> + <string name="exceptions_submit_message">마지막으로 ConnectBot이 실행했을 때 문제가 있었던 것 같습니다. ConnectBot 개발자에 오류 보고서를 보내시겠습니까?</string> + <string name="menu_colors_reset">초기화</string> + <string name="app_is_running">ConnectBot 실행 중</string> + <string name="color_red">빨간색</string> + <string name="color_green">녹색</string> + <string name="color_blue">파란색</string> + <string name="color_gray">회색</string> +</resources> diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml new file mode 100644 index 0000000..7b61c88 --- /dev/null +++ b/app/src/main/res/values-lt/strings.xml @@ -0,0 +1,10 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="title_help">Pagalba</string> + <string name="title_colors">Spalvos</string> + <string name="resolve_connect">Prisijungti</string> + <string name="pubkey_list_empty">Tap Menu to create\nor import key pairs.</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> +</resources> diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml new file mode 100644 index 0000000..94edc23 --- /dev/null +++ b/app/src/main/res/values-lv/strings.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources/> diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml new file mode 100644 index 0000000..9be11ac --- /dev/null +++ b/app/src/main/res/values-mk/strings.xml @@ -0,0 +1,67 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Едноставен но моќен SSH клиент со отворен код.</string> + <string name="service_desc">Ги оддрѓува SSH конекциите и вчитаните pub клучеви</string> + <string name="title_hosts_list">Хостови</string> + <string name="title_pubkey_list">Pub клучеви</string> + <string name="title_host_editor">Промени хост</string> + <string name="title_help">Помош</string> + <string name="title_colors">Бои</string> + <string name="resolve_connect">Поврзи се</string> + <string name="menu_insert">Додај хост</string> + <string name="menu_delete">Одстрани хост</string> + <string name="menu_preferences">Параметри</string> + <string name="help_intro">Одбеи тема од подолу наведените за повеќе информации за таа тема.</string> + <string name="help_about">За ConnectBot</string> + <string name="help_keyboard">Тастатура</string> + <string name="pubkey_generate">Генерирај</string> + <string name="pubkey_import">Увези</string> + <string name="pubkey_delete">Одстрани клуч</string> + <string name="pubkey_generating">Се генерира пар на клучеви</string> + <string name="pubkey_copy_private">Копирај го приватниот клуч</string> + <string name="pubkey_copy_public">Копирај го јавниот клуч</string> + <string name="pubkey_list_empty">Притисни мени за да направиш\nили увезеш нов пар на клучеви</string> + <string name="pubkey_unknown_format">Непознат тип</string> + <string name="pubkey_change_password">Промени лозинка</string> + <string name="pubkey_list_pick">Превземи од /sdcard</string> + <string name="pubkey_import_parse_problem">Проблем со анализирањето на увезениот приватен клуч</string> + <string name="pubkey_unlock">Клуч за отклучување</string> + <string name="pubkey_failed_add">Погрешна лозинка за клучот \'%1$s\'. Неуспешна авторизација.</string> + <string name="pubkey_memory_load">Вчитај во меморија</string> + <string name="pubkey_memory_unload">Извади од меморијата</string> + <string name="pubkey_load_on_start">Вчитај клуч при вклучување</string> + <string name="pubkey_confirm_use">Побарај потврда пред користење</string> + <string name="prompt_nickname">Прекар:</string> + <string name="prompt_nickname_hint_pubkey">Мојот работен клуч</string> + <string name="prompt_source_port">Изворна порта:</string> + <string name="prompt_destination">Дестинација:</string> + <string name="prompt_old_password">Стара лозинка:</string> + <string name="prompt_password">Лозинка:</string> + <string name="prompt_again">(повторно)</string> + <string name="prompt_type">Тип:</string> + <string name="prompt_password_can_be_blank">Белешка: Лозинката не мора да се впише</string> + <string name="prompt_bits">Битови:</string> + <string name="prompt_pubkey_password">Лозинка за \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Дозволи му на одалечениот хост\nда го искористи \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">ПРЕДУПРЕДУВАЊЕ: ИДЕНТИФИКАЦИЈАТА НА ОДАЛЕЧЕНИОТ ХОСТ Е ПРОМЕНЕТА!</string> + <string name="host_verification_failure_warning">МОЖНО Е НЕКОЈ ДА ПРАВИ НЕШТО ЛОШО!\nНекој можеби ве прислушкува сега (напад човек во средина)!\nИсто така можно е да се променил клучот на одалечениот хост.</string> + <string name="prompt_host_disconnected">Хостот се дисконектираше.\nЗатвори ја сесијата?</string> + <string name="prompt_continue_connecting">Дали си сигурен дека\nсакаш да продолжиш со поврзување?</string> + <string name="host_authenticity_warning">Автентичноста на \'%1$s\' не може да биде потврдена.</string> + <string name="alert_passwords_do_not_match_msg">Лозинките не се совпаѓаат!</string> + <string name="alert_wrong_password_msg">Погрешна лозинка!</string> + <string name="alert_key_corrupted_msg">Приватниот клуч е корумпиран!</string> + <string name="alert_sdcard_absent">СД картичката не е ставена!</string> + <string name="button_add">Додај</string> + <string name="button_change">Промени</string> + <string name="button_generate">Направи клуч</string> + <string name="button_resize">Промени големина</string> + <string name="alert_disconnect_msg">Врската е изгубена</string> + <string name="pref_emulation_category">Емулација на терминал</string> + <string name="pref_emulation_title">Вид на емулација</string> + <string name="pref_emulation_summary">Вид на емулација на терминал да се користи за PTY врски</string> + <string name="pref_ui_category">Кориснички интерфејс</string> + <string name="pref_fullscreen_title">На цел екран</string> + <string name="pref_fullscreen_summary">Сокриј ја статусната трака додека си во конзола</string> + <string name="pref_memkeys_title">Запомни ги клучевите во меморија</string> +</resources> diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml new file mode 100644 index 0000000..81025dc --- /dev/null +++ b/app/src/main/res/values-nb/strings.xml @@ -0,0 +1,209 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Enkel og omfattende SSH-klient med åpen kildekode.</string> + <string name="service_desc">Opprettholder SSH-tilkoblinger og innlastede offentlige nøkler</string> + <string name="title_hosts_list">Verter</string> + <string name="title_pubkey_list">Offentlige nøkler</string> + <string name="title_port_forwards_list">Portvideresending</string> + <string name="title_host_editor">Rediger vert</string> + <string name="title_help">Hjelp</string> + <string name="title_colors">Farger</string> + <string name="resolve_connect">Koble til</string> + <string name="resolve_entropy">Samle entropi</string> + <string name="menu_insert">Legg til vert</string> + <string name="menu_delete">Slett vert</string> + <string name="menu_preferences">Innstillinger</string> + <string name="help_intro">Velg en overskrift nedenfor for mer informasjon om et bestemt emne.</string> + <string name="help_about">Om ConnectBot</string> + <string name="help_keyboard">Tastatur</string> + <string name="pubkey_generate">Generer</string> + <string name="pubkey_import">Importer</string> + <string name="pubkey_delete">Slett nøkkel</string> + <string name="pubkey_gather_entropy">Samler entropi</string> + <string name="pubkey_touch_prompt">Berør denne boksen for å samle tilfeldige tall: %1$d%% ferdig</string> + <string name="pubkey_touch_hint">Beveg fingeren over boksen nedenfor for å sikre vilkårlighet ved generering av nøkkel.</string> + <string name="pubkey_generating">Genererer nøkkelpar...</string> + <string name="pubkey_copy_private">Kopier privat nøkkel</string> + <string name="pubkey_copy_public">Kopier offentlig nøkkel</string> + <string name="pubkey_list_empty">Trykk \"Meny\" for å opprette\neller importere nøkkelpar.</string> + <string name="pubkey_unknown_format">Ukjent format</string> + <string name="pubkey_change_password">Endre passord</string> + <string name="pubkey_list_pick">Hent fra /sdcard</string> + <string name="pubkey_import_parse_problem">Feil ved innlasting av privat nøkkel</string> + <string name="pubkey_unlock">Frigjør nøkkel</string> + <string name="pubkey_failed_add">Feil passord for nøkkel \'%1$s\'. Autentisering mislyktes.</string> + <string name="pubkey_memory_load">Last inn i minne</string> + <string name="pubkey_memory_unload">Last ut av minne</string> + <string name="pubkey_load_on_start">Last nøkkel ved start</string> + <string name="pubkey_confirm_use">Godkjenn før bruk</string> + <string name="portforward_list_empty">Trykk \"Meny\" for å opprette\nportvideresending.</string> + <string name="portforward_edit">Rediger videresendte porter</string> + <string name="portforward_delete">Slett videresendte porter</string> + <string name="prompt_nickname">Kallenavn:</string> + <string name="prompt_nickname_hint_pubkey">Min arbeidsnøkkel</string> + <string name="prompt_source_port">Kildeport:</string> + <string name="prompt_destination">Mål:</string> + <string name="prompt_old_password">Tidligere passord:</string> + <string name="prompt_password">Passord:</string> + <string name="prompt_again">(en gang til)</string> + <string name="prompt_type">Type:</string> + <string name="prompt_password_can_be_blank">NB: passord kan stå tomt</string> + <string name="prompt_bits">Bits:</string> + <string name="prompt_pubkey_password">Passord for nøkkel \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Godta ekstern vert til\nå bruke nøkkel \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">ADVARSEL: EKSTERN VERT IDENTIFIKASJON HAR BLITT ENDRET!</string> + <string name="host_verification_failure_warning">DET KAN VÆRE UGLER I MOSEN!\nNoen kan lytte på deg akkurat nå (et mellommann-angrep)!\nMen det er også mulig at vertsnøkkelen har blitt endret.</string> + <string name="prompt_host_disconnected">Vert har koblet fra.\nLukk sesjon?</string> + <string name="prompt_continue_connecting">Er du sikker på at\ndu vil koble til?</string> + <string name="host_authenticity_warning">Kan ikke bekrefte integriteten til \'%1$s\'.</string> + <string name="host_fingerprint">Nøkkelfingeravtrykk til vert %1$s er %2$s</string> + <string name="alert_passwords_do_not_match_msg">Passordene er ikke like!</string> + <string name="alert_wrong_password_msg">Feil passord!</string> + <string name="alert_key_corrupted_msg">Privat nøkkel ser ut til å være korrupt!</string> + <string name="alert_sdcard_absent">SD-kortet er ikke satt inn!</string> + <string name="button_add">Legg til</string> + <string name="button_change">Endre</string> + <string name="button_generate">Generer nøkkel</string> + <string name="button_resize">Endre størrelse</string> + <string name="alert_disconnect_msg">Mistet tilkobling</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminalemulering</string> + <string name="pref_emulation_title">Etterligningsmodus</string> + <string name="pref_emulation_summary">Etterligningsmodus for terminal for PTY-tilkoblinger</string> + <string name="pref_scrollback_title">Husk skjermtekst</string> + <string name="pref_scrollback_summary">Mengde skjermtekst som skal beholdes i minnet for hvert konsollvindu</string> + <string name="pref_ui_category">Brukergrensesnitt</string> + <string name="pref_rotation_title">Rotasjonsretning</string> + <string name="pref_rotation_summary">Hvordan rotasjonsretning endres når tastatur skyves inn/ut</string> + <string name="pref_fullscreen_title">Full skjerm</string> + <string name="pref_fullscreen_summary">Gjem statuslinje mens du er i konsoll</string> + <string name="pref_memkeys_title">Lagre nøkler</string> + <string name="pref_memkeys_summary">Husk opplåste nøkler helt til programmet avsluttes</string> + <string name="pref_conn_persist_title">Oppretthold forbindelser</string> + <string name="pref_conn_persist_summary">Oppretthold forbindelser selv når programmet kjører i bakgrunnen</string> + <string name="pref_keymode_title">Hurtigtaster for mapper</string> + <string name="pref_keymode_summary">Velg hvordan Alt- og Skift-tasten skal brukes for \'/\' og Tab</string> + <string name="pref_camera_title">Snarvei til kamera</string> + <string name="pref_camera_summary">Velg hvilken snarvei som skal benyttes når kameraknapp trykkes</string> + <string name="pref_keepalive_title">Hold skjerm våken</string> + <string name="pref_keepalive_summary">Unngå at skjermen slås av når du jobber konsoll</string> + <string name="pref_wifilock_title">Hold Wi-Fi aktiv</string> + <string name="pref_wifilock_summary">Hindre Wi-Fi å koble fra når en økt er aktiv</string> + <string name="pref_bumpyarrows_title">Vibrerende piler</string> + <string name="pref_bumpyarrows_summary">Vibrer når pilkommandoer sendes med styrekule; nyttig for trege forbindelser</string> + <string name="pref_bell_category">Terminalklokke</string> + <string name="pref_bell_title">Varselklokke</string> + <string name="pref_bell_volume_title">Klokkevolum</string> + <string name="pref_bell_vibrate_title">Vibrere med bjelle</string> + <string name="pref_bell_notification_title">Bakgrunnsvarsling</string> + <string name="pref_bell_notification_summary">Vis varsling når en terminal i bakgrunnen gir et klokkevarsel.</string> + <string name="list_keymode_right">Bruk knapper på høyre side</string> + <string name="list_keymode_left">Bruk knapper på venstre side</string> + <string name="list_keymode_none">Deaktiver</string> + <string name="list_pubkeyids_none">Ikke bruk nøkler</string> + <string name="list_pubkeyids_any">Bruk en vilkårlig ulåst tast</string> + <string name="hostpref_nickname_title">Kallenavn</string> + <string name="hostpref_color_title">Fargeinndeling</string> + <string name="hostpref_fontsize_title">Skriftstørrelse (pt)</string> + <string name="hostpref_pubkeyid_title">Bruk offentlig nøkkel som godkjenning</string> + <string name="hostpref_authagent_title">Bruk SSH auth-agent</string> + <string name="hostpref_postlogin_title">Kommandoer etter innlogging</string> + <string name="hostpref_postlogin_summary">Kommandoer som kjøres på tilkoblet tjener etter vellykket autentisering</string> + <string name="hostpref_compression_title">Komprimering</string> + <string name="hostpref_compression_summary">Dette kan hjelpe på trege nettverk</string> + <string name="hostpref_wantsession_title">Start skallsesjon</string> + <string name="hostpref_wantsession_summary">Deaktiver denne innstillingen og benytt denne kun for portvideresending</string> + <string name="hostpref_stayconnected_title">Oppretthold forbindelse</string> + <string name="hostpref_stayconnected_summary">Forsøk å koble til på nytt ved frakobling</string> + <string name="hostpref_delkey_title">DEL-knapp</string> + <string name="hostpref_delkey_summary">Tastekode som sendes når DEL trykkes</string> + <string name="hostpref_encoding_title">Tegnkoding</string> + <string name="hostpref_encoding_summary">Tegnkoding for verten</string> + <string name="hostpref_connection_category">Tilkoblingsinnstillinger</string> + <string name="hostpref_username_title">Brukernavn</string> + <string name="hostpref_hostname_title">Vert</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Aldri tilkoblet</string> + <string name="bind_minutes">%1$s minutt siden</string> + <string name="bind_hours">%1$s timer siden</string> + <string name="bind_days">%1$s dager siden</string> + <string name="console_copy_done">Kopierte %1$d bytes til utklippstavle</string> + <string name="console_copy_start">Trykk og dra, eller\nbruk styrekule for å\nvelge område å\nkopiere fra</string> + <string name="console_menu_close">Lukk</string> + <string name="console_menu_copy">Kopier</string> + <string name="console_menu_paste">Lim inn</string> + <string name="console_menu_portforwards">Portvideresending</string> + <string name="console_menu_resize">Tving størrelse</string> + <string name="console_menu_urlscan">URL-søk</string> + <string name="button_yes">Ja</string> + <string name="button_no">Nei</string> + <string name="portforward_local">Lokal</string> + <string name="portforward_remote">Ekstern</string> + <string name="portforward_dynamic">Dynamisk (SOCKS)</string> + <string name="portforward_pos">Opprett portvideresending</string> + <string name="portforward_done">Vellykket oppretting av portvideresending</string> + <string name="portforward_problem">Mislyktes å opprette portvideresending. Bruker du porter under 1024, eller er denne porten allerede tatt?</string> + <string name="portforward_menu_add">Legg til portvideresending</string> + <string name="hint_userhost">bruker\@vert</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="format_username">brukernavn</string> + <string name="format_hostname">vertsnavn</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Behandle offentlige nøkler</string> + <string name="list_menu_sortcolor">Sorter etter farge</string> + <string name="list_menu_sortname">Sorter etter navn</string> + <string name="list_menu_settings">Innstillinger</string> + <string name="list_host_disconnect">Koble fra</string> + <string name="list_host_edit">Rediger vert</string> + <string name="list_host_portforwards">Rediger portvidersendinger</string> + <string name="list_host_delete">Slett vert</string> + <string name="list_host_empty">Bruk hurtigtilkoblingsboksen\nunder for å koble til en vert.</string> + <string name="list_rotation_default">Forvalg</string> + <string name="list_rotation_land">Tving landskap</string> + <string name="list_rotation_port">Tving portrett</string> + <string name="list_rotation_auto">Automatisk</string> + <string name="list_camera_ctrlaspace">Ctrl+A og mellomrom</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Ingen</string> + <string name="list_delkey_backspace">Tilbaketast</string> + <string name="list_delkey_del">Slett</string> + <string name="delete_message">Er du sikker på at du vil slette \'%1$s\'?</string> + <string name="delete_pos">Ja, slett</string> + <string name="delete_neg">Avbryt</string> + <string name="wizard_agree">Godta</string> + <string name="wizard_next">Neste</string> + <string name="wizard_back">Tilbake</string> + <string name="terminal_no_hosts_connected">Ingen verter tilkoblet for øyeblikket</string> + <string name="terminal_connecting">Kobler til %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Verifiserte vert \'%1$s\' sin nøkkel: %2$s</string> + <string name="terminal_failed">Verifisering av vertsnøkkelen feilet.</string> + <string name="terminal_using_s2c_algorithm">Algoritme fra tjener til klient: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algoritme fra klient til tjener: %1$s %2$s</string> + <string name="terminal_using_algorithm">Bruker algoritme: %1$s %2$s</string> + <string name="terminal_auth">Forsøker å autentisere</string> + <string name="terminal_auth_pass">Forsøker \'passord\'-autentisering</string> + <string name="terminal_auth_pass_fail">Autentiseringsmetode \'passord\' feilet</string> + <string name="terminal_auth_pubkey_any">Forsøker \'publickey\'-autentisering med lagrede offentlige nøkler</string> + <string name="terminal_auth_pubkey_invalid">Valgt offentlig nøkkel er ugyldig, forsøk å velge en annen nøkkel i vertsvinduet.</string> + <string name="terminal_auth_pubkey_specific">Forsøker \'publickey\'-autentisering med angitt offentlig nøkkel</string> + <string name="terminal_auth_pubkey_fail">Autentisering med \'publickey\' og nøkkel \'%1$s\' mislyktes</string> + <string name="terminal_auth_ki">Forsøker \'keyboard-interactive\' autentisering</string> + <string name="terminal_auth_ki_fail">Autentiseringsmetoden \'keyboard-interactive\' mislyktes</string> + <string name="terminal_auth_fail">[Din vert støtter ikke autentiseringsmetodene \'password\' eller \'keyboard-interactive\'.]</string> + <string name="terminal_no_session">Sesjonen vil ikke bli startet grunnet vertsinnstillinger.</string> + <string name="terminal_enable_portfoward">Aktiver portvideresending: %1$s</string> + <string name="local_shell_unavailable">Feil! Lokalt skall er utilgjengelig på denne telefonen.</string> + <string name="notification_text">%1$s ønsker din oppmerksomhet.</string> + <string name="no">Nei</string> + <string name="with_confirmation">Med bekreftelse</string> + <string name="yes">Ja</string> + <string name="exceptions_submit_message">Det ser ut som ConnectBot hadde et problem forrige gang den kjørte. Vil du sende en feilrapport til utviklerne av ConnectBot?</string> + <string name="menu_colors_reset">Tilbakestill</string> + <string name="app_is_running">ConnectBot kjører</string> + <string name="color_red">rød</string> + <string name="color_green">grønn</string> + <string name="color_blue">blå</string> + <string name="color_gray">grå</string> +</resources> diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000..7d3af43 --- /dev/null +++ b/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,209 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Simpele, krachtige, open-source SSH client.</string> + <string name="service_desc">Onderhoud SSH verbindingen en geladen pubkeys</string> + <string name="title_hosts_list">Hosts</string> + <string name="title_pubkey_list">Pubkeys</string> + <string name="title_port_forwards_list">Port forwards</string> + <string name="title_host_editor">Host bewerken</string> + <string name="title_help">Help</string> + <string name="title_colors">Kleuren</string> + <string name="resolve_connect">Verbinden</string> + <string name="resolve_entropy">Verzamel Entropie</string> + <string name="menu_insert">Host Toevoegen</string> + <string name="menu_delete">Host verwijderen</string> + <string name="menu_preferences">Voorkeuren</string> + <string name="help_intro">Selecteer hieronder een onderwerp voor meer informatie over een onderwerp.</string> + <string name="help_about">Over ConnectBot</string> + <string name="help_keyboard">Toetsenbord</string> + <string name="pubkey_generate">Genereren</string> + <string name="pubkey_import">Importeren</string> + <string name="pubkey_delete">Sleutel verwijderen</string> + <string name="pubkey_gather_entropy">Bezig met het verzamelen van Entropie</string> + <string name="pubkey_touch_prompt">Raak dit vierkant aan om willekeurigheid te verzamelen: %1$d%% gedaan</string> + <string name="pubkey_touch_hint">Beweeg je vinger in willekeurige volgorde over het vak beneden om er voor te zorgen dat de invoer willekeurig blijft.</string> + <string name="pubkey_generating">Genereren van een sleutel paar...</string> + <string name="pubkey_copy_private">Kopieer privé sleutel</string> + <string name="pubkey_copy_public">Kopieer publieke sleutel</string> + <string name="pubkey_list_empty">Tik op Menu om te maken\nof importeer sleutel paren.</string> + <string name="pubkey_unknown_format">Onbekend formaat</string> + <string name="pubkey_change_password">Verander wachtwoord</string> + <string name="pubkey_list_pick">Kies van /sdcard</string> + <string name="pubkey_import_parse_problem">Probleem met het parsen van geimporteerde privé sleutel</string> + <string name="pubkey_unlock">Ontgrendel sleutel</string> + <string name="pubkey_failed_add">Slecht wachtwoord voor sleutel \'%1$s\'. Authenticatie mislukt</string> + <string name="pubkey_memory_load">Laad in geheugen</string> + <string name="pubkey_memory_unload">Ontlaad uit geheugen</string> + <string name="pubkey_load_on_start">Laad sleutel op start</string> + <string name="pubkey_confirm_use">Bevestig voor gebruik</string> + <string name="portforward_list_empty">Tik Menu om poort\nforwards te maken.</string> + <string name="portforward_edit">Bewerk port forward</string> + <string name="portforward_delete">Verwijder port forward</string> + <string name="prompt_nickname">Bijnaam:</string> + <string name="prompt_nickname_hint_pubkey">Mijn werk sleutel</string> + <string name="prompt_source_port">Bron poort:</string> + <string name="prompt_destination">Bestemming:</string> + <string name="prompt_old_password">Oud wachtwoord:</string> + <string name="prompt_password">Wachtwoord:</string> + <string name="prompt_again">(opnieuw)</string> + <string name="prompt_type">Type:</string> + <string name="prompt_password_can_be_blank">Opmerking: wachtwoord kan leeg zijn</string> + <string name="prompt_bits">Bits:</string> + <string name="prompt_pubkey_password">Wachtwoord voor sleutel \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Sta host toe om \'%1$s\' \nsleutel te gebruiken ?</string> + <string name="host_verification_failure_warning_header">WAARSCHUWING: REMOTE HOST IDENTIFICATIE IS VERANDERD!</string> + <string name="host_verification_failure_warning">HET IS MOGELIJK DAT IEMAND ONGEWILD BEZIG IS!\nIemand kan nu misschien mee kijken met wat je aan het doen bent (man-in-the-middle attack)!\nHet is ook mogelijk dat de host sleutel veranderd is.</string> + <string name="prompt_host_disconnected">Host heeft de verbinding verbroken.\nSessie afsluiten?</string> + <string name="prompt_continue_connecting">Weet je zeker dat je\nwilt doorgaan met verbinden?</string> + <string name="host_authenticity_warning">De authenticatie van host \'%1$s\' kan niet worden vastgesteld.</string> + <string name="host_fingerprint">Host %1$s sleutel vingerafdruk is %2$s</string> + <string name="alert_passwords_do_not_match_msg">Wachtwoorden zijn niet hetzelfde!</string> + <string name="alert_wrong_password_msg">Verkeerd wachtwoord!</string> + <string name="alert_key_corrupted_msg">Privé sleutel lijkt corrupt!</string> + <string name="alert_sdcard_absent">SD kaart niet aanwezig!</string> + <string name="button_add">Voeg toe</string> + <string name="button_change">Verander</string> + <string name="button_generate">Genereer sleutel</string> + <string name="button_resize">Verander formaat</string> + <string name="alert_disconnect_msg">Verbinding verloren</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminal emulatie</string> + <string name="pref_emulation_title">Emulatie mode</string> + <string name="pref_emulation_summary">Terminal emulatie mode voor gebruik in PTY verbindingen</string> + <string name="pref_scrollback_title">Scrollback grootte</string> + <string name="pref_scrollback_summary">Grootte van scrollback buffer om in geheugen te houden voor elke console</string> + <string name="pref_ui_category">Gebruikers interface</string> + <string name="pref_rotation_title">Rotatie mode</string> + <string name="pref_rotation_summary">Hoe rotatie te veranderen wanneer keyboard in/uit geschoven is</string> + <string name="pref_fullscreen_title">Volledig scherm</string> + <string name="pref_fullscreen_summary">Verberg status balk terwijl in console</string> + <string name="pref_memkeys_title">Onthoud sleutels in geheugen</string> + <string name="pref_memkeys_summary">Houd ontgrendelde sleutels in geheugen tot achtergrond service is gestopt</string> + <string name="pref_conn_persist_title">Behoud verbindingen</string> + <string name="pref_conn_persist_summary">Forceer dat de verbindingen actief blijven op de achtergrond</string> + <string name="pref_keymode_title">Folder snelkoppelingen</string> + <string name="pref_keymode_summary">Selecteer hoe Alt voor \'/\' en Shift voor Tab te gebruiken</string> + <string name="pref_camera_title">Camera snelkoppeling</string> + <string name="pref_camera_summary">Selecteer welke snelkoppeling te activeren wanneer camera knop is ingedrukt</string> + <string name="pref_keepalive_title">Houd scherm wakker</string> + <string name="pref_keepalive_summary">Voorkom scherm van uitgaan wanneer er gewerkt word in een console</string> + <string name="pref_wifilock_title">Houd Wi-Fi actief</string> + <string name="pref_wifilock_summary">Voorkom Wi-Fi van uitgaan wanneer er een sessie actief is</string> + <string name="pref_bumpyarrows_title">Hobbelige pijlen</string> + <string name="pref_bumpyarrows_summary">Tril als pijl knoppen worden gestuurd van trackball; handig voor langzame verbindingen</string> + <string name="pref_bell_category">Terminal bel</string> + <string name="pref_bell_title">Hoorbare bel</string> + <string name="pref_bell_volume_title">Bel volume</string> + <string name="pref_bell_vibrate_title">Tril bij bel</string> + <string name="pref_bell_notification_title">Achtergrond notificaties</string> + <string name="pref_bell_notification_summary">Stuur notificatie als terminal in de achtergrond een bel laat klinken.</string> + <string name="list_keymode_right">Gebruik rechter kant sleutels</string> + <string name="list_keymode_left">Gebruik linker kant sleutels</string> + <string name="list_keymode_none">Schakel uit</string> + <string name="list_pubkeyids_none">Gebruik sleutels niet</string> + <string name="list_pubkeyids_any">Gebruik een willekeurige ontgrendelde sleutel</string> + <string name="hostpref_nickname_title">Bijnaam</string> + <string name="hostpref_color_title">Kleur catogorie</string> + <string name="hostpref_fontsize_title">Lettergrootte</string> + <string name="hostpref_pubkeyid_title">Gebruik pubkey authenticatie</string> + <string name="hostpref_authagent_title">Gebruik SSH auth agent</string> + <string name="hostpref_postlogin_title">Na-login automatie</string> + <string name="hostpref_postlogin_summary">Commando\'s om uit te voeren op de remote server wanneer geauthenticeerd</string> + <string name="hostpref_compression_title">Compressie</string> + <string name="hostpref_compression_summary">Dit kan helpen bij langzame netwerken</string> + <string name="hostpref_wantsession_title">Start shell sessie</string> + <string name="hostpref_wantsession_summary">Schakel dit uit om alleen port forwards te gebruiken</string> + <string name="hostpref_stayconnected_title">Blijf verbonden</string> + <string name="hostpref_stayconnected_summary">Verbinding automatisch proberen te herstellen</string> + <string name="hostpref_delkey_title">Del-toets</string> + <string name="hostpref_delkey_summary">De toetscode als de DEL-knop in wordt gedrukt</string> + <string name="hostpref_encoding_title">Codering</string> + <string name="hostpref_encoding_summary">Tekencodering voor de host</string> + <string name="hostpref_connection_category">Verbindings instellingen</string> + <string name="hostpref_username_title">Gebruikersnaam</string> + <string name="hostpref_hostname_title">Host</string> + <string name="hostpref_port_title">Poort</string> + <string name="bind_never">Nooit verbonden</string> + <string name="bind_minutes">%1$s minuten geleden</string> + <string name="bind_hours">%1$s uren geleden</string> + <string name="bind_days">%1$s dagen geleden</string> + <string name="console_copy_done">%1$d bytes gekopieerd naar het klembord</string> + <string name="console_copy_start">Tik en sleep\nof gebruik directioneel pad\nom velden te selecteren omte kopiëren</string> + <string name="console_menu_close">Sluit</string> + <string name="console_menu_copy">Kopieer</string> + <string name="console_menu_paste">Plak</string> + <string name="console_menu_portforwards">Port Forwards</string> + <string name="console_menu_resize">Forceer grootte</string> + <string name="console_menu_urlscan">URK Scan</string> + <string name="button_yes">Ja</string> + <string name="button_no">Nee</string> + <string name="portforward_local">Lokaal</string> + <string name="portforward_remote">Op afstand</string> + <string name="portforward_dynamic">Dynamisch (SOCKS)</string> + <string name="portforward_pos">Maak port forward</string> + <string name="portforward_done">Succesvol port forward gemaakt</string> + <string name="portforward_problem">Probleem met het maken van een port forward, misschien gebruik je poort onder 1024 of is de poort al gebruikt?</string> + <string name="portforward_menu_add">Voeg port forward toe</string> + <string name="hint_userhost">gebruiker\@hostnaam</string> + <string name="list_format_error">Gebruik het formaat %1$s</string> + <string name="format_username">gebruiker</string> + <string name="format_hostname">hostnaam</string> + <string name="format_port">poort</string> + <string name="list_menu_pubkeys">Beheer Pubkeys</string> + <string name="list_menu_sortcolor">Sorteer op kleur</string> + <string name="list_menu_sortname">Sorteer op naam</string> + <string name="list_menu_settings">Instellingen</string> + <string name="list_host_disconnect">Verbreek verbinding</string> + <string name="list_host_edit">Bewerk host</string> + <string name="list_host_portforwards">Bewerk port forwards</string> + <string name="list_host_delete">Verwijder hosts</string> + <string name="list_host_empty">Gebruik het snel-verbindings vierkant\nonder om een host te verbinden.</string> + <string name="list_rotation_default">Standaard</string> + <string name="list_rotation_land">Forceer landschap</string> + <string name="list_rotation_port">Forceer portret</string> + <string name="list_rotation_auto">Automatisch</string> + <string name="list_camera_ctrlaspace">Ctrl+A en dan Spatie</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Geen</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Verwijderen</string> + <string name="delete_message">Weet je zeker dat je \'%1$s\' wilt verwijderen?</string> + <string name="delete_pos">Ja, verwijder</string> + <string name="delete_neg">Annuleren</string> + <string name="wizard_agree">Ga akkoord</string> + <string name="wizard_next">Volgende</string> + <string name="wizard_back">Terug</string> + <string name="terminal_no_hosts_connected">Momenteel geen hosts verbonden</string> + <string name="terminal_connecting">Bezig met verbinden naar %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Gecontroleerde host \'%1$s\' sleutel: %2$s</string> + <string name="terminal_failed">Host sleutel verificatie mislukt</string> + <string name="terminal_using_s2c_algorithm">Server-naar-cliënt algoritme: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Cliënt-naar-server algoritme: %1$s %2$s</string> + <string name="terminal_using_algorithm">Gebruikt algoritme: %1$s %2$s</string> + <string name="terminal_auth">Probeert te authenticeren</string> + <string name="terminal_auth_pass">Probeert \'wachtwoord\' authenticatie</string> + <string name="terminal_auth_pass_fail">Authenticatie methode \'wachtwoord\' mislukt</string> + <string name="terminal_auth_pubkey_any">Probeert \'publieke sleutel\' authenticatie met een willekeurige in-geheugen publieke sleutel</string> + <string name="terminal_auth_pubkey_invalid">Geselecteerde publieke sleutel is ongeldig, probeer herselecteren van de sleutel in de host bewerker</string> + <string name="terminal_auth_pubkey_specific">Probeert \'publickey\' authenticatie met een specifieke publieke sleutel</string> + <string name="terminal_auth_pubkey_fail">Authenticatie methode \'publickey\' met sleutel \'%1$s\' gefaald</string> + <string name="terminal_auth_ki">Probeert \'keyboard-interactive\' authenticatie</string> + <string name="terminal_auth_ki_fail">Authenticatie methode \'keyboard-interactive\' mislukt</string> + <string name="terminal_auth_fail">[Uw host ondersteund \'wachtwoord\' of \'keyboard-interactive\' authenticatie niet.]</string> + <string name="terminal_no_session">Sessie zal niet gestart worden door host instelling</string> + <string name="terminal_enable_portfoward">Activeer port forward: %1$s</string> + <string name="local_shell_unavailable">Mislukking! Lokale shell is niet beschikbaar op deze telefoon.</string> + <string name="notification_text">%1$s wil je aandacht.</string> + <string name="no">Nee</string> + <string name="with_confirmation">Met toestemming</string> + <string name="yes">Ja</string> + <string name="exceptions_submit_message">Het lijkt erop dat ConnectBot een probleem had de laatste keer dat het gebruikt werd. Wilt u een foutenrapport naar de ConnectBot ontwikkelaars sturen?</string> + <string name="menu_colors_reset">Terug naar standaardinstellingen</string> + <string name="app_is_running">ConnectBot werkt (Connecties zijn actief)</string> + <string name="color_red">rood</string> + <string name="color_green">groen</string> + <string name="color_blue">blauw</string> + <string name="color_gray">grijs</string> +</resources> diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml new file mode 100644 index 0000000..9dcad52 --- /dev/null +++ b/app/src/main/res/values-oc/strings.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_desc">Un client SSH simple, open-source e poderós.</string> + <string name="title_hosts_list">Òstes</string> + <string name="title_pubkey_list">Claus publicas</string> + <string name="title_port_forwards_list">Redireccions de pòrts</string> + <string name="title_host_editor">Modificar lo servidor</string> + <string name="title_help">Ajuda</string> + <string name="resolve_connect">Connexion</string> + <string name="resolve_entropy">Generacion d\'aleatòri</string> + <string name="menu_insert">Apondre un servidor</string> + <string name="menu_delete">Supprimir lo servidor</string> + <string name="menu_preferences">Preferéncias</string> + <string name="help_intro">Seleccionatz un subjècte çaijós per mai d\'entresenhas.</string> + <string name="help_about">A prepaus de ConnectBot</string> + <string name="help_keyboard">Clavièr</string> + <string name="pubkey_generate">Generar</string> + <string name="pubkey_import">Importar</string> + <string name="pubkey_delete">Suprimir la clau</string> + <string name="pubkey_gather_entropy">Generacion d\'aleatòri</string> + <string name="pubkey_touch_prompt">Tocatz aquesta bóstia per recuperar un nombre aleatòri : %1$d%% fach</string> + <string name="pubkey_copy_private">Copiar la clau privada</string> + <string name="pubkey_copy_public">Copiar la clau publica</string> + <string name="pubkey_list_empty">Tap Menu to create\nor import key pairs.</string> + <string name="pubkey_unknown_format">Format desconegut</string> + <string name="pubkey_change_password">Modificar lo senhal</string> + <string name="pubkey_list_pick">Recuperar dempuèi /sdcard</string> + <string name="pubkey_confirm_use">Confirmar abans utilizacion</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="prompt_nickname">Escais :</string> + <string name="prompt_nickname_hint_pubkey">Ma clau de trabalh</string> + <string name="prompt_destination">Destinacion :</string> + <string name="prompt_old_password">Senhal ancian :</string> + <string name="prompt_password">Senhal :</string> + <string name="prompt_again">(encara)</string> + <string name="prompt_type">Tipe :</string> + <string name="prompt_bits">Bits :</string> + <string name="alert_wrong_password_msg">Marrit senhal</string> + <string name="button_add">Apondre</string> + <string name="button_change">Cambiar</string> + <string name="button_generate">Generar una clau</string> + <string name="button_resize">Redimensionar</string> + <string name="alert_disconnect_msg">Connexion perduda</string> + <string name="pref_ui_category">Interfàcia d\'utilizaire</string> + <string name="pref_fullscreen_title">Ecran complet</string> + <string name="pref_conn_persist_title">Connexions persistentas</string> + <string name="pref_keymode_title">Acorchis de navigacion</string> + <string name="list_keymode_none">Desactivar</string> + <string name="hostpref_nickname_title">Escais</string> + <string name="hostpref_encoding_title">Encodatge</string> + <string name="hostpref_connection_category">Paramètre de connexion</string> + <string name="hostpref_username_title">Nom d\'utilizaire</string> + <string name="hostpref_hostname_title">Òste</string> + <string name="hostpref_port_title">Pòrt</string> + <string name="console_menu_close">Tampar</string> + <string name="console_menu_copy">Copiar</string> + <string name="console_menu_paste">Empegar</string> + <string name="button_yes">Òc</string> + <string name="button_no">Non</string> + <string name="portforward_remote">Distant</string> + <string name="portforward_dynamic">Dinamic (SOCKS)</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="format_username">nom d\'utilizaire</string> + <string name="format_hostname">nom d\'òste</string> + <string name="list_menu_sortname">Triar per nom</string> + <string name="list_menu_settings">Paramètres</string> + <string name="list_host_disconnect">Desconnectar</string> + <string name="list_rotation_default">Per defaut</string> + <string name="list_camera_esc">Escap</string> + <string name="list_camera_none">Pas cap</string> + <string name="list_delkey_backspace">Retorn enrèire</string> + <string name="list_delkey_del">Suprimir</string> + <string name="delete_neg">Anullar</string> + <string name="wizard_agree">Acceptar</string> + <string name="wizard_next">Seguent</string> + <string name="wizard_back">Precedent</string> + <string name="terminal_connecting">Connexion a %1$s:%2$d via %3$s</string> + <string name="no">Non</string> + <string name="with_confirmation">Aprèp confirmacion</string> + <string name="yes">Òc</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> + <string name="menu_colors_reset">Reïnicializar</string> + <string name="color_red">roge</string> + <string name="color_green">verd</string> + <string name="color_blue">blau</string> + <string name="color_gray">gris</string> +</resources> diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000..7f320b0 --- /dev/null +++ b/app/src/main/res/values-pl/strings.xml @@ -0,0 +1,218 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Prosty, wszechstronny i otwarty klient SSH</string> + <string name="service_desc">Zarządza połączeniami SSH i załadowanymi kluczami publicznymi</string> + <string name="title_hosts_list">Hosty</string> + <string name="title_pubkey_list">Klucze publiczne</string> + <string name="title_port_forwards_list">Przekierowania portów</string> + <string name="title_host_editor">Edytuj host</string> + <string name="title_help">Pomoc</string> + <string name="title_colors">Kolory</string> + <string name="title_color_picker">Wybór koloru</string> + <string name="resolve_connect">Połącz</string> + <string name="resolve_entropy">Zbierz dane losowe</string> + <string name="menu_insert">Dodaj host</string> + <string name="menu_delete">Usuń host</string> + <string name="menu_preferences">Ustawienia</string> + <string name="help_intro">Proszę wybrać temat poniżej aby uzyskać więcej informacji na konkretny temat.</string> + <string name="help_about">O ConnectBot</string> + <string name="help_keyboard">Klawiatura</string> + <string name="pubkey_generate">Wygeneruj</string> + <string name="pubkey_import">Importuj</string> + <string name="pubkey_delete">Usuń klucz</string> + <string name="pubkey_gather_entropy">Zbieranie danych losowych</string> + <string name="pubkey_touch_prompt">Dotknij to pole, żeby losowo wygenerować dane. Ukończono: %1$d%%</string> + <string name="pubkey_touch_hint">Dla niepowtarzalności klucza, poruszaj losowo palcem po polu poniżej.</string> + <string name="pubkey_generating">Generowanie pary kluczy...</string> + <string name="pubkey_copy_private">Kopiuj klucz prywatny</string> + <string name="pubkey_copy_public">Kopiuj klucz publiczny</string> + <string name="pubkey_list_empty">Naciśnij \"Menu\", by utworzyć\nlub zaimportować parę kluczy.</string> + <string name="pubkey_unknown_format">Nieznany format</string> + <string name="pubkey_change_password">Zmień hasło</string> + <string name="pubkey_list_pick">Wybierz z karty SD</string> + <string name="pubkey_import_parse_problem">Niewłaściwy format importowanego klucza prywatnego</string> + <string name="pubkey_unlock">Odblokuj klucz</string> + <string name="pubkey_failed_add">Nieprawidłowe hasło dla klucza \'%1$s\'. Uwierzytelnienie nie powiodło się.</string> + <string name="pubkey_memory_load">Załaduj do pamięci</string> + <string name="pubkey_memory_unload">Usuń z pamięci</string> + <string name="pubkey_load_on_start">Załaduj klucz przy uruchamianiu</string> + <string name="pubkey_confirm_use">Potwierdź przed użyciem</string> + <string name="portforward_list_empty">Naciśnij \"Menu\",\nżeby stworzyć przekierowanie portu.</string> + <string name="portforward_edit">Edytuj przekierowanie portu</string> + <string name="portforward_delete">Usuń przekierowanie portu</string> + <string name="prompt_nickname">Skrócona nazwa:</string> + <string name="prompt_nickname_hint_pubkey">Klucz służbowy</string> + <string name="prompt_source_port">Port żródłowy</string> + <string name="prompt_destination">Host docelowy:</string> + <string name="prompt_old_password">Stare hasło:</string> + <string name="prompt_password">Hasło:</string> + <string name="prompt_again">(ponownie)</string> + <string name="prompt_type">Typ:</string> + <string name="prompt_password_can_be_blank">Uwaga: hasło może być puste</string> + <string name="prompt_bits">Bity:</string> + <string name="prompt_pubkey_password">Proszę podać hasło dla klucza \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Zezwolić zdalnemu hostowi na\nużycie klucza \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">OSTRZEŻENIE: DANE IDENTYFIKACYJNE ZDALNEGO HOSTU ZMIENIŁY SIĘ!</string> + <string name="host_verification_failure_warning">MOŻLIWE, ŻE STAŁEŚ SIĘ OFIARĄ ATAKU!\nPrawdopodobnie jest to atak: man-in-the-middle!\nJednak możliwe jest to, że zmienił się tylko klucz hostu.</string> + <string name="prompt_host_disconnected">Host rozłączył się.\nZamknąć sesję?</string> + <string name="prompt_continue_connecting">Czy na pewno chcesz\nkontynuować połączenie?</string> + <string name="host_authenticity_warning">Autentyczność hostu \'%1$s\' nie może zostać ustalona.</string> + <string name="host_fingerprint">Odcisk hostu %1$s to %2$s</string> + <string name="alert_passwords_do_not_match_msg">Hasła nie zgadzają się!</string> + <string name="alert_wrong_password_msg">Nieprawidłowe hasło!</string> + <string name="alert_key_corrupted_msg">Klucz prywatny wydaje się być uszkodzony!</string> + <string name="alert_sdcard_absent">Brak karty SD</string> + <string name="button_add">Dodaj</string> + <string name="button_change">Zmień</string> + <string name="button_generate">Generuj klucz</string> + <string name="button_resize">Zmień rozmiar</string> + <string name="alert_disconnect_msg">Połączenie utracone</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulacja terminalu</string> + <string name="pref_emulation_title">Tryb emulacji</string> + <string name="pref_emulation_summary">Emulacja terminalu dla połączeń PTY</string> + <string name="pref_scrollback_title">Rozmiar buforu przewijania</string> + <string name="pref_scrollback_summary">Rozmiar pamięci dla buforu przewijania dla każdej konsoli</string> + <string name="pref_ui_category">Interfejs użytkownika</string> + <string name="pref_rotation_title">Sposób orientacji ekranu</string> + <string name="pref_rotation_summary">Sposób orientacji ekranu przy wysuniętej/wsuniętej klawiaturze</string> + <string name="pref_fullscreen_title">Pełny ekran</string> + <string name="pref_fullscreen_summary">Ukryj pasek statusu podczas pracy w konsoli</string> + <string name="pref_keyboard_category">Klawiatura</string> + <string name="pref_memkeys_title">Zapamiętaj klucze w pamięci</string> + <string name="pref_memkeys_summary">Trzymaj odblokowane klucze w pamięci dopóki usługa się nie rozłączy</string> + <string name="pref_conn_persist_title">Utrzymuj połącznia</string> + <string name="pref_conn_persist_summary">Wymuś utrzymanie połączeń, gdy aplikacja działa w tle</string> + <string name="pref_keymode_title">Skróty klawiaturowe</string> + <string name="pref_keymode_summary">Wybierz, którego Alt, Shift używać dla \'/\' oraz \'Tab\'</string> + <string name="pref_camera_title">Klawisz aparatu</string> + <string name="pref_camera_summary">Wybierz jaki skrót ma być wywoływany po wciśnięciu klawisza aparatu</string> + <string name="pref_keepalive_title">Wstrzymaj uśpienie ekranu</string> + <string name="pref_keepalive_summary">Wstrzymaj uśpienie ekranu kiedy konsola jest aktywna</string> + <string name="pref_wifilock_title">Wstrzymaj usypianie Wi-Fi</string> + <string name="pref_wifilock_summary">Wstrzymaj usypianie Wi-Fi gdy sesja jest aktywna</string> + <string name="pref_bumpyarrows_title">Podskakujące strzałki</string> + <string name="pref_bumpyarrows_summary">Wibruj kiedy emulowane są klawisze strzałek; użyteczne dla wolnych połączeń</string> + <string name="pref_bell_category">Brzęczyk terminalu</string> + <string name="pref_bell_title">Brzęczyk słyszalny</string> + <string name="pref_bell_volume_title">Głośność brzęczyka</string> + <string name="pref_bell_vibrate_title">Włącz wibracje</string> + <string name="pref_bell_notification_title">Powiadomienia w tle</string> + <string name="pref_bell_notification_summary">Uruchom powiadomienie podczas akcji w zminimalizowanej aplikacji.</string> + <string name="list_keymode_right">Używaj klawiszy z prawej strony</string> + <string name="list_keymode_left">Używaj klawiszy z lewej strony</string> + <string name="list_keymode_none">Wyłącz</string> + <string name="list_pubkeyids_none">Nie używaj kluczy</string> + <string name="list_pubkeyids_any">Użyj dowolnego odblokowanego klucza</string> + <string name="hostpref_nickname_title">Nazwa skrócona</string> + <string name="hostpref_color_title">Kolor kategorii</string> + <string name="hostpref_fontsize_title">Rozmiar czcionki</string> + <string name="hostpref_pubkeyid_title">Używaj kluczy publicznych do uwierzytelnienia</string> + <string name="hostpref_authagent_title">Używaj agenta SSH dla uwierzytelniania</string> + <string name="hostpref_postlogin_title">Wykonaj po zalogowaniu</string> + <string name="hostpref_postlogin_summary">Komendy do wykonania po stronie serwera po zalogowaniu</string> + <string name="hostpref_compression_title">Kompresja</string> + <string name="hostpref_compression_summary">Może pomóc przy wolniejszych łączach</string> + <string name="hostpref_wantsession_title">Uruchom sesję powłoki</string> + <string name="hostpref_wantsession_summary">Wyłącz jeśli chcesz tylko przekierować porty</string> + <string name="hostpref_stayconnected_title">Pozostań połączony</string> + <string name="hostpref_stayconnected_summary">Spróbuj połączyć się z hostem ponownie po przerwaniu połączenia</string> + <string name="hostpref_delkey_title">Klawisz DEL</string> + <string name="hostpref_delkey_summary">Kod klawisza wysyłany przy wciśnięciu DEL</string> + <string name="hostpref_encoding_title">Kodowanie</string> + <string name="hostpref_encoding_summary">Kodowanie znaków dla hostu</string> + <string name="hostpref_connection_category">Ustawienia połączenia</string> + <string name="hostpref_username_title">Nazwa użytkownika</string> + <string name="hostpref_hostname_title">Host</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Nigdy nie połączono</string> + <string name="bind_minutes">%1$s minut temu</string> + <string name="bind_hours">%1$s godzin temu</string> + <string name="bind_days">%1$s dni temu</string> + <string name="console_copy_done">Skopiowano %1$d bajtów do schowka</string> + <string name="console_copy_start">Dotknij i przeciągnij\nlub użyj trackball\'a\naby zaznaczyć obszar do skopiowania</string> + <string name="console_menu_close">Zamknij</string> + <string name="console_menu_copy">Kopiuj</string> + <string name="console_menu_paste">Wklej</string> + <string name="console_menu_portforwards">Przekierowania portów</string> + <string name="console_menu_resize">Wymuś rozmiar</string> + <string name="console_menu_urlscan">Szukaj URLi</string> + <string name="button_yes">Tak</string> + <string name="button_no">Nie</string> + <string name="portforward_local">Lokalny</string> + <string name="portforward_remote">Zdalny</string> + <string name="portforward_dynamic">Dynamiczny (SOCKS)</string> + <string name="portforward_pos">Utwórz przekierowanie portu</string> + <string name="portforward_done">Pomyślnie utworzono przekierowanie portu</string> + <string name="portforward_problem">Wystąpił problem podczas tworzenia przekierowania, być może użyłeś portu niższego niż 1024 lub port jest już używany?</string> + <string name="portforward_menu_add">Dodaj przekierowanie portu</string> + <string name="hint_userhost">użytkownik\@host</string> + <string name="list_format_error">Proszę użyć formatu %1$s</string> + <string name="format_username">Nazwa użytkownika</string> + <string name="format_hostname">nazwa_hostu</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Zarządzaj kluczami publicznymi</string> + <string name="list_menu_sortcolor">Sortuj według koloru</string> + <string name="list_menu_sortname">Sortuj według nazwy</string> + <string name="list_menu_settings">Ustawienia</string> + <string name="list_host_disconnect">Rozłącz</string> + <string name="list_host_edit">Edytuj host</string> + <string name="list_host_portforwards">Edytuj przekierowania portów</string> + <string name="list_host_delete">Usuń host</string> + <string name="list_host_empty">Użyj pola szybkiego połączenia\nponiżej, żeby połączyć się z hostem.</string> + <string name="list_rotation_default">Domyślne</string> + <string name="list_rotation_land">Wymuś orientację poziomą</string> + <string name="list_rotation_port">Wymuś orientację pionową</string> + <string name="list_rotation_auto">Automatycznie</string> + <string name="list_camera_ctrlaspace">Ctrl+A następnie Spacja</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Brak akcji</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Usuń</string> + <string name="delete_message">Jesteś pewien, że chcesz usunąć \'%1$s\'?</string> + <string name="delete_pos">Tak, usuń</string> + <string name="delete_neg">Anuluj</string> + <string name="wizard_agree">Akceptuj</string> + <string name="wizard_next">Dalej</string> + <string name="wizard_back">Wróć</string> + <string name="terminal_no_hosts_connected">Żaden host nie jest obecnie połączony</string> + <string name="terminal_connecting">Łączenie z hostem %1$s:%2$d przez %3$s</string> + <string name="terminal_sucess">Host \'%1$s \' zweryfikowany kluczem: %2$s</string> + <string name="terminal_failed">Weryfikacja klucza dla hostu nie powiodła się.</string> + <string name="terminal_using_s2c_algorithm">Algorytm serwer-klient: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algorytm klient-serwer: %1$s %2$s</string> + <string name="terminal_using_algorithm">Użyty algorytm: %1$s %2$s</string> + <string name="terminal_auth">Próba uwierzytelnienia</string> + <string name="terminal_auth_pass">Próba uwierzytelnienia za pomocą hasła</string> + <string name="terminal_auth_pass_fail">Uwierzytelnienie hasłem nie powiodło się</string> + <string name="terminal_auth_pubkey_any">Próba uwierzytelnienia za pomocą dowolnego zapamiętanego klucza publicznego</string> + <string name="terminal_auth_pubkey_invalid">Wybrany klucz publiczny jest nieprawidłowy, spróbuj wybrać inny w ustawieniach hostu</string> + <string name="terminal_auth_pubkey_specific">"Próba uwierzytelnienia za pomocą wybranego klucza publicznego"</string> + <string name="terminal_auth_pubkey_fail">Uwierzytelnienie metodą klucza publicznego z kluczem \'%1$s\' nie powiodło się</string> + <string name="terminal_auth_ki">Próba uwierzytelnienia za pomocą klawiatury</string> + <string name="terminal_auth_ki_fail">Uwierzytelnienie za pomocą klawiatury nie powiodło się</string> + <string name="terminal_auth_fail">[Twój host nie obsługuje uwierzytelnienia za pomocą klawiatury lub hasła]</string> + <string name="terminal_no_session">Sesja nie zostanie rozpoczęta z powodu ustawień hostu</string> + <string name="terminal_enable_portfoward">Włącz przekierowanie portu: %1$s</string> + <string name="local_shell_unavailable">Niepowodzenie! Lokalna powłoka nie jest obsługiwana na tym telefonie.</string> + <string name="notification_text">%1$s wymaga reakcji</string> + <string name="no">Nie</string> + <string name="with_confirmation">Z potwierdzeniem</string> + <string name="yes">Tak</string> + <string name="exceptions_submit_message">ConnectBot miał prawdopodobnie problem podczas ostatniego uruchomienia. Wysłać raport o błędzie?</string> + <string name="menu_colors_reset">Przywróć</string> + <string name="app_is_running">ConnectBot jest uruchomiony</string> + <string name="color_red">czerwony</string> + <string name="color_green">zielony</string> + <string name="color_blue">niebieski</string> + <string name="color_gray">szary</string> + <string name="colors_fg_label">FG: %1$d</string> + <string name="color_bg_label">BG: %1$d</string> + <string name="image_description_connected">Połączono</string> + <string name="image_description_key_is_locked">Klucz zablokowany</string> + <string name="image_description_toggle_control_character">Przełącz znak kontrolny</string> + <string name="image_description_send_escape_character">Wyślij znak ESC</string> + <string name="image_description_show_keyboard">Pokarz klawiaturę</string> +</resources> diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..79f5620 --- /dev/null +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,217 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Cliente SSH simples e poderoso de código aberto</string> + <string name="service_desc">Mantém conexões SSH e de Chaves Públicas</string> + <string name="title_hosts_list">Hosts</string> + <string name="title_pubkey_list">Chaves Públicas</string> + <string name="title_port_forwards_list">Redirecionamento de Portas</string> + <string name="title_host_editor">Edição de Host</string> + <string name="title_help">Ajuda</string> + <string name="title_colors">Cores</string> + <string name="title_color_picker">Escolha a cor</string> + <string name="resolve_connect">Conectar</string> + <string name="resolve_entropy">Sensibilidade</string> + <string name="menu_insert">Adicionar Host</string> + <string name="menu_delete">Apagar host</string> + <string name="menu_preferences">Preferências</string> + <string name="help_intro">Escolha um dos tópicos abaixo para maiores informações</string> + <string name="help_about">Sobre o ConnectBot</string> + <string name="help_keyboard">Teclado</string> + <string name="pubkey_generate">Gerar</string> + <string name="pubkey_import">Importar</string> + <string name="pubkey_delete">Apagar chave</string> + <string name="pubkey_gather_entropy">Ajustando sensibilidade</string> + <string name="pubkey_touch_prompt">Toque na caixa para testar sensibilidade: %1$d%% pronto</string> + <string name="pubkey_touch_hint">Para garantir aleatoriedade durante a geração da chave, mova seu dedo aleatoriamente na caixa abaixo.</string> + <string name="pubkey_generating">Gerando par de chaves...</string> + <string name="pubkey_copy_private">Copiar chave privada</string> + <string name="pubkey_copy_public">Copiar chave pública</string> + <string name="pubkey_list_empty">Pressione Menu para criar ou importar pares de chaves.</string> + <string name="pubkey_unknown_format">Formato desconhecido</string> + <string name="pubkey_change_password">Mudar senha</string> + <string name="pubkey_list_pick">Escolher do /sdcard</string> + <string name="pubkey_import_parse_problem">Erro na leitura da chave privada importada</string> + <string name="pubkey_unlock">Abrir chave</string> + <string name="pubkey_failed_add">Senha inválida para Chave \'%1$s\'.Falha na autenticação.</string> + <string name="pubkey_memory_load">Carregar na memória</string> + <string name="pubkey_memory_unload">Descarregar da memória</string> + <string name="pubkey_load_on_start">Carregar chave ao iniciar</string> + <string name="pubkey_confirm_use">Confirmar antes de usar</string> + <string name="portforward_list_empty">Pressione Menu para criar encaminhamentos de porta.</string> + <string name="portforward_edit">Editar encaminhamento de porta</string> + <string name="portforward_delete">Apagar encaminhamento de porta</string> + <string name="prompt_nickname">Apelido:</string> + <string name="prompt_nickname_hint_pubkey">Minha chave de trabalho</string> + <string name="prompt_source_port">Porta de origem:</string> + <string name="prompt_destination">Destino:</string> + <string name="prompt_old_password">Senha antiga:</string> + <string name="prompt_password">Senha:</string> + <string name="prompt_again">(novamente)</string> + <string name="prompt_type">Tipo:</string> + <string name="prompt_password_can_be_blank">Nota: a senha pode ser em branco</string> + <string name="prompt_bits">Bits:</string> + <string name="prompt_pubkey_password">Senha para chave %1$s</string> + <string name="prompt_allow_agent_to_use_key">Permitir que a máquina remota use a chave %1$s?</string> + <string name="host_verification_failure_warning_header">ATENÇÃO: A IDENTIFICAÇÃO DO HOST REMOTO FOI ALTERADA!</string> + <string name="host_verification_failure_warning">É POSSÍVEL QUE ALGUÉM ESTEJA QUERENDO TE ENGANAR!\nAlguém pode estar tentando um ataque do tipo man-in-the-middle!\nTambém é possível que a chave do host remoto tenha mesmo sido modificada.</string> + <string name="prompt_host_disconnected">Host desconectado.\nFechar sessão?</string> + <string name="prompt_continue_connecting">Você tem certeza que\ndeseja se conectar?</string> + <string name="host_authenticity_warning">A autenticidade do Host \'%1$s\' não pode ser estabelecida</string> + <string name="host_fingerprint">O host %1$s tem a impressão digital %2$s</string> + <string name="alert_passwords_do_not_match_msg">As senhas são diferentes!</string> + <string name="alert_wrong_password_msg">Senha incorreta!</string> + <string name="alert_key_corrupted_msg">A chave privada parece estar corrompida!</string> + <string name="alert_sdcard_absent">O cartão SD não está inserido!</string> + <string name="button_add">Adicionar</string> + <string name="button_change">Alterar</string> + <string name="button_generate">Gerar chave</string> + <string name="button_resize">Redimensionar</string> + <string name="alert_disconnect_msg">Conexão perdida</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulação de Terminal</string> + <string name="pref_emulation_title">Modo de emulação</string> + <string name="pref_emulation_summary">Modo de emulação do terminal com conexões PTY</string> + <string name="pref_scrollback_title">Tamanho do buffer da tela</string> + <string name="pref_scrollback_summary">Tamanho do buffer de memória a manter para cada terminal</string> + <string name="pref_ui_category">Interface do usuário</string> + <string name="pref_rotation_title">Rotação da tela</string> + <string name="pref_rotation_summary">Como alterar a rotação da tela quando se conecta/desconecta um teclado</string> + <string name="pref_fullscreen_title">Tela cheia</string> + <string name="pref_fullscreen_summary">Ocultar a barra de estatus no modo terminal</string> + <string name="pref_keyboard_category">Teclado</string> + <string name="pref_memkeys_title">Guardar as chaves em memória</string> + <string name="pref_memkeys_summary">Manter chaves desbloqueadas na memória até que o serviço seja terminado</string> + <string name="pref_conn_persist_title">Conexões persistentes</string> + <string name="pref_conn_persist_summary">Forçar as conexões para que continuem conectadas mesmo em segundo plano</string> + <string name="pref_keymode_title">Teclas de atalho</string> + <string name="pref_keymode_summary">Selecione para usar Alt para \'/\' e Shift para Tab</string> + <string name="pref_camera_title">Atalho para a câmera</string> + <string name="pref_camera_summary">Selecione o que fazer quando o botão da câmera for pressionado</string> + <string name="pref_keepalive_title">Manter tela ligada</string> + <string name="pref_keepalive_summary">Evitar desligamento de tela quando conectado em um terminal</string> + <string name="pref_wifilock_title">Manter a rede Wi-Fi ativo</string> + <string name="pref_wifilock_summary">Evitar que a rede Wi-Fi seja desligada quando alguma sesão está ativa</string> + <string name="pref_bumpyarrows_title">Feedback de rolagem</string> + <string name="pref_bumpyarrows_summary">Vibrar quando pressionar o botão principal (trackball)</string> + <string name="pref_bell_category">Alerta do terminal</string> + <string name="pref_bell_title">Alerta audível</string> + <string name="pref_bell_volume_title">Volume dos alerta</string> + <string name="pref_bell_vibrate_title">Vibrar ao alertar</string> + <string name="pref_bell_notification_title">Enviar notificações visuais quando em segundo plano</string> + <string name="pref_bell_notification_summary">Enviar notificações sonoras quando em segundo plano</string> + <string name="list_keymode_right">Usar atalhos especiais da direita</string> + <string name="list_keymode_left">Usar atalhos especiais da esquerda</string> + <string name="list_keymode_none">Desabilitar</string> + <string name="list_pubkeyids_none">Não usar chaves</string> + <string name="list_pubkeyids_any">Usar qualquer chave pública</string> + <string name="hostpref_nickname_title">Apelido</string> + <string name="hostpref_color_title">Categoria de cor</string> + <string name="hostpref_fontsize_title">Tamanho da fonte (pontos)</string> + <string name="hostpref_pubkeyid_title">Usar autenticação por chave púbilca</string> + <string name="hostpref_authagent_title">Usar agente de autenticação SSH</string> + <string name="hostpref_postlogin_title">Automação pós-login</string> + <string name="hostpref_postlogin_summary">Comandos a serem executados no servidor remoto na autenticação</string> + <string name="hostpref_compression_title">Compressão</string> + <string name="hostpref_compression_summary">Útil em conexões lentas (2G)</string> + <string name="hostpref_wantsession_title">Iniciar sessão de terminal na conexão</string> + <string name="hostpref_wantsession_summary">Desabilite esta preferência para usar apenas encaminhamento de portas</string> + <string name="hostpref_stayconnected_title">Manter conectado</string> + <string name="hostpref_stayconnected_summary">Tentar reconectar ao host caso a conexão seja perdida</string> + <string name="hostpref_delkey_title">Tecla DEL</string> + <string name="hostpref_delkey_summary">Código a ser enviado quando a tecla DEL for pressionada</string> + <string name="hostpref_encoding_title">Codificação</string> + <string name="hostpref_encoding_summary">Codificação de caracteres para o host</string> + <string name="hostpref_connection_category">Configurações de conexão</string> + <string name="hostpref_username_title">Usuário</string> + <string name="hostpref_hostname_title">Anfitrião (host)</string> + <string name="hostpref_port_title">Porta</string> + <string name="bind_never">Nunca usado</string> + <string name="bind_minutes">%1$s minutos atrás</string> + <string name="bind_hours">%1$s horas atrás</string> + <string name="bind_days">%1$s dias atrás</string> + <string name="console_copy_done">%1$d bytes copiados para a área de tranferência</string> + <string name="console_copy_start">Toque e arraste\nou use o trackball\npara selecionar uma área para copiar</string> + <string name="console_menu_close">Fechar</string> + <string name="console_menu_copy">Copiar</string> + <string name="console_menu_paste">Colar</string> + <string name="console_menu_portforwards">Encaminhamento de portas</string> + <string name="console_menu_resize">Forçar tamanho</string> + <string name="console_menu_urlscan">Localizar URLs</string> + <string name="button_yes">Sim</string> + <string name="button_no">Não</string> + <string name="portforward_local">Porta local</string> + <string name="portforward_remote">Remoto</string> + <string name="portforward_dynamic">Dinâmico (SOCKS)</string> + <string name="portforward_pos">Criar encaminhamento de porta</string> + <string name="portforward_done">Encaminhamento de porta criado com sucesso</string> + <string name="portforward_problem">Problema na criação do redirecionamento de portas, talvez você esteja usando portas menores que 1024 ou a porta já esteja sendo usada.</string> + <string name="portforward_menu_add">Adicionar encaminhamento de porta</string> + <string name="hint_userhost">usuario\@nome_do_host</string> + <string name="list_format_error">Use o formato %1$s</string> + <string name="format_username">usuário</string> + <string name="format_hostname">nome do host</string> + <string name="format_port">porta</string> + <string name="list_menu_pubkeys">Gerenciar chaves públicas</string> + <string name="list_menu_sortcolor">Ordenar por cor</string> + <string name="list_menu_sortname">Ordenar por nome</string> + <string name="list_menu_settings">Configurações</string> + <string name="list_host_disconnect">Desconectar</string> + <string name="list_host_edit">Editar host</string> + <string name="list_host_portforwards">Editar encaminhamento de porta</string> + <string name="list_host_delete">Apagar host</string> + <string name="list_host_empty">Use a conexão rápida abaixo\npara conectar a um host.</string> + <string name="list_rotation_default">Padrão</string> + <string name="list_rotation_land">Forçar modo paisagem</string> + <string name="list_rotation_port">Forçar modo retrato</string> + <string name="list_rotation_auto">Automático</string> + <string name="list_camera_ctrlaspace">Ctrl + A + Espaço</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Nada</string> + <string name="list_delkey_backspace">Tecla Backspace</string> + <string name="list_delkey_del">Apagar</string> + <string name="delete_message">Certeza que quer deletar \'%1$s\' ?</string> + <string name="delete_pos">Sim, apagar</string> + <string name="delete_neg">Cancelar</string> + <string name="wizard_agree">Aceito</string> + <string name="wizard_next">Próximo</string> + <string name="wizard_back">Voltar</string> + <string name="terminal_no_hosts_connected">Sem host conectado atualmente</string> + <string name="terminal_connecting">Conectando no %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">"Anfitrião comprovado \'%1$s\' chave: %2$s"</string> + <string name="terminal_failed">Falha na verificação da chave do host</string> + <string name="terminal_using_s2c_algorithm">Algoritmo do servidor para o cliente: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algoritmo do cliente para o servidor: %1$s %2$s</string> + <string name="terminal_using_algorithm">Usando o algoritmo: %1$s %2$s</string> + <string name="terminal_auth">Tentando autenticação</string> + <string name="terminal_auth_pass">Tentativa de autenticação de \'senha\' em andamento</string> + <string name="terminal_auth_pass_fail">Método de autenticação por \'senha\' falhou</string> + <string name="terminal_auth_pubkey_any">Tentando autenticação \'chave pública\' com qualquer chave pública na memória</string> + <string name="terminal_auth_pubkey_invalid">A chave pública selecionada é inválida, tente selecionar outra no editor de hosts</string> + <string name="terminal_auth_pubkey_specific">Tentando autenticação \'chave pública\' com uma chave pública específica</string> + <string name="terminal_auth_pubkey_fail">Metodo de autenticação \'publickey\' com a chave \'%1$s\' falhou</string> + <string name="terminal_auth_ki">Tentando autenticação \'teclado-interativo\'</string> + <string name="terminal_auth_ki_fail">Metodo de autenticação \'interativa por teclado\' falhou</string> + <string name="terminal_auth_fail">[Seu host não suporta autenticação por \'senha\' ou \'interativa por teclado\'.</string> + <string name="terminal_no_session">A sessão não será iniciada devido à configurações do host.</string> + <string name="terminal_enable_portfoward">Habilitar encaminhamento de porta: %1$s</string> + <string name="local_shell_unavailable">Erro! O terminal local não está disponível neste telefone.</string> + <string name="notification_text">%1$s deseja sua atenção.</string> + <string name="no">Não</string> + <string name="with_confirmation">Com confirmação</string> + <string name="yes">Sim</string> + <string name="exceptions_submit_message">Aparentemente o ConnectBot teve um problema na última execução. Deseja enviar relatório de erro aos desenvolvedores do aplicativo?</string> + <string name="menu_colors_reset">Reiniciar</string> + <string name="app_is_running">ConnectBot está executando</string> + <string name="color_red">vermelho</string> + <string name="color_green">verde</string> + <string name="color_blue">azul</string> + <string name="color_gray">cinza</string> + <string name="colors_fg_label">PP: %1$d</string> + <string name="color_bg_label">SP: %1$d</string> + <string name="image_description_connected">Conectado.</string> + <string name="image_description_key_is_locked">A chave está bloqueada.</string> + <string name="image_description_send_escape_character">Enviar caractere de escape.</string> + <string name="image_description_show_keyboard">Exibir teclado</string> +</resources> diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml new file mode 100644 index 0000000..4aeb1a2 --- /dev/null +++ b/app/src/main/res/values-pt/strings.xml @@ -0,0 +1,209 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Simples e poderoso cliente SSH open-source</string> + <string name="service_desc">Manter ligações SSH e chaves públicas carregadas</string> + <string name="title_hosts_list">Máquinas</string> + <string name="title_pubkey_list">Chaves Públicas</string> + <string name="title_port_forwards_list">Redireciomento de portas</string> + <string name="title_host_editor">Editar a Máquina</string> + <string name="title_help">Ajuda</string> + <string name="title_colors">Cores</string> + <string name="resolve_connect">Ligar</string> + <string name="resolve_entropy">Gerar Entropia</string> + <string name="menu_insert">Adicionar uma Máquina</string> + <string name="menu_delete">Eliminar anfitrião</string> + <string name="menu_preferences">Preferências</string> + <string name="help_intro">Selecione um tópico para mais informações sobre o assunto.</string> + <string name="help_about">Sobre ConnectBot</string> + <string name="help_keyboard">Teclado</string> + <string name="pubkey_generate">Gerar</string> + <string name="pubkey_import">Importar</string> + <string name="pubkey_delete">Apagar a chave</string> + <string name="pubkey_gather_entropy">Recolhendo entropia</string> + <string name="pubkey_touch_prompt">Toque nesta caixa para gerar aleariedade: %1$d%% completo</string> + <string name="pubkey_touch_hint">Para assegurar aleatoriedade durante a geração da chave, mova o dedo aleatoriamente sobre a caixa a baixo.</string> + <string name="pubkey_generating">A gerar par de chaves</string> + <string name="pubkey_copy_private">Copiar chave privada</string> + <string name="pubkey_copy_public">Copiar chave pública</string> + <string name="pubkey_list_empty">Carregue em Menu para criar\nou importar as chaves.</string> + <string name="pubkey_unknown_format">Formato desconhecido</string> + <string name="pubkey_change_password">Alterar password</string> + <string name="pubkey_list_pick">Escolher de /sdcard</string> + <string name="pubkey_import_parse_problem">Problema de análise com a chave privada importada</string> + <string name="pubkey_unlock">Desbloquear chave</string> + <string name="pubkey_failed_add">Password inválida para a chave \'%1$s\'. Erro na autenticação.</string> + <string name="pubkey_memory_load">Carregar para a memória</string> + <string name="pubkey_memory_unload">retirar da memória</string> + <string name="pubkey_load_on_start">carregar chave no Inicio</string> + <string name="pubkey_confirm_use">Confirmar antes de usar</string> + <string name="portforward_list_empty">Carregue Menu para criar\nport forward</string> + <string name="portforward_edit">Editar port forward</string> + <string name="portforward_delete">Apagar port forward</string> + <string name="prompt_nickname">Alcunha:</string> + <string name="prompt_nickname_hint_pubkey">A minha chave de trabalho</string> + <string name="prompt_source_port">Porta de origem:</string> + <string name="prompt_destination">Destino:</string> + <string name="prompt_old_password">Palavra-passe Antiga:</string> + <string name="prompt_password">Palavra-passe:</string> + <string name="prompt_again">(novamente)</string> + <string name="prompt_type">Tipo:</string> + <string name="prompt_password_can_be_blank">Nota: password pode ser em branco</string> + <string name="prompt_bits">bits:</string> + <string name="prompt_pubkey_password">Password para a chave \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Permitir ao anfitrião remoto\nusar a chave \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">AVISO: a identificação do anfitrião remoto mudou!</string> + <string name="host_verification_failure_warning">É POSSÍVEL QUE ALGUÉM ESTEJA A FAZER ALGO ERRADO!\n Alguém pode estar espiando ( ataque man-in-the-middle)!\nTambém e possível que a chave do hospede tenha sido alterada</string> + <string name="prompt_host_disconnected">Hospede desconectou-se.\nFechar sessão?</string> + <string name="prompt_continue_connecting">Tem a certeza que quer\ncontinuar a connectar?</string> + <string name="host_authenticity_warning">A autenticidade do anfitrião \'%1$s\' não pode ser verificada.</string> + <string name="host_fingerprint">Hospede %1$s id da chave é %2$s</string> + <string name="alert_passwords_do_not_match_msg">As palavras passe não coincidem!</string> + <string name="alert_wrong_password_msg">Palavra passe errada!</string> + <string name="alert_key_corrupted_msg">Chave privada parece estar corrupta.</string> + <string name="alert_sdcard_absent">Cartão SD não parece estar inserido!</string> + <string name="button_add">Adicionar</string> + <string name="button_change">Alterar</string> + <string name="button_generate">Gerar Chave</string> + <string name="button_resize">Redimensionar</string> + <string name="alert_disconnect_msg">Ligação Perdida</string> + <string name="msg_copyright">Direitos de Autor © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminal de emulação</string> + <string name="pref_emulation_title">Modo de emulação</string> + <string name="pref_emulation_summary">Terminal de emulação para usar conecções PTY</string> + <string name="pref_scrollback_title">Tamanho de Scrollback</string> + <string name="pref_scrollback_summary">Tamanho do buffer de scrollback em memória para cada consola</string> + <string name="pref_ui_category">Interface de utilizador</string> + <string name="pref_rotation_title">Modo de rotação</string> + <string name="pref_rotation_summary">Como alterar a rotação quando o teclado é aberto/fechado</string> + <string name="pref_fullscreen_title">Ecrã completo</string> + <string name="pref_fullscreen_summary">Esconder a barra de notificação quando estiver na consola</string> + <string name="pref_memkeys_title">Lembrar chaves em memoria</string> + <string name="pref_memkeys_summary">Manter chaves desbloqueadas em memória até ao serviço de fundo terminar</string> + <string name="pref_conn_persist_title">Persistir ligações</string> + <string name="pref_conn_persist_summary">Forçar ligações a ficarem ligadas quando em segundo plano</string> + <string name="pref_keymode_title">Atalhos de directoria</string> + <string name="pref_keymode_summary">Escolher como usar Alt para \'/\' e Shift para Tab</string> + <string name="pref_camera_title">Atalho para a camera</string> + <string name="pref_camera_summary">Escolha o atalho a activar quando o botão da camara é pressionado</string> + <string name="pref_keepalive_title">Mantar ecrã activo</string> + <string name="pref_keepalive_summary">Previne que o ecrã seja desligado quando se esta a utilizar a consola</string> + <string name="pref_wifilock_title">Manter Wi-Fi activo</string> + <string name="pref_wifilock_summary">Evitar que o Wi-Fi desligue quando uma sessão está activa</string> + <string name="pref_bumpyarrows_title">Cursores com vibração</string> + <string name="pref_bumpyarrows_summary">Vibrar quando enviar teclas de cursor com a trackball; útil para ligações com atraso</string> + <string name="pref_bell_category">Aviso sonoro do terminal</string> + <string name="pref_bell_title">Aviso sonoro audível</string> + <string name="pref_bell_volume_title">Volume do aviso sonoro</string> + <string name="pref_bell_vibrate_title">Vibrar em aviso sonoro</string> + <string name="pref_bell_notification_title">Notificações em segundo plano</string> + <string name="pref_bell_notification_summary">Enviar notificação quando um terminal a correr em segundo plano enviar um aviso sonoro.</string> + <string name="list_keymode_right">Usar teclas do lado direito</string> + <string name="list_keymode_left">Usar teclas do lado esquerdo</string> + <string name="list_keymode_none">Desactivar</string> + <string name="list_pubkeyids_none">Não usar teclas</string> + <string name="list_pubkeyids_any">Usar qualquer tecla não bloqueada</string> + <string name="hostpref_nickname_title">Alcunha</string> + <string name="hostpref_color_title">Categoria de cor</string> + <string name="hostpref_fontsize_title">Tamanho da fonte (pt)</string> + <string name="hostpref_pubkeyid_title">Utilizar autenticação de chave publica (pubkey)</string> + <string name="hostpref_authagent_title">Usar agente de autenticação SSH</string> + <string name="hostpref_postlogin_title">Automação pós-login</string> + <string name="hostpref_postlogin_summary">Comandos a correr no servidor remoto após autenticar</string> + <string name="hostpref_compression_title">Compressão</string> + <string name="hostpref_compression_summary">Isto pode ajudar em redes mais lentas</string> + <string name="hostpref_wantsession_title">Iniciar sessão de shell</string> + <string name="hostpref_wantsession_summary">Desactivar esta preferência para usar apenas redirecionamento de portas</string> + <string name="hostpref_stayconnected_title">Permanecer ligado</string> + <string name="hostpref_stayconnected_summary">Tentar ligar de novo ao anfitrião se desligar</string> + <string name="hostpref_delkey_title">Tecla DEL</string> + <string name="hostpref_delkey_summary">O código de tecla a enviar quando a tecla DEL é pressionada</string> + <string name="hostpref_encoding_title">Codificação</string> + <string name="hostpref_encoding_summary">Codificação de caractéres para o anfitrião</string> + <string name="hostpref_connection_category">Configuração da Ligação</string> + <string name="hostpref_username_title">Nome do Utilizador</string> + <string name="hostpref_hostname_title">Anfitrião</string> + <string name="hostpref_port_title">Porto</string> + <string name="bind_never">Nunca ligado</string> + <string name="bind_minutes">%1$s minutos atrás</string> + <string name="bind_hours">%1$s horas atrás</string> + <string name="bind_days">%1$s dias atrás</string> + <string name="console_copy_done">Copiados %1$d bytes para a área de transferência</string> + <string name="console_copy_start">Toque e arraste\nou use as teclas direcionais\npara selecionar a área a copiar</string> + <string name="console_menu_close">Fechar</string> + <string name="console_menu_copy">Copiar</string> + <string name="console_menu_paste">Colar</string> + <string name="console_menu_portforwards">Redirecionamento de portas</string> + <string name="console_menu_resize">Forçar Tamanho</string> + <string name="console_menu_urlscan">Detecção de URL</string> + <string name="button_yes">Sim</string> + <string name="button_no">Não</string> + <string name="portforward_local">Local</string> + <string name="portforward_remote">Remoto</string> + <string name="portforward_dynamic">Dinamico (SOCKS)</string> + <string name="portforward_pos">Criar redirecionamento de portas</string> + <string name="portforward_done">Redirecionamento de porta criado com sucesso</string> + <string name="portforward_problem">Problema ao criar redirecionamento de porta, talvez esteja a usar portas abaixo de 1024 ou a porta já esteja em uso?</string> + <string name="portforward_menu_add">Adicionar redirecionamento de porta</string> + <string name="hint_userhost">utilizador\@servidor</string> + <string name="list_format_error">Use o formato %1$s</string> + <string name="format_username">nome do utilizador</string> + <string name="format_hostname">endereço</string> + <string name="format_port">porto</string> + <string name="list_menu_pubkeys">Gerir Chaves Públicas</string> + <string name="list_menu_sortcolor">Ordenar por cor</string> + <string name="list_menu_sortname">Ordenar por nome</string> + <string name="list_menu_settings">Definições</string> + <string name="list_host_disconnect">Desconectar</string> + <string name="list_host_edit">Editar anfitrião</string> + <string name="list_host_portforwards">Editar redirecionamento de portas</string> + <string name="list_host_delete">Eliminar anfitrião</string> + <string name="list_host_empty">Usar a caixa de ligação rápida\nabaixo para ligar a um anfitrião.</string> + <string name="list_rotation_default">Padrão</string> + <string name="list_rotation_land">Forçar panoramico</string> + <string name="list_rotation_port">Forçar retrato</string> + <string name="list_rotation_auto">Automática</string> + <string name="list_camera_ctrlaspace">Ctrl+A seguido de Espaço</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Nenhum</string> + <string name="list_delkey_backspace">Retrocesso</string> + <string name="list_delkey_del">Apagar</string> + <string name="delete_message">Tem a certeza que deseja eliminar \'%1$s\'?</string> + <string name="delete_pos">Sim, apagar</string> + <string name="delete_neg">Cancelar</string> + <string name="wizard_agree">Aceitar</string> + <string name="wizard_next">Seguinte</string> + <string name="wizard_back">Retroceder</string> + <string name="terminal_no_hosts_connected">Nenhum anfitrião ligado actualmente</string> + <string name="terminal_connecting">A ligar a %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Verificado anfitrião \'%1$s\' chave: %2$s</string> + <string name="terminal_failed">Verificação da chave do anfitrião falhou.</string> + <string name="terminal_using_s2c_algorithm">Algoritmo servidor-cliente: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Algoritmo cliente-servidor: %1$s %2$s</string> + <string name="terminal_using_algorithm">A usar algoritmo %1$s %2$s</string> + <string name="terminal_auth">A tentar autenticação</string> + <string name="terminal_auth_pass">A tentar autenticação por \'password\'</string> + <string name="terminal_auth_pass_fail">Autenticação pelo método \'password\' falhou</string> + <string name="terminal_auth_pubkey_any">A tentar autenticação de chave pública (pubkey) com todas as chaves públicas em memória</string> + <string name="terminal_auth_pubkey_invalid">A chave pública escolhida é inválida, tente escolher outra chave no editor de anfitriões</string> + <string name="terminal_auth_pubkey_specific">A tentar autenticação de chave pública (publickey) com uma chave pública específica</string> + <string name="terminal_auth_pubkey_fail">Autenticação pelo método de chave pública (publickey) com a chave \'%1$s\' falhou</string> + <string name="terminal_auth_ki">A tentar autenticação pelo método \'teclado-interactivo\'</string> + <string name="terminal_auth_ki_fail">Autenticação pelo método \'teclado-interactivo\' falhou</string> + <string name="terminal_auth_fail">[O seu anfitrião não suporta autenticação por \'password\' ou \'teclado-interactivo\'.]</string> + <string name="terminal_no_session">A sessão não será iniciada devido a preferência do anfitrião.</string> + <string name="terminal_enable_portfoward">Permitir redirecionamento de porta: %1$s</string> + <string name="local_shell_unavailable">Falha! Shell local não está disponível neste telemóvel.</string> + <string name="notification_text">%1$s requer a sua atenção.</string> + <string name="no">Não</string> + <string name="with_confirmation">com confirmação</string> + <string name="yes">Sim</string> + <string name="exceptions_submit_message">Aparentemente o ConnectBot teve um problema na ultima vez que foi executado. Enviar relatório de erros para os programadores do ConnectBot?</string> + <string name="menu_colors_reset">Limpar</string> + <string name="app_is_running">O ConnectBot esta a ser executado</string> + <string name="color_red">vermelho</string> + <string name="color_green">verde</string> + <string name="color_blue">azul</string> + <string name="color_gray">cinzento</string> +</resources> diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml new file mode 100644 index 0000000..2581c39 --- /dev/null +++ b/app/src/main/res/values-ro/strings.xml @@ -0,0 +1,55 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Un client SSH simplu, flexibil, open-source</string> + <string name="service_desc">Managementul conexiunilor si al cheilor publice</string> + <string name="title_hosts_list">Gazde</string> + <string name="title_pubkey_list">Chei publice</string> + <string name="title_port_forwards_list">Redirectări trafic</string> + <string name="title_host_editor">Editare gazdă</string> + <string name="title_help">Ajutor</string> + <string name="title_colors">Culori</string> + <string name="resolve_connect">Conectează-te</string> + <string name="resolve_entropy">Genereaza sursă entropie</string> + <string name="menu_insert">Adaugă gazdă</string> + <string name="menu_delete">Sterge gazdă</string> + <string name="menu_preferences">Setări</string> + <string name="help_intro">Selecteaza un subiect pentru a accesa mai multe informatii</string> + <string name="help_about">Despre ConnectBot</string> + <string name="help_keyboard">Tastatură</string> + <string name="pubkey_generate">Generează</string> + <string name="pubkey_import">Importă</string> + <string name="pubkey_delete">Șterge cheia</string> + <string name="pubkey_gather_entropy">Genereaza sursa entropie</string> + <string name="pubkey_touch_prompt">Apasa aceasta zona pentru a genera entropie: %1$d%% finalizat</string> + <string name="pubkey_touch_hint">Pentru a va asigura un grad inalt de stocasticitate in timpul generarii cheii, miscati cat mai aleator degetul in zona de mai jos</string> + <string name="pubkey_generating">Generare cheie ...</string> + <string name="pubkey_copy_private">Copiaza cheia privata</string> + <string name="pubkey_copy_public">Copiaza cheia publica</string> + <string name="pubkey_list_empty">Apasa \"Menu\" pentru a crea sau pentru a importa chei.</string> + <string name="pubkey_unknown_format">Format necunoscut</string> + <string name="pubkey_change_password">Schimbă parola</string> + <string name="pubkey_list_pick">Selecteaza de pe /card SD</string> + <string name="pubkey_import_parse_problem">A aparut o eroare in timpul citirii cheii private</string> + <string name="pubkey_unlock">Decripteaza cheia</string> + <string name="pubkey_failed_add">Parola gresita pentru cheia \'%1$s\'. Autenticitatea cererii nu poate fi verificata.</string> + <string name="pubkey_memory_load">Incarca in memorie</string> + <string name="pubkey_memory_unload">Sterge din memorie</string> + <string name="pubkey_load_on_start">Incarca cheia la start</string> + <string name="pubkey_confirm_use">Confirma alegerea inainte de folosirea cheii</string> + <string name="portforward_list_empty">Apasa \"Menu\" pentru a crea redirectari de trafic</string> + <string name="portforward_edit">Editeaza redirectari trafic</string> + <string name="portforward_delete">Sterge redirectari trafic</string> + <string name="prompt_nickname">Poreclă:</string> + <string name="prompt_nickname_hint_pubkey">Cheia mea</string> + <string name="prompt_source_port">Port sursă</string> + <string name="prompt_destination">Destinație</string> + <string name="prompt_old_password">Parola veche:</string> + <string name="prompt_password">Parolă:</string> + <string name="prompt_again">(din nou)</string> + <string name="prompt_type">Mod redirectare:</string> + <string name="prompt_password_can_be_blank">Nota: parola</string> + <string name="prompt_bits">Biți:</string> + <string name="prompt_pubkey_password">Parola pentru cheia \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Acceptați utilizarea cheii \'%1$s\' \nde către agentul de autentificare?</string> + <string name="host_verification_failure_warning_header">ATENTIE: IDENTITATEA SISTEMULUI CONECTAT S-A SCHIMBAT!</string> +</resources> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..61e19c3 --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,215 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Простой и мощный SSH-клиент с открытым исходным кодом.</string> + <string name="service_desc">Управляет SSH-соединениями и загруженными публичными ключами</string> + <string name="title_hosts_list">Серверы</string> + <string name="title_pubkey_list">Публичные ключи</string> + <string name="title_port_forwards_list">Проброс портов</string> + <string name="title_host_editor">Изменить сервер</string> + <string name="title_help">Помощь</string> + <string name="title_colors">Настройка цветов</string> + <string name="resolve_connect">Подключиться</string> + <string name="resolve_entropy">Сбор случайных данных</string> + <string name="menu_insert">Добавить сервер</string> + <string name="menu_delete">Удалить сервер</string> + <string name="menu_preferences">Настройки</string> + <string name="help_intro">Выберите то, о чём Вы хотите узнать</string> + <string name="help_about">О ConnectBot</string> + <string name="help_keyboard">Клавиатура</string> + <string name="pubkey_generate">Генерировать</string> + <string name="pubkey_import">Импортировать</string> + <string name="pubkey_delete">Удалить ключ</string> + <string name="pubkey_gather_entropy">Сбор случайной информации</string> + <string name="pubkey_touch_prompt">Нажмите сюда для генерирования случайной информации: %1$d%% готово</string> + <string name="pubkey_touch_hint">Для использования случайной выборки во время генерации ключа - перемещайте палец в области экрана ниже.</string> + <string name="pubkey_generating">Генерирование пары ключей</string> + <string name="pubkey_copy_private">Копировать приватный ключ</string> + <string name="pubkey_copy_public">Копировать публичный ключ</string> + <string name="pubkey_list_empty">Выберите Меню для создания или импорта пары ключей</string> + <string name="pubkey_unknown_format">Неизвестный формат</string> + <string name="pubkey_change_password">Сменить пароль</string> + <string name="pubkey_list_pick">Загрузить с SD карты</string> + <string name="pubkey_import_parse_problem">Проблема при распозновании импортированного частного ключа</string> + <string name="pubkey_unlock">Разблокировать ключ</string> + <string name="pubkey_failed_add">Неверный пароль для ключа \'%1$s\'. Аутентификация не удалась</string> + <string name="pubkey_memory_load">Загрузить в память</string> + <string name="pubkey_memory_unload">Выгрузить из памяти</string> + <string name="pubkey_load_on_start">Загружать ключ при старте</string> + <string name="pubkey_confirm_use">Подтверждение перед использованием</string> + <string name="portforward_list_empty">Выберите Меню для переадресации порта</string> + <string name="portforward_edit">Правка переадресации порта</string> + <string name="portforward_delete">Удалить переадресацию порта</string> + <string name="prompt_nickname">Псевдоним:</string> + <string name="prompt_nickname_hint_pubkey">Мой рабочий ключ</string> + <string name="prompt_source_port">С порта</string> + <string name="prompt_destination">На порт</string> + <string name="prompt_old_password">Старый пароль:</string> + <string name="prompt_password">Пароль:</string> + <string name="prompt_again">(повторить)</string> + <string name="prompt_type">Тип переадресации</string> + <string name="prompt_password_can_be_blank">Поле для ввода пароля не может быть пустым</string> + <string name="prompt_bits">Размер в битах</string> + <string name="prompt_pubkey_password">Пароль для ключа \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Использовать ключ \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">ВНИМАНИЕ! ИДЕНТИФИКАЦИЯ УДАЛЕННОГО ХОСТА ЗАМЕНЕНА</string> + <string name="host_verification_failure_warning">Похоже есть возможность что вас ломают. Или кто то изменил ключ на удаленном хосте.</string> + <string name="prompt_host_disconnected">Хост отвалился.\nЗакрыть сессию ?</string> + <string name="prompt_continue_connecting">Хотите продолжить\nпопытки соединения?</string> + <string name="host_authenticity_warning">Подлинность хоста \'%1$s\' не может быть установлена</string> + <string name="host_fingerprint">Подпись (fingerprint) host-ключа для сервера %1$s - %2$s</string> + <string name="alert_passwords_do_not_match_msg">Пароли не совпадают!</string> + <string name="alert_wrong_password_msg">Неверный пароль!</string> + <string name="alert_key_corrupted_msg">Возможно частный ключ повреждён!</string> + <string name="alert_sdcard_absent">SD карта не вставлена!</string> + <string name="button_add">Добавить</string> + <string name="button_change">Изменить</string> + <string name="button_generate">Генерировать ключ</string> + <string name="button_resize">Изменить размер</string> + <string name="alert_disconnect_msg">Соединение потеряно</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Эмулятор терминала</string> + <string name="pref_emulation_title">Режим эмуляции</string> + <string name="pref_emulation_summary">Режим эмуляции терминала для использования PTY соединений</string> + <string name="pref_scrollback_title">Размер прокрутки</string> + <string name="pref_scrollback_summary">Размер буфера прокруктки в памяти для каждой консоли</string> + <string name="pref_ui_category">Пользовательский интерфейс</string> + <string name="pref_rotation_title">Настройки ориентации экрана</string> + <string name="pref_rotation_summary">Настройки смены ориентации дисплея при пользовании клавиатурой</string> + <string name="pref_titlebarhide_title">Автоскрытие заголовка</string> + <string name="pref_fullscreen_title">Полный экран</string> + <string name="pref_fullscreen_summary">Убирать область статуса во время работы с консолью</string> + <string name="pref_ctrlfkeys_title">Ctrl+num аналогично F-keys</string> + <string name="pref_volumefont_title">Клавиша громкости регулирует размер шрифта</string> + <string name="pref_memkeys_title">Запомнить ключи в памяти</string> + <string name="pref_memkeys_summary">Держать ключи в памяти пока процессы работают</string> + <string name="pref_conn_persist_title">Постоянные соединения</string> + <string name="pref_conn_persist_summary">Принудительно сохранять соединение в фоновом режиме.</string> + <string name="pref_keymode_title">Ярлыки папок</string> + <string name="pref_keymode_summary">Выберите, как использовать Alt вместо \'/\' и Shift вместо Tab</string> + <string name="pref_camera_title">Ярлык камеры</string> + <string name="pref_camera_summary">Выберите ярлык, запускаемый при нажатии клавиши камеры</string> + <string name="pref_keepalive_title">Держать экран включенным</string> + <string name="pref_keepalive_summary">Препятствовать отключению экрана при работе в консоли</string> + <string name="pref_wifilock_title">Держать Wi-Fi включенным</string> + <string name="pref_wifilock_summary">Препятствовать отключению Wi-Fi при активном сеансе</string> + <string name="pref_bumpyarrows_title">Настройки обратной связи при нажатии</string> + <string name="pref_bumpyarrows_summary">Вибросигнал при передаче клавиш управления курсором с трекбола; полезно при медленном соединении</string> + <string name="pref_bell_category">Настройки звукового сигнала в терминале</string> + <string name="pref_bell_title">Включить звонок</string> + <string name="pref_bell_volume_title">Громкость сигнала</string> + <string name="pref_bell_vibrate_title">Виброзвонок</string> + <string name="pref_bell_notification_title">Фоновые уведомления</string> + <string name="pref_bell_notification_summary">Включить уведомления для терминала работающего в фоновом процессе</string> + <string name="list_keymode_right">Для специальных функций использовать клавиши с правой стороны</string> + <string name="list_keymode_left">Использовать клавиши с левой стороны</string> + <string name="list_keymode_none">Отключить</string> + <string name="list_pubkeyids_none">Не использовать ключи</string> + <string name="list_pubkeyids_any">Использовать любую свободную клавишу</string> + <string name="hostpref_nickname_title">Псевдоним</string> + <string name="hostpref_color_title">Цвет для категории</string> + <string name="hostpref_fontsize_title">Размер шрифта</string> + <string name="hostpref_pubkeyid_title">Использовать аутентификацию по открытому ключу</string> + <string name="hostpref_authagent_title">Использовать SSH протокол для соединения</string> + <string name="hostpref_postlogin_title">Автоматизация после авторизации</string> + <string name="hostpref_postlogin_summary">Комманды, выполняемые на удаленной машине после подключения</string> + <string name="hostpref_compression_title">Сжатие</string> + <string name="hostpref_compression_summary">Это может быть полезным в медленных сетях</string> + <string name="hostpref_wantsession_title">Начать сеанс оболочки</string> + <string name="hostpref_wantsession_summary">Выключить для использования только переадресации портов</string> + <string name="hostpref_stayconnected_title">Держать подключение</string> + <string name="hostpref_stayconnected_summary">Пытаться переподключиться при отключении узла</string> + <string name="hostpref_delkey_title">Клавиша DEL</string> + <string name="hostpref_delkey_summary">Послать данный код при нажатие клавиши DEL</string> + <string name="hostpref_encoding_title">Кодировка</string> + <string name="hostpref_encoding_summary">Кодировка символов узла</string> + <string name="hostpref_connection_category">Настройки соединения</string> + <string name="hostpref_username_title">Имя пользователя</string> + <string name="hostpref_hostname_title">Узел</string> + <string name="hostpref_port_title">Порт</string> + <string name="bind_never">Не подключен</string> + <string name="bind_minutes">%1$s минут назад</string> + <string name="bind_hours">%1$s час(ов) назад</string> + <string name="bind_days">%1$s дней назад</string> + <string name="console_copy_done">Скопировано %1$d байт в буфер обмена</string> + <string name="console_copy_start">Для выделения области на экране используйте Touch and Drag или Directional Pad</string> + <string name="console_menu_close">Закрыть</string> + <string name="console_menu_copy">Копировать</string> + <string name="console_menu_paste">Вставить</string> + <string name="console_menu_portforwards">Перенаправления портов</string> + <string name="console_menu_resize">Зафиксировать размер</string> + <string name="console_menu_urlscan">Список URL</string> + <string name="button_yes">Да</string> + <string name="button_no">Нет</string> + <string name="portforward_local">Локальный</string> + <string name="portforward_remote">Удалённый</string> + <string name="portforward_dynamic">Динамический (SOCKS)</string> + <string name="portforward_pos">Создать перенаправление порта</string> + <string name="portforward_done">Перенаправление порта успешно завершено</string> + <string name="portforward_problem">Не удалось создать перенаправление порта. Может быть вы используете порты меньше чем 1024 или пор уже используется?</string> + <string name="portforward_menu_add">Добавить перенаправление порта</string> + <string name="hint_userhost">пользователь\@имяхоста</string> + <string name="list_format_error">Используйте формат %1$s</string> + <string name="format_username">имя пользователя</string> + <string name="format_hostname">имяхоста</string> + <string name="format_port">порт</string> + <string name="list_menu_pubkeys">Управление открытыми ключами</string> + <string name="list_menu_sortcolor">Сортировать по цвету</string> + <string name="list_menu_sortname">Сортировать по имени</string> + <string name="list_menu_settings">Настройки</string> + <string name="list_host_disconnect">Разорвать соединение</string> + <string name="list_host_edit">Редактировать хост</string> + <string name="list_host_portforwards">Редактировать перенаправление портов</string> + <string name="list_host_delete">Удалить хост</string> + <string name="list_host_empty">Для быстрого подключения к хосту используйте чекбокс внизу</string> + <string name="list_rotation_default">По умолчанию</string> + <string name="list_rotation_land">Только альбомная ориентация</string> + <string name="list_rotation_port">Только портретная ориетация</string> + <string name="list_rotation_auto">Автоматически</string> + <string name="list_camera_ctrlaspace">Ctrl+A затем пробел</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Нет</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Удалить</string> + <string name="delete_message">Вы уверены, что хотите удалить \'%1$s\'?</string> + <string name="delete_pos">Да, удалить</string> + <string name="delete_neg">Отменить</string> + <string name="wizard_agree">Соглашаюсь</string> + <string name="wizard_next">Далее</string> + <string name="wizard_back">Назад</string> + <string name="terminal_no_hosts_connected">Нет подключенных узлов</string> + <string name="terminal_connecting">Подключение к %1$s:%2$d через %3$s</string> + <string name="terminal_sucess">Проверенный узел \'%1$s\' ключ: %2$s</string> + <string name="terminal_failed">Проверка ключа узла провалена</string> + <string name="terminal_using_s2c_algorithm">Алгоритм сервер-клиент: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Алгоритм клиент-сервер: %1$s %2$s</string> + <string name="terminal_using_algorithm">Используемый алгоритм: %1$s %2$s</string> + <string name="terminal_auth">Пытаюсь аутентифицироваться</string> + <string name="terminal_auth_pass">Попытка \'password\' авторизации</string> + <string name="terminal_auth_pass_fail">Неуспешная попытка \'password\' авторизации</string> + <string name="terminal_auth_pubkey_any">Попытка \'publickey\' авторизации с публичными ключами в памяти</string> + <string name="terminal_auth_pubkey_invalid">Выбранный открытый ключ неверен, выберите другой в редакторе узлов</string> + <string name="terminal_auth_pubkey_specific">Попытка \'publickey\' авторизации</string> + <string name="terminal_auth_pubkey_fail">Неуспешная авторизация \'publickey\' с ключом \'%1$s\'</string> + <string name="terminal_auth_ki">Попытка \'keyboard-interactive\' авторизации</string> + <string name="terminal_auth_ki_fail">Неуспешная авторизация</string> + <string name="terminal_auth_fail">Ваш хост не поддерживает \'password\' и \'keyboard-interactive\' типа авторизации</string> + <string name="terminal_no_session">Настройки хоста не позволяют запустить данную сессию</string> + <string name="terminal_enable_portfoward">Включить переадресацию порта: %1$s</string> + <string name="local_shell_unavailable">Ошибка! На этом телефоне отсутствует локальная оболочка.</string> + <string name="notification_text">%1$s требует внимания</string> + <string name="no">Нет</string> + <string name="with_confirmation">С подтверждением</string> + <string name="yes">Да</string> + <string name="exceptions_submit_message">Возможно, при последнем запуске программы возникла проблема. Отправить разработчикам отчет об ошибке?</string> + <string name="menu_colors_reset">Сбросить</string> + <string name="app_is_running">ConnectBot запущен</string> + <string name="color_red">красный</string> + <string name="color_green">зелёный</string> + <string name="color_blue">синий</string> + <string name="color_gray">серый</string> + <string name="image_description_connected">Подключено</string> + <string name="image_description_key_is_locked">Ключ заблокирован</string> + <string name="image_description_show_keyboard">Показать клавиатуру</string> +</resources> diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000..aa5e226 --- /dev/null +++ b/app/src/main/res/values-sk/strings.xml @@ -0,0 +1,209 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Jednoduchý, výkonný, open-source SSH klient.</string> + <string name="service_desc">Spravuje SSH spojenia a načítané verejné kľúče</string> + <string name="title_hosts_list">Hostitelia</string> + <string name="title_pubkey_list">Verejné kľúče</string> + <string name="title_port_forwards_list">Presmerovanie portov</string> + <string name="title_host_editor">Upraviť Hostiteľa</string> + <string name="title_help">Nápoveda</string> + <string name="title_colors">Farby</string> + <string name="resolve_connect">Pripojiť</string> + <string name="resolve_entropy">Získať entropiu</string> + <string name="menu_insert">Pridať hostiteľa</string> + <string name="menu_delete">Zmazať hostiteľa</string> + <string name="menu_preferences">Nastavenia</string> + <string name="help_intro">Prosím, vyberte si tému pre viac informácií o danom subjekte.</string> + <string name="help_about">O programe ConnectBot</string> + <string name="help_keyboard">Klávesnica</string> + <string name="pubkey_generate">Vygenerovať</string> + <string name="pubkey_import">Importovať</string> + <string name="pubkey_delete">Zmazať kľúč</string> + <string name="pubkey_gather_entropy">Získavanie entropie</string> + <string name="pubkey_touch_prompt">Dotknite sa plochy pre získanie náhodnosti: %1$d%% hotovo</string> + <string name="pubkey_touch_hint">Pre zabezpečenie náhodnosti počas generovania kľúča pohybujte prstom náhodne po ploche nižšie.</string> + <string name="pubkey_generating">Generujem kľúčový pár...</string> + <string name="pubkey_copy_private">Kopírovať súkromný kľúč</string> + <string name="pubkey_copy_public">Kopírovať verejný kľúč</string> + <string name="pubkey_list_empty">Ťukni na Menu pre vytvorenie\nalebo importovanie páru kľúčov.</string> + <string name="pubkey_unknown_format">Neznámy formát</string> + <string name="pubkey_change_password">Zmeniť heslo</string> + <string name="pubkey_list_pick">Vyberte z /sdcard</string> + <string name="pubkey_import_parse_problem">Problém s importom súkromného kľúča</string> + <string name="pubkey_unlock">Odomknúť kľúč</string> + <string name="pubkey_failed_add">Nesprávne heslo pre kľúč \'%1$s\'. Overenie zlyhalo.</string> + <string name="pubkey_memory_load">Nahrať do pamäte</string> + <string name="pubkey_memory_unload">Odstrániť z pamäte</string> + <string name="pubkey_load_on_start">Načítať kľúč pri štarte</string> + <string name="pubkey_confirm_use">Potvrdiť pred použitím</string> + <string name="portforward_list_empty">Ťukni na Menu pre vytvorenie\npresmerovania portov.</string> + <string name="portforward_edit">Upraviť presmerovanie portov</string> + <string name="portforward_delete">Zmazať smerovanie portu</string> + <string name="prompt_nickname">Prezývka:</string> + <string name="prompt_nickname_hint_pubkey">Môj pracovný kľúč</string> + <string name="prompt_source_port">Zdrojový port:</string> + <string name="prompt_destination">Cieľ:</string> + <string name="prompt_old_password">Staré heslo:</string> + <string name="prompt_password">Heslo:</string> + <string name="prompt_again">(znovu)</string> + <string name="prompt_type">Typ:</string> + <string name="prompt_password_can_be_blank">Poznámka: heslo nemôže byť prázdne</string> + <string name="prompt_bits">Veľkost v bitoch:</string> + <string name="prompt_pubkey_password">Heslo pre kľúč \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Povoliť vzdialenému hostitelovi\npoužíť kľúč \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">VAROVANIE! IDENTIFIKÁCIA VZDIALENÉHO HOSTITEĽA SA ZMENILA!</string> + <string name="host_verification_failure_warning">JE MOŽNÉ, ŽE IDE O NEKALÚ AKTIVITU!\nNiekto vás môže sledovať (man-in-the-middle útok)!\nTiež je možné, že kľúč hostiteľa bol zmenený.</string> + <string name="prompt_host_disconnected">Hostiteľ sa odpojil.\nUkončiť sedenie?</string> + <string name="prompt_continue_connecting">Naozaj chcete\npokračovať v pripájaní?</string> + <string name="host_authenticity_warning">Autenticita hostiteľa \'%1$s\' nemohla byť overená.</string> + <string name="host_fingerprint">Odtlačok kľúča hostiteľa %1$s je %2$s</string> + <string name="alert_passwords_do_not_match_msg">Heslo sa nezhoduje!</string> + <string name="alert_wrong_password_msg">Nesprávne heslo!</string> + <string name="alert_key_corrupted_msg">Súkromný kľúč vyzerá poškodene!</string> + <string name="alert_sdcard_absent">Nie je vložená SD karta!</string> + <string name="button_add">Pridať</string> + <string name="button_change">Zmeniť</string> + <string name="button_generate">Vygenerovať kľúč</string> + <string name="button_resize">Zmeniť veľkosť</string> + <string name="alert_disconnect_msg">Spojenie stratené</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Emulácia terminálu</string> + <string name="pref_emulation_title">Mód emulácie</string> + <string name="pref_emulation_summary">Použiť mód emulácie terminálu pri PTY spojeniach</string> + <string name="pref_scrollback_title">Veľkosť histórie</string> + <string name="pref_scrollback_summary">Veľkosť histórie uložená v pamäti pre každú konzolu</string> + <string name="pref_ui_category">Užívateľské rozhranie</string> + <string name="pref_rotation_title">Mód otáčania</string> + <string name="pref_rotation_summary">Ako zmeniť rotáciu, keď sa zobrazí / skryje klávesnica</string> + <string name="pref_fullscreen_title">Celá obrazovka</string> + <string name="pref_fullscreen_summary">Skryť stavový riadok, keď je zobrazená konzola</string> + <string name="pref_memkeys_title">Zapamätať kľúče v pamäti</string> + <string name="pref_memkeys_summary">Ponechať odomknuté kľúče v pamäti pokiaľ nie je ukončená služba na pozadí.</string> + <string name="pref_conn_persist_title">Perzistentné pripojenia</string> + <string name="pref_conn_persist_summary">Vynútiť spojenie aj v pozadí</string> + <string name="pref_keymode_title">Skratky adresárov</string> + <string name="pref_keymode_summary">Vyberte ako použiť Alt pre \'/\' a Shift pre Tab</string> + <string name="pref_camera_title">Odkaz pre fotoaparát</string> + <string name="pref_camera_summary">Vyberte odkaz, ktorý spustiť po stlačení tlačidla kamera</string> + <string name="pref_keepalive_title">Ponechať obrazovku zapnutú</string> + <string name="pref_keepalive_summary">Zabrániť vypnutiu obrazovky pri práci s konzolou</string> + <string name="pref_wifilock_title">Ponechať Wi-Fi aktívne</string> + <string name="pref_wifilock_summary">Ponechať Wi-Fi aktívne pokiaľ je otvorené spojenie</string> + <string name="pref_bumpyarrows_title">Hrboľaté šípky</string> + <string name="pref_bumpyarrows_summary">Vibrovať pri posielaní kurzorových tlačidiel z trackballu; užitočné pre chybové spojenia</string> + <string name="pref_bell_category">Zvonček terminálu</string> + <string name="pref_bell_title">Akustický zvonček</string> + <string name="pref_bell_volume_title">Hlasitosť zvončeka</string> + <string name="pref_bell_vibrate_title">Vibrovať pri zvončeku</string> + <string name="pref_bell_notification_title">Upozornenia na pozadí</string> + <string name="pref_bell_notification_summary">Zobraziť upozorneni ak zazvoní zvonček terminálu bežiaceho v pozadí.</string> + <string name="list_keymode_right">Použite klávesy vpravo</string> + <string name="list_keymode_left">Použite klávesy vľavo</string> + <string name="list_keymode_none">Vypnúť</string> + <string name="list_pubkeyids_none">Nepoužívať kľúče</string> + <string name="list_pubkeyids_any">Použiť ktorýkoľvek odomknutý kľúč</string> + <string name="hostpref_nickname_title">Prezývka</string> + <string name="hostpref_color_title">Farebná kategória</string> + <string name="hostpref_fontsize_title">Veľkosť písma (bodov)</string> + <string name="hostpref_pubkeyid_title">Použiť verejný kľúč pre autentifikáciu</string> + <string name="hostpref_authagent_title">Použiť SSH autorizačného agenta</string> + <string name="hostpref_postlogin_title">Automatizácia po prihlásení</string> + <string name="hostpref_postlogin_summary">Príkazy, ktoré sa spustia na vzdialenom serveri po prihlásení</string> + <string name="hostpref_compression_title">Kompresia</string> + <string name="hostpref_compression_summary">Môže pomôcť pri pomalých sieťach</string> + <string name="hostpref_wantsession_title">Spustiť sedenie shellu</string> + <string name="hostpref_wantsession_summary">Vypnite túto možnosť ak chcete len presmerovať porty</string> + <string name="hostpref_stayconnected_title">Zostať pripojený</string> + <string name="hostpref_stayconnected_summary">Znovu sa pripojiť pri odpojení</string> + <string name="hostpref_delkey_title">Klávesa DEL</string> + <string name="hostpref_delkey_summary">Kód stlačenej DEL klávesy</string> + <string name="hostpref_encoding_title">Kódovanie</string> + <string name="hostpref_encoding_summary">Kódovanie znakov hostiteľa</string> + <string name="hostpref_connection_category">Nastavenia pripojenia</string> + <string name="hostpref_username_title">Užívateľské meno</string> + <string name="hostpref_hostname_title">Hostiteľ</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Nikdy nepripojený</string> + <string name="bind_minutes">Pred %1$s minútami</string> + <string name="bind_hours">Pred %1$s hodinami</string> + <string name="bind_days">Pred %1$s dňami</string> + <string name="console_copy_done">%1$d bajtov skopírovaných do schránky</string> + <string name="console_copy_start">Pre výber kopírovanej oblasti \nsa dotknite a ťahajte\nalebo použite smerové tlačidlo</string> + <string name="console_menu_close">Zatvoriť</string> + <string name="console_menu_copy">Kopírovať</string> + <string name="console_menu_paste">Vložiť</string> + <string name="console_menu_portforwards">Presmerovanie portov</string> + <string name="console_menu_resize">Vynútiť veľkosť</string> + <string name="console_menu_urlscan">Nájsť URL</string> + <string name="button_yes">Áno</string> + <string name="button_no">Nie</string> + <string name="portforward_local">Lokálny</string> + <string name="portforward_remote">Vzdialený</string> + <string name="portforward_dynamic">Dynamický (SOCKS)</string> + <string name="portforward_pos">Vytvoriť presmerovanie portu</string> + <string name="portforward_done">Premerovanie portu úspešne vytvorené</string> + <string name="portforward_problem">Problém s vytvorením presmerovania portu, možno používate port menší ako 1024 alebo je už obsadený?</string> + <string name="portforward_menu_add">Pridať presmerovanie portu</string> + <string name="hint_userhost">používateľské_meno\@hostiteľ</string> + <string name="list_format_error">Použite formát \"%1$s\"</string> + <string name="format_username">užívateľské meno</string> + <string name="format_hostname">meno hostiteľa</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Spravovať verejné kľúče</string> + <string name="list_menu_sortcolor">Usporiadať podľa farby</string> + <string name="list_menu_sortname">Usporiadať podľa názvu</string> + <string name="list_menu_settings">Nastavenia</string> + <string name="list_host_disconnect">Odpojiť</string> + <string name="list_host_edit">Zmeniť hostiteľa</string> + <string name="list_host_portforwards">Upraviť presmerovanie portov</string> + <string name="list_host_delete">Zmazať hostiteľa</string> + <string name="list_host_empty">Použite pole rýchleho pripojenia\nnižšie pre pripojenie k hostiteľovi.</string> + <string name="list_rotation_default">Predvolené</string> + <string name="list_rotation_land">Vynútiť na šírku</string> + <string name="list_rotation_port">Vynútiť na výšku</string> + <string name="list_rotation_auto">Automaticky</string> + <string name="list_camera_ctrlaspace">Ctrl+A , potom medzerník</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Klávesa Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Žiadny</string> + <string name="list_delkey_backspace">Backspace</string> + <string name="list_delkey_del">Delete</string> + <string name="delete_message">Naozaj chcete zmazať \'%1$s\'?</string> + <string name="delete_pos">Áno, vymazať</string> + <string name="delete_neg">Zrušiť</string> + <string name="wizard_agree">Súhlasím</string> + <string name="wizard_next">Ďalej</string> + <string name="wizard_back">Späť</string> + <string name="terminal_no_hosts_connected">Žiadny hostiteľ nie je pripijený</string> + <string name="terminal_connecting">Pripájanie k %1$s:%2$d cez %3$s</string> + <string name="terminal_sucess">Hostiteľ overený \'%1$s\' kľúč: %2$s</string> + <string name="terminal_failed">Overenie kľúča hostiteľa neúspešné.</string> + <string name="terminal_using_s2c_algorithm">Server-klient algoritmus: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Klient-server algoritmus: %1$s %2$s</string> + <string name="terminal_using_algorithm">Použitie algoritmu: %1$s %2$s</string> + <string name="terminal_auth">Pokus o overenie</string> + <string name="terminal_auth_pass">Pokus o overenie heslom</string> + <string name="terminal_auth_pass_fail">Overenie heslom neúspešné</string> + <string name="terminal_auth_pubkey_any">Pokus o overenie verejným kľúčom z pamäti</string> + <string name="terminal_auth_pubkey_invalid">Vybraný verejný kľúč je neplatný, skúste zvoliť kľúč v editore hostiteľov</string> + <string name="terminal_auth_pubkey_specific">Pokus o overenie konkrétnym verejným kľúčom</string> + <string name="terminal_auth_pubkey_fail">Overenie verejným kľúčom \'%1$s\' zlyhalo</string> + <string name="terminal_auth_ki">Pokus o overenie cez interaktívnu klávesnicu</string> + <string name="terminal_auth_ki_fail">Overenie cez interaktívnu klávesnicu zlyhalo</string> + <string name="terminal_auth_fail">[Váš hostiteľ nepodporuje overenie heslom alebo overenie cez interaktívnu klávesnicu.]</string> + <string name="terminal_no_session">Sedenie nebude spustené z dôvodu nastavenia hostiteľa.</string> + <string name="terminal_enable_portfoward">Zapnúť presmerovanie portu: %1$s</string> + <string name="local_shell_unavailable">Chyba! Lokálny shell je nedostupný na tomto telefóne.</string> + <string name="notification_text">%1$s vyžaduje vašu pozornosť.</string> + <string name="no">Nie</string> + <string name="with_confirmation">S potvrdením</string> + <string name="yes">Áno</string> + <string name="exceptions_submit_message">Ostatné spustenie ConnectBot asi skončilo chybou. Zaslať chýbovú správu vývojárom ConnectBot?</string> + <string name="menu_colors_reset">Pôvodné</string> + <string name="app_is_running">ConnectBot je spustený</string> + <string name="color_red">červená</string> + <string name="color_green">zelená</string> + <string name="color_blue">modrá</string> + <string name="color_gray">sivá</string> +</resources> diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml new file mode 100644 index 0000000..8552716 --- /dev/null +++ b/app/src/main/res/values-sl/strings.xml @@ -0,0 +1,188 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Enostaven, močan, odprtokodni SSH odjemalec.</string> + <string name="service_desc">Vzdržuje SSH povezave in naložene javne ključe</string> + <string name="title_hosts_list">Gostitelji</string> + <string name="title_pubkey_list">Javni ključi</string> + <string name="title_port_forwards_list">Posredovana vrata</string> + <string name="title_host_editor">Urejanje gostitelja</string> + <string name="title_help">Pomoč</string> + <string name="title_colors">Barve</string> + <string name="resolve_connect">Poveži</string> + <string name="resolve_entropy">Zberi entropijo</string> + <string name="menu_insert">Dodaj gostitelja</string> + <string name="menu_delete">Odstrani gostitelja</string> + <string name="menu_preferences">Možnosti</string> + <string name="help_intro">Izberite temo spodaj za več informacij o določeni zadevi.</string> + <string name="help_about">O ConnectBotu</string> + <string name="help_keyboard">Tipkovnica</string> + <string name="pubkey_generate">Ustvari</string> + <string name="pubkey_import">Uvozi javni ključ</string> + <string name="pubkey_delete">Izbriši ključ</string> + <string name="pubkey_gather_entropy">Zbiranje entropije</string> + <string name="pubkey_touch_prompt">Dotaknite to polje za zbiranje naključnih vrednosti: %1$d%% končano</string> + <string name="pubkey_touch_hint">Da bi zagotovili naključnost med ustvarjanjem ključa, naključno premikajte prst po polju spodaj.</string> + <string name="pubkey_generating">Ustvarjanje para ključev</string> + <string name="pubkey_copy_private">Kopirajte zasebni ključ</string> + <string name="pubkey_copy_public">Kopirajte javni ključ</string> + <string name="pubkey_list_empty">Dotaknite \"Meni\" za ustvarjanje\nali uvažanje para ključev</string> + <string name="pubkey_unknown_format">Neznana vrsta</string> + <string name="pubkey_change_password">Spremenite geslo</string> + <string name="pubkey_list_pick">Izberite iz /sdcard</string> + <string name="pubkey_import_parse_problem">Problem pri razčlenjevanju uvoženega zasebnega ključa</string> + <string name="pubkey_unlock">Odkleni ključ</string> + <string name="pubkey_failed_add">Napačno geslo za ključ \'%1$s. Overitev spodletela.</string> + <string name="pubkey_memory_load">Naloži v spomin</string> + <string name="pubkey_memory_unload">Razloži s spomina</string> + <string name="pubkey_load_on_start">Naloži ključ ob začetku</string> + <string name="pubkey_confirm_use">Potrdi pred uporabo</string> + <string name="portforward_list_empty">Dotakni \"Meni\" za ustvarjanje\nposredovanih vrat.</string> + <string name="portforward_edit">Urejaj posredovana vrata</string> + <string name="portforward_delete">Izbriši posredovana vrata</string> + <string name="prompt_nickname">Vzdevek:</string> + <string name="prompt_nickname_hint_pubkey">Moj delovni ključ</string> + <string name="prompt_source_port">Izvorna vrata:</string> + <string name="prompt_destination">Cilj:</string> + <string name="prompt_old_password">Staro geslo:</string> + <string name="prompt_password">Geslo:</string> + <string name="prompt_again">(ponovno)</string> + <string name="prompt_type">Vrsta:</string> + <string name="prompt_password_can_be_blank">Opomba: geslo je lahko prazno</string> + <string name="prompt_bits">Biti:</string> + <string name="prompt_pubkey_password">Geslo za ključ \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Dovoli oddaljenemu gostitelju\nuporabo ključa \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">OPOZORILO: ISTOVETENJE ODDALJENEGA GOSTITELJA SE JE SPREMENILO!</string> + <string name="host_verification_failure_warning">MOŽNO JE, DA NEKDO POČNE NEKAJ ZLOBNEGA!\nNekdo vam lahko prisluškuje (napad s posrednikom)!\nMožno pa je tudi, da se je spremenil ključ gostitelja.</string> + <string name="prompt_host_disconnected">Gostitelj je prekinil povezavo.\nZaprem sejo?</string> + <string name="prompt_continue_connecting">Ali ste prepričani, da želite\nnadaljevati s povezovanjem?</string> + <string name="host_authenticity_warning">Istovetnost gostitelja \'%1$s\' ni mogoče ugotoviti.</string> + <string name="host_fingerprint">Ključ gostitelja %1$s ima zgoščeno vrednost %2$s</string> + <string name="alert_passwords_do_not_match_msg">Gesla se ne ujemajo!</string> + <string name="alert_wrong_password_msg">Napačno geslo!</string> + <string name="alert_key_corrupted_msg">Zasebni ključ izgleda pokvarjen!</string> + <string name="alert_sdcard_absent">Kartica SD ni vstavljena!</string> + <string name="button_add">Dodaj</string> + <string name="button_change">Spremeni</string> + <string name="button_generate">Ustvari ključ</string> + <string name="button_resize">Spremeni velikost</string> + <string name="alert_disconnect_msg">Povezava prekinjena</string> + <string name="msg_copyright">Avtorske pravice © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Posnemanje terminala</string> + <string name="pref_emulation_title">Način posnemanja</string> + <string name="pref_emulation_summary">Način posnemanja terminala za uporabo za PTY povezave</string> + <string name="pref_scrollback_title">Velikost pomnjenja vrstic</string> + <string name="pref_scrollback_summary">Velikost medpomnilnika pomnjenih vrstic ohraniti v spomnu za vsako konzolo</string> + <string name="pref_ui_category">Uporabniški vmesnik</string> + <string name="pref_rotation_title">Način za sukanje</string> + <string name="pref_rotation_summary">Kako spremeniti sukanje, ko tipkovnica skoči ven/noter</string> + <string name="pref_fullscreen_title">Celozaslonski način</string> + <string name="pref_fullscreen_summary">Skrij vrstico stanja, ko v konzoli</string> + <string name="pref_memkeys_title">Zapomni ključe v spominu</string> + <string name="pref_memkeys_summary">Hrani odklenjene ključe v spomnu dokler storitev v zaledju ni zaključena</string> + <string name="pref_conn_persist_title">Obstojne povezave</string> + <string name="pref_conn_persist_summary">Prisili povezave, da ostanejo povezane v ozadju</string> + <string name="pref_keymode_title">Bližnjice mape</string> + <string name="pref_keymode_summary">Izberite kako uporabiti Alt za \'/\' ter Shift za Tab</string> + <string name="pref_camera_title">Bližnjica kamere</string> + <string name="pref_camera_summary">Izberite, katera bližnjica se sproži, ko se pritisne gumb kamere</string> + <string name="pref_keepalive_title">Ohrani zaslon dejaven</string> + <string name="pref_keepalive_summary">Prepreči ugašanje zaslona med delom v konzoli</string> + <string name="pref_wifilock_title">Ohrani Wi-Fi dejaven</string> + <string name="pref_wifilock_summary">Prepreči ugašanje Wi-Fija med dejavno sejo</string> + <string name="pref_bumpyarrows_title">Izbočene puščice</string> + <string name="pref_bell_category">Zvonec terminala</string> + <string name="pref_bell_title">Slišen zvonec</string> + <string name="pref_bell_volume_title">Glasnost zvonca</string> + <string name="pref_bell_vibrate_title">Vibriraj ob zvoncu</string> + <string name="pref_bell_notification_title">Obvestila ozadja</string> + <string name="list_keymode_left">Uporabi ključe z leve strani</string> + <string name="list_keymode_none">Onemogoči</string> + <string name="list_pubkeyids_none">Ne uporabljaj ključev</string> + <string name="list_pubkeyids_any">Uporabi kateregakoli odklenjenega ključa</string> + <string name="hostpref_nickname_title">Vzdevek</string> + <string name="hostpref_color_title">Barvna kategorija</string> + <string name="hostpref_fontsize_title">Velikost pisave (pt)</string> + <string name="hostpref_pubkeyid_title">Uporabi overitev z javnim ključem</string> + <string name="hostpref_authagent_title">Uporabi SSH overitvenega agenta</string> + <string name="hostpref_compression_title">Stiskanje</string> + <string name="hostpref_compression_summary">To vam lahko pomaga pri počasnejših omrežjih</string> + <string name="hostpref_wantsession_title">Začni sejo lupine</string> + <string name="hostpref_stayconnected_title">Ostanite povezani</string> + <string name="hostpref_stayconnected_summary">Poskusi se ponovno povezati, če se prekine povezava</string> + <string name="hostpref_delkey_title">Tipka DEL</string> + <string name="hostpref_delkey_summary">Poslana koda tipke, ko je tipka DEL pritisnjena</string> + <string name="hostpref_encoding_title">Kodiranje</string> + <string name="hostpref_encoding_summary">Kodiranje znakov za gostitelja</string> + <string name="hostpref_connection_category">Nastavitve povezave</string> + <string name="hostpref_username_title">Uporabniško ime</string> + <string name="hostpref_hostname_title">Gostitelj</string> + <string name="hostpref_port_title">Vrata</string> + <string name="bind_never">Nikoli povezani</string> + <string name="bind_minutes">%1$s minut nazaj</string> + <string name="bind_hours">%1$s ur nazaj</string> + <string name="bind_days">%1$s dni nazaj</string> + <string name="console_menu_close">Zapri</string> + <string name="console_menu_copy">Kopiraj</string> + <string name="console_menu_paste">Prilepi</string> + <string name="console_menu_portforwards">Posredovanje vrat</string> + <string name="button_yes">Da</string> + <string name="button_no">Ne</string> + <string name="portforward_local">Krajevno</string> + <string name="portforward_remote">Oddaljeno</string> + <string name="portforward_pos">Ustvari posredovanje vrat</string> + <string name="portforward_done">Uspešno ustvarjeno posredovanje vrat</string> + <string name="portforward_menu_add">Dodaj posredovanje vrat</string> + <string name="hint_userhost">uporabnik\@gostitelj</string> + <string name="list_format_error">Uporabi vrsto %1$s</string> + <string name="format_username">Uporabniško ime</string> + <string name="format_hostname">ime gostitelja</string> + <string name="format_port">vrata</string> + <string name="list_menu_pubkeys">Upravljanje z javnimi ključi</string> + <string name="list_menu_sortcolor">Razvrsti po barvi</string> + <string name="list_menu_sortname">Razvrsti po imenu</string> + <string name="list_menu_settings">Nastavitve</string> + <string name="list_host_disconnect">Prekini povezavo</string> + <string name="list_host_edit">Urejaj gostitelja</string> + <string name="list_host_portforwards">Urejaj posredovanja vrat</string> + <string name="list_host_delete">Izbriši gostitelja</string> + <string name="list_rotation_default">Privzeto</string> + <string name="list_rotation_land">Prisili ležeče</string> + <string name="list_rotation_port">Prisili pokončno</string> + <string name="list_rotation_auto">Samodejno</string> + <string name="list_camera_ctrlaspace">Ctrl+A nato Space</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Nič</string> + <string name="list_delkey_backspace">Vračalka</string> + <string name="list_delkey_del">Delete</string> + <string name="delete_message">Ali ste prepričani, da želite izbrisati \'%1$s\'?</string> + <string name="delete_pos">Da, izbriši.</string> + <string name="delete_neg">Prekliči</string> + <string name="wizard_agree">Se strinjam</string> + <string name="wizard_next">Naprej</string> + <string name="wizard_back">Nazaj</string> + <string name="terminal_no_hosts_connected">Trenutno ni noben gostitelj povezan</string> + <string name="terminal_connecting">Povezujem se na %1$s:%2$d preko %3$s</string> + <string name="terminal_sucess">Preverjen gostitelj \'%1$s\' ključ: %2$s</string> + <string name="terminal_failed">Preverjanje ključa gostitelja spodletelo.</string> + <string name="terminal_using_s2c_algorithm">Strežnik - odjemalec algoritem: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Odjemalec - strežnik algoritem: %1$s %2$s</string> + <string name="terminal_using_algorithm">Uporabljam algoritem: %1$s %2$s</string> + <string name="terminal_auth">Poskušam se overit</string> + <string name="terminal_auth_pass">Poskušam overitev z geslom</string> + <string name="terminal_auth_pass_fail">Overitev z geslom spodletela</string> + <string name="terminal_auth_pubkey_fail">Spodletela avtentikacijska metoda \'publickey\' z ključem \'%1$s\'</string> + <string name="terminal_auth_fail">[Vaš gostitelj ne podpira \'password\' ali \'keyboard-interactive\' avtentikacije.]</string> + <string name="notification_text">%1$s želi vašo pozornost.</string> + <string name="no">Ne</string> + <string name="with_confirmation">S potrditvijo</string> + <string name="yes">Da</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> + <string name="menu_colors_reset">Ponastavi</string> + <string name="app_is_running">ConnectBot je dejaven</string> + <string name="color_red">rdeča</string> + <string name="color_green">zelena</string> + <string name="color_blue">modra</string> + <string name="color_gray">siva</string> +</resources> diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml new file mode 100644 index 0000000..73e73da --- /dev/null +++ b/app/src/main/res/values-sv/strings.xml @@ -0,0 +1,209 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Enkel och kraftfull SSH-klient baserad på öppen källkod.</string> + <string name="service_desc">Upprätthåller SSH-anslutningar och laddade publika nycklar</string> + <string name="title_hosts_list">Värddatorer</string> + <string name="title_pubkey_list">Publika nycklar</string> + <string name="title_port_forwards_list">Vidarebefordring av portar</string> + <string name="title_host_editor">Redigera värddator</string> + <string name="title_help">Hjälp</string> + <string name="title_colors">Färger</string> + <string name="resolve_connect">Anslut</string> + <string name="resolve_entropy">Samla in entropi</string> + <string name="menu_insert">Lägg till värd</string> + <string name="menu_delete">Ta bort värd</string> + <string name="menu_preferences">Inställningar</string> + <string name="help_intro">Var god välj ett ämne nedan som du skulle vilja ha mer information om.</string> + <string name="help_about">Om ConnectBot</string> + <string name="help_keyboard">Tangentbord</string> + <string name="pubkey_generate">Generera</string> + <string name="pubkey_import">Importera</string> + <string name="pubkey_delete">Ta bort nyckel</string> + <string name="pubkey_gather_entropy">Samlar in entropi</string> + <string name="pubkey_touch_prompt">Rör vid rutan för att samla in slumpdata: %1$d%% klart</string> + <string name="pubkey_touch_hint">För att försäkra dig om att nyckeln som skapas är slumpmässig, rör fingret slumpmässigt över skärmen.</string> + <string name="pubkey_generating">Genererar nyckelpar...</string> + <string name="pubkey_copy_private">Kopiera privat nyckel</string> + <string name="pubkey_copy_public">Kopiera publik nyckel</string> + <string name="pubkey_list_empty">Tryck på Meny för att\nskapa eller importera nyckelpar</string> + <string name="pubkey_unknown_format">Okänt format</string> + <string name="pubkey_change_password">Ändra lösenord</string> + <string name="pubkey_list_pick">Välj från /sdcard</string> + <string name="pubkey_import_parse_problem">Svårigheter med att tyda den importerade privata nyckeln</string> + <string name="pubkey_unlock">Lås upp nyckel</string> + <string name="pubkey_failed_add">Felaktigt lösenord för nyckeln \'%1$s\'. Autentiseringen misslyckades.</string> + <string name="pubkey_memory_load">Ladda in i minnet</string> + <string name="pubkey_memory_unload">Ladda ur minnet</string> + <string name="pubkey_load_on_start">Ladda nyckel från start</string> + <string name="pubkey_confirm_use">Bekräfta innan användning</string> + <string name="portforward_list_empty">Rör vid Menu för att skapa\nvidarebefodringar av portar.</string> + <string name="portforward_edit">Ändra vidarebefordring av port</string> + <string name="portforward_delete">Ta bort vidarebefodring av port</string> + <string name="prompt_nickname">Namn:</string> + <string name="prompt_nickname_hint_pubkey">Min jobbnyckel</string> + <string name="prompt_source_port">Inkommande portnummer:</string> + <string name="prompt_destination">Destination:</string> + <string name="prompt_old_password">Gammalt lösenord:</string> + <string name="prompt_password">Lösenord:</string> + <string name="prompt_again">(igen)</string> + <string name="prompt_type">Typ:</string> + <string name="prompt_password_can_be_blank">Notis: lösenordet kan vara blankt</string> + <string name="prompt_bits">Bitar:</string> + <string name="prompt_pubkey_password">Lösenord för nyckel \'%1$s\'</string> + <string name="prompt_allow_agent_to_use_key">Tillåt fjärrvärd att\nanvända nyckel \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">VARNING: IDENTIFIERING AV FJÄRRVÄRD HAR ÄNDRATS!</string> + <string name="host_verification_failure_warning">MÖJLIGT INTRÅNGSFÖRSÖK!\nNågon skulle kunna avlyssna dig nu (man-in-the-middle attack)!\nDet är även möjligt att värdnyckeln just har ändrats.</string> + <string name="prompt_host_disconnected">Värden har kopplat ner anslutningen.\nStäng?</string> + <string name="prompt_continue_connecting">Är du säker att du vill\nfortsätta att ansluta?</string> + <string name="host_authenticity_warning">Identiteten av värden \'%1$s\' går ej att fastställa.</string> + <string name="host_fingerprint">Värd %1$s nyckel-fingeravtryck är %2$s</string> + <string name="alert_passwords_do_not_match_msg">Lösenorden överenstämmer inte med varandra!</string> + <string name="alert_wrong_password_msg">Felaktigt lösenord!</string> + <string name="alert_key_corrupted_msg">Privata nyckeln ser ut att vara korrupt!</string> + <string name="alert_sdcard_absent">SD-kort ej isatt!</string> + <string name="button_add">Lägg till</string> + <string name="button_change">Ändra</string> + <string name="button_generate">Generera nyckel</string> + <string name="button_resize">Ändra storlek</string> + <string name="alert_disconnect_msg">Anslutningen förlorad</string> + <string name="msg_copyright">Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Terminalemulering</string> + <string name="pref_emulation_title">Emuleringsläge</string> + <string name="pref_emulation_summary">Emuleringsläge för PTY-anslutningar</string> + <string name="pref_scrollback_title">Textbufferstorlek</string> + <string name="pref_scrollback_summary">Storlek på textbuffer för varje konsol</string> + <string name="pref_ui_category">Användargränssnitt</string> + <string name="pref_rotation_title">Rotationsläge</string> + <string name="pref_rotation_summary">Hur rotation skall ändras när tangentbord aktiveras/avaktiveras</string> + <string name="pref_fullscreen_title">Helskärm</string> + <string name="pref_fullscreen_summary">Göm status medan konsol visas</string> + <string name="pref_memkeys_title">Kom ihåg nycklar</string> + <string name="pref_memkeys_summary">Behåll olåsta nycklar i minnet tills bakände-tjänst avslutas</string> + <string name="pref_conn_persist_title">Kvarstående anslutningar</string> + <string name="pref_conn_persist_summary">Tvinga anslutningar att fortsätta i bakgrunden</string> + <string name="pref_keymode_title">Genvägar för katalog</string> + <string name="pref_keymode_summary">Välj hur Alt används för \'/\' och Shift för Tab</string> + <string name="pref_camera_title">Genväg för kamera</string> + <string name="pref_camera_summary">Välj genväg för kamera-knappen</string> + <string name="pref_keepalive_title">Håll skärmen aktiv</string> + <string name="pref_keepalive_summary">Håll skärmen aktiv medan konsolen används</string> + <string name="pref_wifilock_title">Håll trådlöst nätverk aktivt</string> + <string name="pref_wifilock_summary">Hindra att trådlöst nätverk slås av under en aktiv session</string> + <string name="pref_bumpyarrows_title">Aktiva piltangenter</string> + <string name="pref_bumpyarrows_summary">Vibrera när piltangenter sänds från pekdon; bra för långsamma anslutningar</string> + <string name="pref_bell_category">Terminal-ljud</string> + <string name="pref_bell_title">Aktivt</string> + <string name="pref_bell_volume_title">Ljudvolym</string> + <string name="pref_bell_vibrate_title">Vibrera</string> + <string name="pref_bell_notification_title">Bakgrundsmeddelanden</string> + <string name="pref_bell_notification_summary">Skicka meddelande när en terminal som körs i bakgrunden skickar ljudsignal.</string> + <string name="list_keymode_right">Använd högerknapparna</string> + <string name="list_keymode_left">Använd vänsterknapparna</string> + <string name="list_keymode_none">Avaktivera</string> + <string name="list_pubkeyids_none">Använd inte knapparna</string> + <string name="list_pubkeyids_any">Använd valfri olåst nyckel</string> + <string name="hostpref_nickname_title">Smeknamn</string> + <string name="hostpref_color_title">Färgkategori</string> + <string name="hostpref_fontsize_title">Teckenstorlek</string> + <string name="hostpref_pubkeyid_title">Använd publik nyckelverifiering</string> + <string name="hostpref_authagent_title">Använd SSH-verifieringsagent</string> + <string name="hostpref_postlogin_title">Automatisering efter inloggning</string> + <string name="hostpref_postlogin_summary">Kommandon att köras på fjärrservern när verifierad</string> + <string name="hostpref_compression_title">Kompression</string> + <string name="hostpref_compression_summary">Detta kan hjälpa på långsammare nätverk</string> + <string name="hostpref_wantsession_title">Starta shell-session</string> + <string name="hostpref_wantsession_summary">Inaktivera denna inställning för att bara använda vidarebefodring av portar</string> + <string name="hostpref_stayconnected_title">Fortsätt vara ansluten</string> + <string name="hostpref_stayconnected_summary">Försök återansluta till värden om frånkopplad</string> + <string name="hostpref_delkey_title">DEL-tangent</string> + <string name="hostpref_delkey_summary">Tangentkoden som sänds när DEL-tangenten trycks ner</string> + <string name="hostpref_encoding_title">Teckenkodning</string> + <string name="hostpref_encoding_summary">Teckenkodning för värddatorn</string> + <string name="hostpref_connection_category">Anslutningsinställningar</string> + <string name="hostpref_username_title">Användarnamn</string> + <string name="hostpref_hostname_title">Värd</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Aldrig ansluten</string> + <string name="bind_minutes">%1$s minuter sedan</string> + <string name="bind_hours">%1$s timmar sedan</string> + <string name="bind_days">%1$s dagar sedan</string> + <string name="console_copy_done">Kopierade %1$d bytes till urklippshanteraren.</string> + <string name="console_copy_start">Tryck och drag\neller använd bollen\nför att markera en yta för kopiering</string> + <string name="console_menu_close">Stäng</string> + <string name="console_menu_copy">Kopiera</string> + <string name="console_menu_paste">Klistra in</string> + <string name="console_menu_portforwards">Vidarebefodring av port</string> + <string name="console_menu_resize">Fast storlek</string> + <string name="console_menu_urlscan">URL-sökning</string> + <string name="button_yes">Acceptera</string> + <string name="button_no">Neka</string> + <string name="portforward_local">Lokal</string> + <string name="portforward_remote">Fjärr</string> + <string name="portforward_dynamic">Dynamisk (SOCKS)</string> + <string name="portforward_pos">Skapa vidarebefodring av port</string> + <string name="portforward_done">Vidarebefodring av port skapad.</string> + <string name="portforward_problem">Lyckades inte att skapa en vidarebefodring av port, är den under 1024 eller används den kanske?</string> + <string name="portforward_menu_add">Lägg till vidarebefodring av port.</string> + <string name="hint_userhost">användarnamn\@värdnamn</string> + <string name="list_format_error">Använd formatet %1$s</string> + <string name="format_username">användarnamn</string> + <string name="format_hostname">värdnamn</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Hantera publika nycklar</string> + <string name="list_menu_sortcolor">Sortera efter färg</string> + <string name="list_menu_sortname">Sortera efter namn</string> + <string name="list_menu_settings">Inställningar</string> + <string name="list_host_disconnect">Koppla ner</string> + <string name="list_host_edit">Inställningar för värd</string> + <string name="list_host_portforwards">Ändra vidarebefodringar av portar</string> + <string name="list_host_delete">Ta bort värd</string> + <string name="list_host_empty">Använd snabbanslutningen\nnedan för att ansluta till en värd.</string> + <string name="list_rotation_default">Förvalt värde</string> + <string name="list_rotation_land">Tvinga horisontellt läge</string> + <string name="list_rotation_port">Tvinga vertikalt läge</string> + <string name="list_rotation_auto">Automatiskt</string> + <string name="list_camera_ctrlaspace">Ctrl+A sen Space</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Ingen</string> + <string name="list_delkey_backspace">Baksteg</string> + <string name="list_delkey_del">Ta bort</string> + <string name="delete_message">Är du säker att du vill ta bort \'%1$s\'?</string> + <string name="delete_pos">Ja, ta bort</string> + <string name="delete_neg">Avbryt</string> + <string name="wizard_agree">Godkänn</string> + <string name="wizard_next">Nästa</string> + <string name="wizard_back">Tillbaka</string> + <string name="terminal_no_hosts_connected">Inga värdar anslutna</string> + <string name="terminal_connecting">Ansluter till %1$s:%2$d via %3$s</string> + <string name="terminal_sucess">Verifierad värd \'%1$s\' nyckel: %2$s</string> + <string name="terminal_failed">Verifikation av värd-nyckeln misslyckades.</string> + <string name="terminal_using_s2c_algorithm">Server-till-klient algoritm: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Klient-till-server algoritm: %1$s %2$s</string> + <string name="terminal_using_algorithm">Använder algoritm: %1$s %2$s</string> + <string name="terminal_auth">Försöker att autentisera</string> + <string name="terminal_auth_pass">Försöker med \'password\' autentisering</string> + <string name="terminal_auth_pass_fail">Autentiseringsmetoden \'password\' misslyckades</string> + <string name="terminal_auth_pubkey_any">Försöker \'publik nyckel\' verifiering med en nyckel från det lokala minnet</string> + <string name="terminal_auth_pubkey_invalid">Vald publik nyckel är ogiltig, försök välj ny nyckel i värd redigerare</string> + <string name="terminal_auth_pubkey_specific">Försöker \'publik nyckel\' verifiera med en specifik publik nyckel</string> + <string name="terminal_auth_pubkey_fail">Verifierings metod \'publik nyckel\' med nyckel \'%1$s\' misslyckades</string> + <string name="terminal_auth_ki">Försöker med \'keyboard-interactive\' autentisering</string> + <string name="terminal_auth_ki_fail">Autentiseringsmetoden \'keyboard-interactive\' misslyckades</string> + <string name="terminal_auth_fail">[Din värd stödjer inte \'password\' eller \'keyboard-interactive\' autentiseringsmetoderna]</string> + <string name="terminal_no_session">Session kommer inte att startas på grund av värd önskemål.</string> + <string name="terminal_enable_portfoward">Aktivera vidarebefodring av port: %1$s</string> + <string name="local_shell_unavailable">Misslyckande! Lokal shell är otillgänglig på denna telefon</string> + <string name="notification_text">%1$s vill ha din uppmärksamhet.</string> + <string name="no">Nej</string> + <string name="with_confirmation">Med bekräftelse</string> + <string name="yes">Ja</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> + <string name="menu_colors_reset">Återställ</string> + <string name="app_is_running">ConnectBot är igång</string> + <string name="color_red">röd</string> + <string name="color_green">grön</string> + <string name="color_blue">blå</string> + <string name="color_gray">grå</string> +</resources> diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000..6e1527b --- /dev/null +++ b/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,215 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Basit, güçlü, açık kaynak kodlu SSH client</string> + <string name="service_desc">SSH bağlantılarını ve yüklenmiş açık-anahtarları muhafaza eder</string> + <string name="title_hosts_list">Hostlar</string> + <string name="title_pubkey_list">Açık-anahtarlar</string> + <string name="title_port_forwards_list">Port yönlendirme</string> + <string name="title_host_editor">Host\'u Düzenle</string> + <string name="title_help">Yardım</string> + <string name="title_colors">Renkler</string> + <string name="title_color_picker">Bir renk seç</string> + <string name="resolve_connect">Bağlan</string> + <string name="resolve_entropy">Rastgele girdi topla</string> + <string name="menu_insert">Host ekle</string> + <string name="menu_delete">Host\'u sil</string> + <string name="menu_preferences">Seçenekler</string> + <string name="help_intro">Özel bir konu hakkinda daha fazla bilgi için lütfen aşağıdaki ilgili konuyu seçiniz.</string> + <string name="help_about">ConnectBot Hakkında</string> + <string name="help_keyboard">Klavye</string> + <string name="pubkey_generate">Oluştur</string> + <string name="pubkey_import">Harici dosyadan aktar</string> + <string name="pubkey_delete">Anahtarı Sil</string> + <string name="pubkey_gather_entropy">Entropi Toplanıyor</string> + <string name="pubkey_touch_prompt">Rastgele girdi toplamak için bu kutuya dokun: %1$d%% oluşturuldu</string> + <string name="pubkey_touch_hint">Anahtar oluşturmada gerekli rastgele girdi kaydedebilmemiz için parmağınızı aşağıdaki kutu üzerinde rastgele haraket ettiriniz.</string> + <string name="pubkey_generating">Anahtar çifti oluşturuluyor...</string> + <string name="pubkey_copy_private">Özel-anahtarı kopyala</string> + <string name="pubkey_copy_public">Umumi-anahtarı kopyala</string> + <string name="pubkey_list_empty">Anahtar-çiftini oluşturmak için\nveya dişardan eklemek için \"Menü\"ye dokunun.</string> + <string name="pubkey_unknown_format">Tanınmayan dosya formatı</string> + <string name="pubkey_change_password">Şifreyi değiştir</string> + <string name="pubkey_list_pick">/sdcard dan aktar</string> + <string name="pubkey_import_parse_problem">Dışardan eklenen özel-anahtarı okurken sorun oluştu</string> + <string name="pubkey_unlock">Anahtarın şifresini giriniz</string> + <string name="pubkey_failed_add">Anahtar \'%1$s\' için hatalı şifre verdiniz. Kimlik doğrulama başarısız oldu.</string> + <string name="pubkey_memory_load">Belleğe yükle</string> + <string name="pubkey_memory_unload">Bellekten kaldır</string> + <string name="pubkey_load_on_start">Başlangıçta anahtarı yükle</string> + <string name="pubkey_confirm_use">Kullanım öncesi doğrulama</string> + <string name="portforward_list_empty">Port yönlendirmesi oluşturmak için\n\"Menü\"ye dokunun</string> + <string name="portforward_edit">Port yönlendirmeyi düzenle</string> + <string name="portforward_delete">Port yönlendirmeyi sil</string> + <string name="prompt_nickname">Rumuz:</string> + <string name="prompt_nickname_hint_pubkey">Umumi-anahtar rumuzu</string> + <string name="prompt_source_port">Kaynak kapı:</string> + <string name="prompt_destination">Hedef (Anasistem:Kapı):</string> + <string name="prompt_old_password">Eski şifre:</string> + <string name="prompt_password">Şifre:</string> + <string name="prompt_again">(tekrar)</string> + <string name="prompt_type">Tip:</string> + <string name="prompt_password_can_be_blank">Not: parola boş bırakılabilir</string> + <string name="prompt_bits">İkiller:</string> + <string name="prompt_pubkey_password">Anahtar \'%1$s\' için parola</string> + <string name="prompt_allow_agent_to_use_key">Uzak anasistemin \'%1$s\' anahtarını\nkullanmasına izin ver?</string> + <string name="host_verification_failure_warning_header">UYARI: UZAK ANASİSTEM KİMLİĞİ DEĞİŞMİŞ!</string> + <string name="host_verification_failure_warning">BİRİLERİNİN KÖTÜ BİRŞEYLER YAPIYOR OLMASI OLASI!\nBirileri şu anda sizi dinliyor olabilir (aradaki adam saldırısı)!\nSadece anasistem anahtarı da değişmiş olabilir.</string> + <string name="prompt_host_disconnected">Anasistem bağlantısı kesildi.\nOturumu kapat?</string> + <string name="prompt_continue_connecting">Bağlanmaya devam etmek\nistediğinize emin misiniz?</string> + <string name="host_authenticity_warning">Anasistem \'%1$s\' in gerçekliği doğrulanamadı.</string> + <string name="host_fingerprint">Anasistem %1$s anahtar parmakizi %2$s</string> + <string name="alert_passwords_do_not_match_msg">Parolalar uyuşmuyor!</string> + <string name="alert_wrong_password_msg">Yanlış parola!</string> + <string name="alert_key_corrupted_msg">Kişisel anahtar bozuk görünüyor</string> + <string name="alert_sdcard_absent">SD kart takılmamış!</string> + <string name="button_add">Ekle</string> + <string name="button_change">Değiştir</string> + <string name="button_generate">Anahtar Üret</string> + <string name="button_resize">Yeniden boyutlandır</string> + <string name="alert_disconnect_msg">Bağlantı Kesildi</string> + <string name="msg_copyright">Telif Hakkı © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">Uçbirim öykünümü</string> + <string name="pref_emulation_title">Öykünüm kipi</string> + <string name="pref_emulation_summary">PTY bağlantıları için kullanılan uçbirim öykünüm kipi</string> + <string name="pref_scrollback_title">Geri kaydırma boyu</string> + <string name="pref_scrollback_summary">Her bir konsol için bellekte tutulacak geri kaydırma arabellek boyu</string> + <string name="pref_ui_category">Kullanıcı arayüzü</string> + <string name="pref_rotation_title">Dönme kipi</string> + <string name="pref_rotation_summary">Klavye saklandığında/açıldığında dönüş nasıl değişir</string> + <string name="pref_fullscreen_title">Tüm ekran</string> + <string name="pref_fullscreen_summary">Konsolda iken durum çubuğunu gizle</string> + <string name="pref_keyboard_category">Klavye</string> + <string name="pref_memkeys_title">Anahtarları bellekte tut</string> + <string name="pref_memkeys_summary">Kilidi açılmış anahtarları arkadaki hizmetler sonlanana kadar bellekte tut</string> + <string name="pref_conn_persist_title">Bağlantıları sürekli tut</string> + <string name="pref_conn_persist_summary">Arkaplanda iken bağlantıları bağlı tutmaya zorla</string> + <string name="pref_keymode_title">Dizin kısayolları</string> + <string name="pref_keymode_summary">\'/\' için Alt ve Tab için Shift kullanımının nasıl olacağını seç</string> + <string name="pref_camera_title">Kamera kısayolu</string> + <string name="pref_camera_summary">Kamera düğmesine basıldığında hangi kısayolun tetikleneceğini seç</string> + <string name="pref_keepalive_title">Ekranı açık tut</string> + <string name="pref_keepalive_summary">Bir konsolda çalışırken ekranın kapanmasını engelle</string> + <string name="pref_wifilock_title">Wi-Fi etkin tut</string> + <string name="pref_wifilock_summary">Bir oturum etkinken Wi-Fi kapanmasını engelle</string> + <string name="pref_bumpyarrows_title">dalgalı oklar</string> + <string name="pref_bumpyarrows_summary">Okları izotop ile kullanırken titret; düşük bağlantılar için kullanışlıdır</string> + <string name="pref_bell_category">Terminal zili</string> + <string name="pref_bell_title">Sesli zil</string> + <string name="pref_bell_volume_title">Zil ses düzeyi</string> + <string name="pref_bell_vibrate_title">Titreşim</string> + <string name="pref_bell_notification_title">Arkaplan uyarıları</string> + <string name="pref_bell_notification_summary">Arka planda çalışan terminal zil sesinde uyarı gönder</string> + <string name="list_keymode_right">Sağ taraftaki tuşları kullan</string> + <string name="list_keymode_left">Sol taraftaki tuşları kullan</string> + <string name="list_keymode_none">Devre Dışı Bırak</string> + <string name="list_pubkeyids_none">Tuşları kullanma</string> + <string name="list_pubkeyids_any">Herhangi bir tuşu kullan</string> + <string name="hostpref_nickname_title">Rumuz</string> + <string name="hostpref_color_title">Renk kategorisi</string> + <string name="hostpref_fontsize_title">Yazıtipi boyutu (pt)</string> + <string name="hostpref_pubkeyid_title">Umumi-anahtar doğrulaması yap</string> + <string name="hostpref_authagent_title">SSH auth agent teyidi kullan</string> + <string name="hostpref_postlogin_title">post-login otomasyonu</string> + <string name="hostpref_postlogin_summary">Uzak sunucuya bağlanıldığında çalıştırılacak komutlar</string> + <string name="hostpref_compression_title">Sıkıştırma</string> + <string name="hostpref_compression_summary">Yavaş ağlarda yardımcı olabilir</string> + <string name="hostpref_wantsession_title">Shell oturumu başlat</string> + <string name="hostpref_wantsession_summary">Sadece port yönlendirmeyi kullanmak için bu seçeneği boş bırakın</string> + <string name="hostpref_stayconnected_title">Bağlı kal</string> + <string name="hostpref_stayconnected_summary">Bağlantı koparsa tekrar bağlanmayı dene</string> + <string name="hostpref_delkey_title">DEL tuşu</string> + <string name="hostpref_delkey_summary">DEL yani sil tuşuna basıldığında gönderilecek key kodu</string> + <string name="hostpref_encoding_title">Dil Kodlaması</string> + <string name="hostpref_encoding_summary">Sunucu karakter kodlaması</string> + <string name="hostpref_connection_category">Bağlantı ayarları</string> + <string name="hostpref_username_title">Kullanıcı Adı</string> + <string name="hostpref_hostname_title">Sunucu</string> + <string name="hostpref_port_title">Port</string> + <string name="bind_never">Daha önce bağlanılmadı</string> + <string name="bind_minutes">%1$s dakika önce</string> + <string name="bind_hours">%1$s saat önce</string> + <string name="bind_days">%1$s gün önce</string> + <string name="console_copy_done">%1$d byte panoya kopyalandı</string> + <string name="console_copy_start">Kopyalanacak alanı seçmek için dokun ve sürükle ya da pad kullan</string> + <string name="console_menu_close">Kapat</string> + <string name="console_menu_copy">Kopyala</string> + <string name="console_menu_paste">Yapıştır</string> + <string name="console_menu_portforwards">Port Yönlendirmeleri</string> + <string name="console_menu_resize">Ebat zorunlu</string> + <string name="console_menu_urlscan">URL Tara</string> + <string name="button_yes">Evet</string> + <string name="button_no">Hayır</string> + <string name="portforward_local">Yerel</string> + <string name="portforward_remote">Uzak</string> + <string name="portforward_dynamic">Dinamik (SOCKS)</string> + <string name="portforward_pos">Port yönlendirmesi oluştur</string> + <string name="portforward_done">Port yönlendirmesi başarıyla oluşturuldu</string> + <string name="portforward_problem">Port yönlendirmesi oluşturmada hata oldu, port 1024 altı yada meşgul bir port kullanmakdan kaynaklanabilir</string> + <string name="portforward_menu_add">Port yönlendirmesi ekle</string> + <string name="hint_userhost">kullanici\@sunucuadi</string> + <string name="list_format_error">%1$s formatında kullanın</string> + <string name="format_username">kullanıcı adı</string> + <string name="format_hostname">sunucu adı</string> + <string name="format_port">port</string> + <string name="list_menu_pubkeys">Umumi-anahtarlar Yönetimi</string> + <string name="list_menu_sortcolor">Rengine göre sırala</string> + <string name="list_menu_sortname">İsime göre sırala</string> + <string name="list_menu_settings">Ayarlar</string> + <string name="list_host_disconnect">Bağlantıyı kes</string> + <string name="list_host_edit">Sunucu düzenle</string> + <string name="list_host_portforwards">Port yönlendirmeleri düzenle</string> + <string name="list_host_delete">Sunucuyu sil</string> + <string name="list_host_empty">Sunucuya bağlanmak için\naşağıdaki hızlı bağlan kutucuğunu kullan</string> + <string name="list_rotation_default">Varsayılan</string> + <string name="list_rotation_land">Yatay zorunlu</string> + <string name="list_rotation_port">Dikey zorunlu</string> + <string name="list_rotation_auto">Otomatik</string> + <string name="list_camera_ctrlaspace">Ctrl+A ardından Boşluk</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">Hiçbiri</string> + <string name="list_delkey_backspace">Sil</string> + <string name="list_delkey_del">Sil</string> + <string name="delete_message">\'%1$s\' silinecek onaylıyor musunuz?</string> + <string name="delete_pos">Evet, sil</string> + <string name="delete_neg">İptal Et</string> + <string name="wizard_agree">Kabul et</string> + <string name="wizard_next">İleri</string> + <string name="wizard_back">Geri dön</string> + <string name="terminal_no_hosts_connected">Hiçbir sunucuya bağlanılmadı</string> + <string name="terminal_connecting">%3$s ile %1$s:%2$d bağlanıyor</string> + <string name="terminal_sucess">Host doğrulaması \'%1$s\' anahtar: %2$s</string> + <string name="terminal_failed">Sunucu anahtarı doğrulaması başarısız oldu</string> + <string name="terminal_using_s2c_algorithm">Server-to-client s2c algoritması: %1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">Client-to-server c2s algoritması: %1$s %2$s</string> + <string name="terminal_using_algorithm">Kullanılan algoritma: %1$s %2$s</string> + <string name="terminal_auth">Kimlik doğrulaması yapılıyor</string> + <string name="terminal_auth_pass">\'Şifre\' doğrulaması yapılıyor</string> + <string name="terminal_auth_pass_fail">Kimlik doğrulama \'şifre\' başarısız</string> + <string name="terminal_auth_pubkey_any">Bellekteki herhangi bir umumi-anahtar ile \'umumi-anahtar\' doğrulaması yapılıyor</string> + <string name="terminal_auth_pubkey_invalid">Seçilen umumi-anahtar geçersiz, host düzenleyicideki anahtarı tekrar seçerek deneyiniz</string> + <string name="terminal_auth_pubkey_specific">yerel umumi-anahtar specific public key ile \'publickey\' doğrulaması yapılıyor</string> + <string name="terminal_auth_pubkey_fail">Anahtar \'%1$s\' ile \'publickey\' doğrulama işlemi başarısız</string> + <string name="terminal_auth_ki">\'Klavyeden karşılıklı\' kimlik doğrulaması deneniyor</string> + <string name="terminal_auth_ki_fail">\'Klavyeden karşılıklı\' kimlik doğrulaması işlemi başarısız</string> + <string name="terminal_auth_fail">[Hostunuz \'şifre\' veya \'klavyeden karşılıklı\' kimlik doğrulamasını desteklemiyor.]</string> + <string name="terminal_no_session">Sunucu tercihleri sebebiyle oturum başlatılamıyor</string> + <string name="terminal_enable_portfoward">Port yönlendirmesini etkinleştir: %1$s</string> + <string name="local_shell_unavailable">Başarısız! Yerel shell bu telefonda kullanılamıyor.</string> + <string name="notification_text">%1$s sizi uyarıyor</string> + <string name="no">Hayır</string> + <string name="with_confirmation">teyitli</string> + <string name="yes">Evet</string> + <string name="exceptions_submit_message">ConnectBot son çalıştırıldığında sorunla karşılaştı. Hata raporunu ConnectBot geliştiricilerine iletmek ister misiniz?</string> + <string name="menu_colors_reset">Sıfırla</string> + <string name="app_is_running">ConnectBot çalışıyor</string> + <string name="color_red">kırmızı</string> + <string name="color_green">yeşil</string> + <string name="color_blue">mavi</string> + <string name="color_gray">gri</string> + <string name="colors_fg_label">ÖR: %1$d</string> + <string name="color_bg_label">AR: %1$d</string> + <string name="image_description_connected">Bağlandı.</string> + <string name="image_description_show_keyboard">Klavye göster.</string> +</resources> diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000..f6d929c --- /dev/null +++ b/app/src/main/res/values-uk/strings.xml @@ -0,0 +1,27 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">Простий та потужний SSH клієнт з відкритим кодом</string> + <string name="title_hosts_list">Вузли</string> + <string name="title_pubkey_list">Відкриті ключі</string> + <string name="title_port_forwards_list">Переадресація портів</string> + <string name="title_host_editor">Змінити вузол</string> + <string name="title_help">Допомога</string> + <string name="title_colors">Кольори</string> + <string name="resolve_connect">Підключитись</string> + <string name="menu_insert">Додати вузол</string> + <string name="menu_delete">Вилучити вузол</string> + <string name="menu_preferences">Налаштування</string> + <string name="help_intro">Для додаткової інформації виберіть топік</string> + <string name="help_about">Про ConnectBot</string> + <string name="help_keyboard">Клавіатура</string> + <string name="pubkey_generate">Генерувати</string> + <string name="pubkey_import">Імпортувати</string> + <string name="pubkey_delete">Вилучити ключ</string> + <string name="pubkey_list_empty">Tap Menu to create\nor import key pairs.</string> + <string name="pubkey_change_password">Змінити пароль</string> + <string name="pubkey_list_pick">Завантажити з SD карти</string> + <string name="pubkey_unlock">Розблокувати ключ</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> +</resources> diff --git a/app/src/main/res/values-v11/styles.xml b/app/src/main/res/values-v11/styles.xml new file mode 100644 index 0000000..9a0c22b --- /dev/null +++ b/app/src/main/res/values-v11/styles.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <style name="NoTitle" parent="android:Theme.Holo"> + <item name="android:windowContentOverlay">@null</item> + <item name="android:actionBarStyle">@style/SolidActionBar</item> + </style> + + <style name="SolidActionBar" parent="android:Widget.Holo.ActionBar"> + <item name="android:background">#222222</item> + </style> +</resources> diff --git a/app/src/main/res/values-v14/styles.xml b/app/src/main/res/values-v14/styles.xml new file mode 100644 index 0000000..3969010 --- /dev/null +++ b/app/src/main/res/values-v14/styles.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <style name="NoTitle" parent="android:Theme.DeviceDefault"> + <item name="android:windowContentOverlay">@null</item> + <item name="android:actionBarStyle">@style/SolidActionBar</item> + </style> + + <style name="SolidActionBar" parent="android:Widget.Holo.ActionBar"> + <item name="android:background">#222222</item> + </style> +</resources> diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000..a9c64c2 --- /dev/null +++ b/app/src/main/res/values-vi/strings.xml @@ -0,0 +1,11 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="title_hosts_list">Máy chủ</string> + <string name="title_help">Trợ giúp</string> + <string name="title_colors">Màu sắc</string> + <string name="resolve_connect">Kết nối</string> + <string name="pubkey_list_empty">Tap Menu to create\nor import key pairs.</string> + <string name="portforward_list_empty">Tap Menu to create\nport forwards.</string> + <string name="list_format_error">Use the format %1$s</string> + <string name="exceptions_submit_message">It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?</string> +</resources> diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..61fc9f5 --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,224 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">简洁、强大、开源的SSH客户端</string> + <string name="service_desc">维护SSH连接及已装载的公钥</string> + <string name="title_hosts_list">主机</string> + <string name="title_pubkey_list">公钥</string> + <string name="title_port_forwards_list">端口转发</string> + <string name="title_host_editor">编辑主机</string> + <string name="title_help">帮助</string> + <string name="title_colors">颜色</string> + <string name="title_color_picker">选择一种颜色</string> + <string name="resolve_connect">连接</string> + <string name="resolve_entropy">收集杂乱数</string> + <string name="menu_insert">添加主机</string> + <string name="menu_delete">删除主机</string> + <string name="menu_preferences">设置</string> + <string name="help_intro">请在下面主题中选择一个以查看更多内容</string> + <string name="help_about">关于ConnectBot</string> + <string name="help_keyboard">键盘</string> + <string name="pubkey_generate">产生</string> + <string name="pubkey_import">导入</string> + <string name="pubkey_delete">删除密钥</string> + <string name="pubkey_gather_entropy">正在收集用户熵</string> + <string name="pubkey_touch_prompt">触摸方框来收集随机数:%1$d%完成</string> + <string name="pubkey_touch_hint">请在方框内随机移动手指来生成随机密钥</string> + <string name="pubkey_generating">正在产生密钥配对</string> + <string name="pubkey_copy_private">复制私钥</string> + <string name="pubkey_copy_public">复制公钥</string> + <string name="pubkey_list_empty">点击\"菜单\"来创建或者导入密钥对</string> + <string name="pubkey_unknown_format">未知格式</string> + <string name="pubkey_change_password">更改密码</string> + <string name="pubkey_list_pick">从/sdcard导入</string> + <string name="pubkey_import_parse_problem">导入私钥的时候出现问题</string> + <string name="pubkey_unlock">解锁密钥</string> + <string name="pubkey_failed_add">验证失败,\'%1$s\'密钥密码错误</string> + <string name="pubkey_memory_load">载入到内存</string> + <string name="pubkey_memory_unload">从内存中移除</string> + <string name="pubkey_load_on_start">启动时自动载入公钥</string> + <string name="pubkey_confirm_use">使用前确认</string> + <string name="portforward_list_empty">点击「菜单」来创建端口转发</string> + <string name="portforward_edit">编辑转发端口</string> + <string name="portforward_delete">删除端口转发</string> + <string name="prompt_nickname">昵称:</string> + <string name="prompt_nickname_hint_pubkey">我的工作密钥</string> + <string name="prompt_source_port">源端口:</string> + <string name="prompt_destination">目标端口:</string> + <string name="prompt_old_password">旧密码:</string> + <string name="prompt_password">新密码:</string> + <string name="prompt_again">确认新密码</string> + <string name="prompt_type">转发类型</string> + <string name="prompt_password_can_be_blank">注意:密码可以为空</string> + <string name="prompt_bits">位数:</string> + <string name="prompt_pubkey_password">密钥「%1$s」的密码</string> + <string name="prompt_allow_agent_to_use_key">允许远程主机使用\'%1$s\'密钥?</string> + <string name="host_verification_failure_warning_header">警告:远程主机身份标识已经改变</string> + <string name="host_verification_failure_warning">可能某人正在做非善意的事情\n可能有人正在偷听你的会话(中间人攻击)\n但也许仅仅是主密匙(host key)已被更改,你确信如此?</string> + <string name="prompt_host_disconnected">主机已经断开。\n是否关闭本次对话?</string> + <string name="prompt_continue_connecting">是否要继续连接?</string> + <string name="host_authenticity_warning">主机「%1$s」的验证无法建立</string> + <string name="host_fingerprint">主机 %1$s 密钥指纹是 %2$s</string> + <string name="alert_passwords_do_not_match_msg">密码不匹配!</string> + <string name="alert_wrong_password_msg">密码错误</string> + <string name="alert_key_corrupted_msg">私钥似乎已破坏</string> + <string name="alert_sdcard_absent">没有插入SD卡!</string> + <string name="button_add">添加</string> + <string name="button_change">修改</string> + <string name="button_generate">生成密钥</string> + <string name="button_resize">改变窗口大小</string> + <string name="alert_disconnect_msg">连接中断</string> + <string name="msg_copyright">版权所有 ©2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">模拟终端</string> + <string name="pref_emulation_title">仿真模式</string> + <string name="pref_emulation_summary">设置使用PTY连接时的终端模式</string> + <string name="pref_scrollback_title">回滚长度</string> + <string name="pref_scrollback_summary">在内存中为每个控制台保留的回滚缓存长度</string> + <string name="pref_ui_category">用户界面</string> + <string name="pref_rotation_title">循环模式</string> + <string name="pref_rotation_summary">当键盘打开或关闭时屏幕如何旋转(水平或竖直)</string> + <string name="pref_fullscreen_title">全屏</string> + <string name="pref_fullscreen_summary">处于控制台时隐藏状态栏</string> + <string name="pref_keyboard_category">键盘</string> + <string name="pref_shiftfkeys_title">Shift+num(数字键) 作为 功能键</string> + <string name="pref_shiftfkeys_summary">在硬件键盘上,数字键和 shift 键组合发送 F1-F10</string> + <string name="pref_ctrlfkeys_title">Ctrl+num(数字键) 作为 功能键</string> + <string name="pref_ctrlfkeys_summary">在软件键盘上,数字键和 ctrl 键组合发送 F1-F10</string> + <string name="pref_volumefont_title">音量键用来改变字体大小</string> + <string name="pref_volumefont_summary">字体大小也可以在每个主机的设置中改变</string> + <string name="pref_memkeys_title">在内存中记录密钥</string> + <string name="pref_memkeys_summary">保留解锁的密匙于内存中直到后台服务停止</string> + <string name="pref_conn_persist_title">保持联机</string> + <string name="pref_conn_persist_summary">切换到后台时强制保持连接</string> + <string name="pref_keymode_title">目录快捷方式</string> + <string name="pref_keymode_summary">选择如何使用 Alt 代表 /,Shift 代表 Tab</string> + <string name="pref_camera_title">摄像快捷键</string> + <string name="pref_camera_summary">选择照相键按下时触发的快捷功能</string> + <string name="pref_keepalive_title">保持屏幕唤醒</string> + <string name="pref_keepalive_summary">终端运行时禁止屏幕休眠</string> + <string name="pref_wifilock_title">保持WI-FI激活</string> + <string name="pref_wifilock_summary">会话运行时禁止Wi-Fi关闭</string> + <string name="pref_bumpyarrows_title">触摸反馈</string> + <string name="pref_bumpyarrows_summary">轨迹球发出模拟方向键时振动; (对滞后连接特别有用)</string> + <string name="pref_bell_category">终端响铃</string> + <string name="pref_bell_title">响铃</string> + <string name="pref_bell_volume_title">铃声音量</string> + <string name="pref_bell_vibrate_title">响铃时振动</string> + <string name="pref_bell_notification_title">背景通知</string> + <string name="pref_bell_notification_summary">当终端在后台响铃时发出通知</string> + <string name="list_keymode_right">使用右边键盘</string> + <string name="list_keymode_left">使用左边键盘</string> + <string name="list_keymode_none">禁用</string> + <string name="list_pubkeyids_none">不使用密匙</string> + <string name="list_pubkeyids_any">使用解锁密匙</string> + <string name="hostpref_nickname_title">昵称</string> + <string name="hostpref_color_title">颜色类型</string> + <string name="hostpref_fontsize_title">字型大小 (pt)</string> + <string name="hostpref_pubkeyid_title">使用公钥验证</string> + <string name="hostpref_authagent_title">使用 SSH 认证</string> + <string name="hostpref_postlogin_title">登录后自动运行</string> + <string name="hostpref_postlogin_summary">登录后在远程服务器上自动运行命令</string> + <string name="hostpref_compression_title">压缩</string> + <string name="hostpref_compression_summary">此选项对低速网络会有帮助</string> + <string name="hostpref_wantsession_title">开始Shell会话</string> + <string name="hostpref_wantsession_summary">仅仅对端口转发禁用偏好设置</string> + <string name="hostpref_stayconnected_title">保持连接</string> + <string name="hostpref_stayconnected_summary">断线重连</string> + <string name="hostpref_delkey_title">DEL键</string> + <string name="hostpref_delkey_summary">按下DEL键时发送给服务器的键值</string> + <string name="hostpref_encoding_title">编码</string> + <string name="hostpref_encoding_summary">主机的字符编码</string> + <string name="hostpref_connection_category">连接设置</string> + <string name="hostpref_username_title">用户名</string> + <string name="hostpref_hostname_title">主机</string> + <string name="hostpref_port_title">端口</string> + <string name="bind_never">尚未连接</string> + <string name="bind_minutes">已连接 %1$s 分钟</string> + <string name="bind_hours">已连接 %1$s 小时</string> + <string name="bind_days">已连接 %1$s 天</string> + <string name="console_copy_done">已复制%1$d位</string> + <string name="console_copy_start">点击拖动\n或使用触摸板\n来选择要复制的区域</string> + <string name="console_menu_close">关闭</string> + <string name="console_menu_copy">复制</string> + <string name="console_menu_paste">粘贴</string> + <string name="console_menu_portforwards">端口转发</string> + <string name="console_menu_resize">指定大小</string> + <string name="console_menu_urlscan">URL 地址扫描</string> + <string name="button_yes">确认</string> + <string name="button_no">取消</string> + <string name="portforward_local">本地</string> + <string name="portforward_remote">远端</string> + <string name="portforward_dynamic">动态套接字(SOCKS)</string> + <string name="portforward_pos">创建端口转发</string> + <string name="portforward_done">创建端口转发成功</string> + <string name="portforward_problem">创建端口转发失败,检查你选择的端口是否小于1024或该端口已被占用</string> + <string name="portforward_menu_add">添加端口转发</string> + <string name="hint_userhost">用户名\@主机名</string> + <string name="list_format_error">使用%1$s格式</string> + <string name="format_username">用户名</string> + <string name="format_hostname">主机名</string> + <string name="format_port">端口</string> + <string name="list_menu_pubkeys">管理公钥</string> + <string name="list_menu_sortcolor">按颜色排序</string> + <string name="list_menu_sortname">按名称排序</string> + <string name="list_menu_settings">设置</string> + <string name="list_host_disconnect">断开连接</string> + <string name="list_host_edit">编辑主机</string> + <string name="list_host_portforwards">编辑端口转发</string> + <string name="list_host_delete">删除主机</string> + <string name="list_host_empty">使用下面的快速连接框连接主机</string> + <string name="list_rotation_default">默认</string> + <string name="list_rotation_land">强制横屏显示</string> + <string name="list_rotation_port">强制竖屏显示</string> + <string name="list_rotation_auto">自动</string> + <string name="list_camera_ctrlaspace">Ctrl+A 然后 Space</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc 键</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">无</string> + <string name="list_delkey_backspace">退格键</string> + <string name="list_delkey_del">删除</string> + <string name="delete_message">确定要删除\'%1$s\'吗?</string> + <string name="delete_pos">是,删除</string> + <string name="delete_neg">取消</string> + <string name="wizard_agree">同意</string> + <string name="wizard_next">下一页</string> + <string name="wizard_back">返回</string> + <string name="terminal_no_hosts_connected">当前没有连接主机</string> + <string name="terminal_connecting">正在通过%3$s连接到 %1$s:%2$d</string> + <string name="terminal_sucess">验证主机 %1$s 键值: %2$s</string> + <string name="terminal_failed">主机验证失败</string> + <string name="terminal_using_s2c_algorithm">服务器到客户端算法:%1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">客户端到服务器算法:%1$s %2$s</string> + <string name="terminal_using_algorithm">使用算法:%1$s %2$s</string> + <string name="terminal_auth">尝试验证</string> + <string name="terminal_auth_pass">尝试进行用户口令认证</string> + <string name="terminal_auth_pass_fail">密码验证失败</string> + <string name="terminal_auth_pubkey_any">尝试使用保存在内存中的公匙进行用户认证</string> + <string name="terminal_auth_pubkey_invalid">所选公钥失效,请在主机中重新选择</string> + <string name="terminal_auth_pubkey_specific">正在以公钥认证方式认证所指定的公钥</string> + <string name="terminal_auth_pubkey_fail">公钥%1$s认证失败</string> + <string name="terminal_auth_ki">尝试键盘交互式认证</string> + <string name="terminal_auth_ki_fail">键盘交互式认证失败</string> + <string name="terminal_auth_fail">[该主机不支持用户口令认证或键盘交互式认证]</string> + <string name="terminal_no_session">由于主机设定不起动该会话</string> + <string name="terminal_enable_portfoward">启用端口转发:%1$s</string> + <string name="local_shell_unavailable">失败! 本地Shell在该手机上不可用</string> + <string name="notification_text">%1$s 需要你的注意</string> + <string name="no">不</string> + <string name="with_confirmation">需要确认</string> + <string name="yes">确定</string> + <string name="exceptions_submit_message">ConnectBot上次运行时出现异常, 是否提交报告至ConnectBot开发者?</string> + <string name="menu_colors_reset">重置</string> + <string name="app_is_running">ConnectBot 正在运行</string> + <string name="color_red">红色</string> + <string name="color_green">绿色</string> + <string name="color_blue">蓝色</string> + <string name="color_gray">灰色</string> + <string name="colors_fg_label">前景色:%1$d</string> + <string name="color_bg_label">背景色:%1$d</string> + <string name="image_description_connected">已连接。</string> + <string name="image_description_key_is_locked">密钥已锁。</string> + <string name="image_description_toggle_control_character">切换控制字符。</string> + <string name="image_description_send_escape_character">发送转义字符。</string> + <string name="image_description_show_keyboard">显示键盘。</string> +</resources> diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml new file mode 100644 index 0000000..94edc23 --- /dev/null +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources/> diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..bbfd6f0 --- /dev/null +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,227 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="app_desc">簡單、強大、開放原始碼的SSH用戶端。</string> + <string name="service_desc">管理 SSH 連線及載入公鑰</string> + <string name="title_hosts_list">主機清單</string> + <string name="title_pubkey_list">公鑰</string> + <string name="title_port_forwards_list">連接埠轉址</string> + <string name="title_host_editor">編輯主機資訊</string> + <string name="title_help">說明</string> + <string name="title_colors">顏色</string> + <string name="title_color_picker">挑選顏色</string> + <string name="resolve_connect">連線</string> + <string name="resolve_entropy">收集用戶熵</string> + <string name="menu_insert">新增主機</string> + <string name="menu_delete">刪除主機</string> + <string name="menu_preferences">設定</string> + <string name="help_intro">請在下列選擇一個主題以查看更多內容。</string> + <string name="help_about">關於 ConnectBot</string> + <string name="help_keyboard">鍵盤</string> + <string name="pubkey_generate">產生</string> + <string name="pubkey_import">匯入</string> + <string name="pubkey_delete">刪除金鑰</string> + <string name="pubkey_gather_entropy">正在收集用戶熵</string> + <string name="pubkey_touch_prompt">觸摸此方框來收集隨機數:%1$d%完成</string> + <string name="pubkey_touch_hint">由於金鑰產生的時候需要隨機資訊,請在下面的方框中隨意移動您的手指。</string> + <string name="pubkey_generating">正在產生金鑰對...</string> + <string name="pubkey_copy_private">複製私鑰</string> + <string name="pubkey_copy_public">複製公鑰</string> + <string name="pubkey_list_empty">按「選單」鍵來產生\n或匯入金鑰對。</string> + <string name="pubkey_unknown_format">未知格式</string> + <string name="pubkey_change_password">變更密碼</string> + <string name="pubkey_list_pick">從 /sdcard 選擇</string> + <string name="pubkey_import_parse_problem">匯入私鑰時發生問題</string> + <string name="pubkey_unlock">解鎖金鑰</string> + <string name="pubkey_failed_add">驗證失敗,錯誤的的金鑰密碼「%1$s」</string> + <string name="pubkey_memory_load">載入到記憶體中</string> + <string name="pubkey_memory_unload">從記憶體中卸載</string> + <string name="pubkey_load_on_start">啟動時載入金鑰</string> + <string name="pubkey_confirm_use">使用前確認</string> + <string name="portforward_list_empty">按「選單」鍵來產生\n連接埠轉址。</string> + <string name="portforward_edit">編輯連接埠轉址</string> + <string name="portforward_delete">刪除連接埠轉址</string> + <string name="prompt_nickname">暱稱:</string> + <string name="prompt_nickname_hint_pubkey">我的工作金鑰</string> + <string name="prompt_source_port">來源連接埠:</string> + <string name="prompt_destination">目的連接埠:</string> + <string name="prompt_old_password">舊密碼:</string> + <string name="prompt_password">密碼:</string> + <string name="prompt_again">(再輸入一遍密碼)</string> + <string name="prompt_type">轉址類型:</string> + <string name="prompt_password_can_be_blank">注意:密碼可以為空白</string> + <string name="prompt_bits">位元:</string> + <string name="prompt_pubkey_password">公鑰「%1$s」的密碼</string> + <string name="prompt_allow_agent_to_use_key">允許遠端主機\n使用金鑰 \'%1$s\'?</string> + <string name="host_verification_failure_warning_header">警告:遠端主機的識別已變更!</string> + <string name="host_verification_failure_warning">可能某人正在做非善意的事情!\n可能有人正在側錄您的連線(中間攻擊法)!\n但也許僅僅是主機金鑰已變更。</string> + <string name="prompt_host_disconnected">主機已經中斷。\n是否關閉本次連線階段?</string> + <string name="prompt_continue_connecting">是否要繼續連線?</string> + <string name="host_authenticity_warning">主機「%1$s」的驗證無法建立</string> + <string name="host_fingerprint">主機 %1$s 金鑰指紋是 %2$s</string> + <string name="alert_passwords_do_not_match_msg">密碼不符!</string> + <string name="alert_wrong_password_msg">錯誤的密碼!</string> + <string name="alert_key_corrupted_msg">私鑰已經損壞!</string> + <string name="alert_sdcard_absent">沒有插入 SD 卡!</string> + <string name="button_add">新增</string> + <string name="button_change">變更</string> + <string name="button_generate">產生金鑰</string> + <string name="button_resize">調整大小</string> + <string name="alert_disconnect_msg">連線中斷</string> + <string name="msg_copyright">版權所有 © 2007-2008 Kenny Root http://the-b.org/ , Jeffrey Sharkey http://jsharkey.org/</string> + <string name="pref_emulation_category">終端模擬器</string> + <string name="pref_emulation_title">終端機類型</string> + <string name="pref_emulation_summary">設置使用 PTY 連接時的終端模式</string> + <string name="pref_scrollback_title">回溯記憶體大小</string> + <string name="pref_scrollback_summary">在記憶體中為每個控制台保留的回溯緩衝大小</string> + <string name="pref_ui_category">使用者介面</string> + <string name="pref_rotation_title">螢幕旋轉模式</string> + <string name="pref_rotation_summary">當鍵盤視窗出現或隱藏時要如何改變螢幕轉向</string> + <string name="pref_titlebarhide_title">自動隱藏標題列</string> + <string name="pref_titlebarhide_summary">輕觸終端機來顯示標題列並存取選單</string> + <string name="pref_fullscreen_title">全螢幕</string> + <string name="pref_fullscreen_summary">當在控制台時隱藏狀態列</string> + <string name="pref_keyboard_category">鍵盤</string> + <string name="pref_shiftfkeys_title">Shift+num 為功能鍵</string> + <string name="pref_shiftfkeys_summary">在硬體鍵盤上,shift 加數字鍵時傳送 F1-F10</string> + <string name="pref_ctrlfkeys_title">Ctrl+num 為功能鍵</string> + <string name="pref_ctrlfkeys_summary">在軟體鍵盤上,ctrl 加數字時傳送 F1-F10</string> + <string name="pref_volumefont_title">音量鍵更改字體大小</string> + <string name="pref_volumefont_summary">字體大小可以在各主機設定內更改</string> + <string name="pref_memkeys_title">將金鑰儲存在記憶體</string> + <string name="pref_memkeys_summary">保持記憶體中的金鑰是解鎖狀態,直到背景服務結束</string> + <string name="pref_conn_persist_title">保持連線</string> + <string name="pref_conn_persist_summary">在背景執行時仍保持連線狀態</string> + <string name="pref_keymode_title">快捷鍵</string> + <string name="pref_keymode_summary">選擇如何使用 Alt 來叫出 \'/\' 和用 Shift 呼叫 Tab</string> + <string name="only_alt">只有Alt</string> + <string name="pref_camera_title">照相快捷鍵</string> + <string name="pref_camera_summary">選擇當照相按鈕按下時將要觸發的快捷鍵</string> + <string name="pref_keepalive_title">保持螢幕喚醒</string> + <string name="pref_keepalive_summary">執行控制台時不關閉螢幕</string> + <string name="pref_wifilock_title">保持 Wi-Fi 連線</string> + <string name="pref_wifilock_summary">連線階段未結束前,避免 Wi-Fi 關閉</string> + <string name="pref_bumpyarrows_title">觸覺方向鍵</string> + <string name="pref_bumpyarrows_summary">當用軌跡球發出模擬方向鍵時振動; 對延遲連線特別有用</string> + <string name="pref_bell_category">終端鈴聲</string> + <string name="pref_bell_title">響鈴</string> + <string name="pref_bell_volume_title">鈴聲音量</string> + <string name="pref_bell_vibrate_title">振動同時響鈴</string> + <string name="pref_bell_notification_title">背景通知</string> + <string name="pref_bell_notification_summary">當背景終端響鈴時發出通知。</string> + <string name="list_keymode_right">使用右側按鍵</string> + <string name="list_keymode_left">使用左側按鍵</string> + <string name="list_keymode_none">停用</string> + <string name="list_pubkeyids_none">不使用公鑰</string> + <string name="list_pubkeyids_any">使用任何解鎖金鑰</string> + <string name="hostpref_nickname_title">暱稱</string> + <string name="hostpref_color_title">顏色類型</string> + <string name="hostpref_fontsize_title">字體大小 (pt)</string> + <string name="hostpref_pubkeyid_title">使用公鑰驗證</string> + <string name="hostpref_authagent_title">使用 SSH 認證</string> + <string name="hostpref_postlogin_title">登入後自動執行</string> + <string name="hostpref_postlogin_summary">每次驗證後在遠端伺服器上執行的命令</string> + <string name="hostpref_compression_title">壓縮</string> + <string name="hostpref_compression_summary">此選項對低速網路會有幫助</string> + <string name="hostpref_wantsession_title">開始 Shell 連線階段</string> + <string name="hostpref_wantsession_summary">停用本偏好設定來僅使用連接埠轉址</string> + <string name="hostpref_stayconnected_title">保持連線</string> + <string name="hostpref_stayconnected_summary">斷線後與主機重新連線</string> + <string name="hostpref_delkey_title">DEL 鍵</string> + <string name="hostpref_delkey_summary">當按下 DEL 鍵時,傳送金鑰代碼</string> + <string name="hostpref_encoding_title">編碼</string> + <string name="hostpref_encoding_summary">主機的字元編碼</string> + <string name="hostpref_connection_category">連線設定</string> + <string name="hostpref_username_title">使用者名稱</string> + <string name="hostpref_hostname_title">主機</string> + <string name="hostpref_port_title">連接埠</string> + <string name="bind_never">從未連線</string> + <string name="bind_minutes">%1$s 分鐘前連線</string> + <string name="bind_hours">%1$s 小時前連線</string> + <string name="bind_days">%1$s 天前連線</string> + <string name="console_copy_done">複製 %1$d 位元組到剪貼簿</string> + <string name="console_copy_start">滑動手指\n或使用軌跡球\n選擇要複製的區域</string> + <string name="console_menu_close">關閉</string> + <string name="console_menu_copy">複製</string> + <string name="console_menu_paste">貼上</string> + <string name="console_menu_portforwards">連接埠轉址</string> + <string name="console_menu_resize">字體大小</string> + <string name="console_menu_urlscan">URL 掃描</string> + <string name="button_yes">是</string> + <string name="button_no">否</string> + <string name="portforward_local">本機</string> + <string name="portforward_remote">遠端</string> + <string name="portforward_dynamic">動態 (SOCKS)</string> + <string name="portforward_pos">建立連接埠轉址</string> + <string name="portforward_done">成功建立連接埠轉址</string> + <string name="portforward_problem">新增連接埠時發生問題,可能您正在使用 1024 以下的埠或是埠被佔用?</string> + <string name="portforward_menu_add">新增連接埠轉址</string> + <string name="hint_userhost">使用者名稱\@主機名稱</string> + <string name="list_format_error">使用 %1$s 格式</string> + <string name="format_username">使用者名稱</string> + <string name="format_hostname">主機名稱</string> + <string name="format_port">連接埠</string> + <string name="list_menu_pubkeys">管理公鑰</string> + <string name="list_menu_sortcolor">按顏色排序</string> + <string name="list_menu_sortname">按名字排序</string> + <string name="list_menu_settings">設定</string> + <string name="list_host_disconnect">中斷連線</string> + <string name="list_host_edit">編輯主機</string> + <string name="list_host_portforwards">編輯連接埠轉址</string> + <string name="list_host_delete">刪除主機</string> + <string name="list_host_empty">使用下面的快速連接框連接主機</string> + <string name="list_rotation_default">預設值</string> + <string name="list_rotation_land">強制橫屏顯示</string> + <string name="list_rotation_port">強制竪屏顯示</string> + <string name="list_rotation_auto">自動</string> + <string name="list_camera_ctrlaspace">Ctrl+A 然後 Space</string> + <string name="list_camera_ctrla">Ctrl+A</string> + <string name="list_camera_esc">Esc</string> + <string name="list_camera_esc_a">Esc+A</string> + <string name="list_camera_none">無</string> + <string name="list_delkey_backspace">退格</string> + <string name="list_delkey_del">刪除</string> + <string name="delete_message">您確定要刪除 \'%1$s\'</string> + <string name="delete_pos">是,刪除</string> + <string name="delete_neg">取消</string> + <string name="wizard_agree">同意</string> + <string name="wizard_next">下一步</string> + <string name="wizard_back">返回</string> + <string name="terminal_no_hosts_connected">目前沒有已連線主機</string> + <string name="terminal_connecting">連接到 %1$s:%2$d,通過 %3$s</string> + <string name="terminal_sucess">驗證主機 \'%1$s\' 金鑰: %2$s</string> + <string name="terminal_failed">主機驗證失敗</string> + <string name="terminal_using_s2c_algorithm">伺服器到用戶端算法:%1$s %2$s</string> + <string name="terminal_using_c2s_algorithm">用戶端到伺服器算法:%1$s %2$s</string> + <string name="terminal_using_algorithm">使用演算法:%1$s %2$s</string> + <string name="terminal_auth">嘗試驗證</string> + <string name="terminal_auth_pass">正在嘗試 \'密碼\' 認證</string> + <string name="terminal_auth_pass_fail">認證方法 \'密碼\' 失敗</string> + <string name="terminal_auth_pubkey_any">正在嘗試 \'公鑰\' 認證暨記憶體中任何公鑰</string> + <string name="terminal_auth_pubkey_invalid">所選擇的公鑰無法使用,請在主機編輯中嘗試重新選擇</string> + <string name="terminal_auth_pubkey_specific">正在嘗試 \'公鑰\' 認證暨指定公鑰</string> + <string name="terminal_auth_pubkey_fail">認證方法 \'公鑰\' 暨金鑰 \'%1$s\' 失敗</string> + <string name="terminal_auth_ki">正在嘗試 \'keyboard-interactive\' 認證</string> + <string name="terminal_auth_ki_fail">認證方法 \'keyboard-interactive\' 失敗</string> + <string name="terminal_auth_fail">[您的主機不支援 \'密碼\' 或 \'keyboard-interactive\' 驗證機制。]</string> + <string name="terminal_no_session">因為主機的相關設置,不會啓動會話階段。</string> + <string name="terminal_enable_portfoward">啓用連接埠轉址:%1$s</string> + <string name="local_shell_unavailable">失敗!此手機 Shell 不可用。</string> + <string name="notification_text">%1$s 需要您的注意。</string> + <string name="no">否</string> + <string name="with_confirmation">已確認過</string> + <string name="yes">是</string> + <string name="exceptions_submit_message">ConnectBot 在上次執行時出現異常。回報給 ConnectBot 開發者?</string> + <string name="menu_colors_reset">重設</string> + <string name="app_is_running">ConnectBot 執行中</string> + <string name="color_red">紅</string> + <string name="color_green">綠</string> + <string name="color_blue">藍</string> + <string name="color_gray">灰</string> + <string name="colors_fg_label">前景:%1$d</string> + <string name="color_bg_label">背景:%1$d</string> + <string name="image_description_connected">已連線。</string> + <string name="image_description_key_is_locked">金鑰已鎖定。</string> + <string name="image_description_toggle_control_character">切換控制字元</string> + <string name="image_description_send_escape_character">送出 Esc 字元。</string> + <string name="image_description_show_keyboard">顯示鍵盤。</string> +</resources> diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..4721490 --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="list_emulation_modes" translatable="false"> + <item>xterm-color</item> + <item>xterm-256color</item> + <item>xterm</item> + <item>vt100</item> + <item>ansi</item> + <item>screen</item> + </string-array> + + <string-array name="list_rotation" translatable="false"> + <item>@string/list_rotation_default</item> + <item>@string/list_rotation_land</item> + <item>@string/list_rotation_port</item> + <item>@string/list_rotation_auto</item> + </string-array> + + <string-array name="list_rotation_values" translatable="false"> + <item>Default</item> + <item>Force landscape</item> + <item>Force portrait</item> + <item>Automatic</item> + </string-array> + + <string-array name="list_camera" translatable="false"> + <item>@string/list_camera_ctrlaspace</item> + <item>@string/list_camera_ctrla</item> + <item>@string/list_camera_esc</item> + <item>@string/list_camera_esc_a</item> + <item>@string/list_camera_none</item> + </string-array> + + <string-array name="list_camera_values" translatable="false"> + <item>Ctrl+A then Space</item> + <item>Ctrl+A</item> + <item>Esc</item> + <item>Esc+A</item> + <item>None</item> + </string-array> + + <string-array name="list_colors" translatable="false"> + <item>@string/color_red</item> + <item>@string/color_green</item> + <item>@string/color_blue</item> + <item>@string/color_gray</item> + </string-array> + + <string-array name="list_color_values" translatable="false"> + <item>red</item> + <item>green</item> + <item>blue</item> + <item>gray</item> + </string-array> + + <string-array name="list_stickymodifiers" translatable="false"> + <item>@string/no</item> + <item>@string/only_alt</item> + <item>@string/yes</item> + </string-array> + + <string-array name="list_stickymodifiers_values" translatable="false"> + <item>no</item> + <item>alt</item> + <item>yes</item> + </string-array> + + <string-array name="list_keymode" translatable="false"> + <item>@string/list_keymode_right</item> + <item>@string/list_keymode_left</item> + <item>@string/list_keymode_none</item> + </string-array> + + <string-array name="list_keymode_values" translatable="false"> + <item>Use right-side keys</item> + <item>Use left-side keys</item> + <item>none</item> + </string-array> + + <string-array name="list_pubkeyids" translatable="false"> + <item>@string/list_pubkeyids_none</item> + <item>@string/list_pubkeyids_any</item> + </string-array> + + <string-array name="list_pubkeyids_value" translatable="false"> + <item>-2</item> + <item>-1</item> + </string-array> + + <string-array name="list_authagent" translatable="false"> + <item>@string/no</item> + <item>@string/with_confirmation</item> + <item>@string/yes</item> + </string-array> + + <string-array name="list_authagent_values" translatable="false"> + <item>no</item> + <item>confirm</item> + <item>yes</item> + </string-array> + + <string-array name="list_portforward_types" translatable="false"> + <item>@string/portforward_local</item> + <item>@string/portforward_remote</item> + <item>@string/portforward_dynamic</item> + </string-array> + + <string-array name="list_wizard_topics" translatable="false"> + <item>Hints</item> + <item>PhysicalKeyboard</item> + <item>VirtualKeyboard</item> + </string-array> + + <string-array name="list_delkey" translatable="false"> + <item>@string/list_delkey_del</item> + <item>@string/list_delkey_backspace</item> + </string-array> + + <string-array name="list_delkey_values" translatable="false"> + <item>del</item> + <item>backspace</item> + </string-array> + +</resources> diff --git a/app/src/main/res/values/notrans.xml b/app/src/main/res/values/notrans.xml new file mode 100644 index 0000000..3142ed6 --- /dev/null +++ b/app/src/main/res/values/notrans.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <string name="app_name" translatable="false">ConnectBot</string> + + <string name="copyright_info" translatable="false">Before we get started, we need to get some legal information out of the way. ConnectBot is provided under the Apache License, Version 2.0 (the “License”). Here are a few key points:\n\nYou may not use this program except in compliance with the License. You may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.</string> + +</resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..49c3af4 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,513 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Summary of what ConnectBot is; used as a short description in the Android running apps list --> + <string name="app_desc">"Simple, powerful, open-source SSH client."</string> + + <!-- Summary of what the ConnectBot service does; displayed in the Android running apps list --> + <string name="service_desc">"Maintains SSH connections and loaded pubkeys"</string> + + <!-- Window title for the Host List --> + <string name="title_hosts_list">"Hosts"</string> + <!-- Window title for the Pubkeys List --> + <string name="title_pubkey_list">"Pubkeys"</string> + <!-- Window title for the Port Forwards List --> + <string name="title_port_forwards_list">"Port forwards"</string> + <!-- Window title when editing host details --> + <string name="title_host_editor">"Edit Host"</string> + <!-- Window title for Help index --> + <string name="title_help">"Help"</string> + <!-- Window title for color list editing screen --> + <string name="title_colors">"Colors"</string> + <!-- Dialog title for color picker dialog --> + <string name="title_color_picker">"Pick a color"</string> + + <string name="resolve_connect">"Connect"</string> + <!-- Menu selection where user must move finger randomly over an area to gather entropy (collect random bits) --> + <string name="resolve_entropy">"Gather Entropy"</string> + + <string name="menu_insert">"Add Host"</string> + <string name="menu_delete">"Delete Host"</string> + <string name="menu_preferences">"Preferences"</string> + + <string name="help_intro">"Please select a topic below for more information on a particular subject."</string> + <string name="help_about">"About ConnectBot"</string> + <string name="help_keyboard">"Keyboard"</string> + + <string name="pubkey_generate">"Generate"</string> + <string name="pubkey_import">"Import"</string> + <string name="pubkey_delete">"Delete key"</string> + <!-- Dialog title when user must move finger randomly over an area to gather entropy (collect random bits) --> + <string name="pubkey_gather_entropy">"Gathering Entropy"</string> + <string name="pubkey_touch_prompt">"Touch this box to gather randomness: %1$d%% done"</string> + <string name="pubkey_touch_hint">"In order to assure randomness during the key generation, move your finger randomly over the box below."</string> + <string name="pubkey_generating">"Generating key pair..."</string> + <string name="pubkey_copy_private">"Copy private key"</string> + <string name="pubkey_copy_public">"Copy public key"</string> + <!-- Note that the '\n' just splits lines, so it's actually "create or import" --> + <string name="pubkey_list_empty">"Tap \"Menu\" to create"\n"or import key pairs."</string> + <string name="pubkey_unknown_format">"Unknown format"</string> + <string name="pubkey_change_password">"Change password"</string> + <string name="pubkey_list_pick">"Pick from /sdcard"</string> + <string name="pubkey_import_parse_problem">"Problem parsing imported private key"</string> + <string name="pubkey_unlock">"Unlock key"</string> + <string name="pubkey_failed_add">"Bad password for key '%1$s'. Authentication failed."</string> + <string name="pubkey_memory_load">"Load into memory"</string> + <string name="pubkey_memory_unload">"Unload from memory"</string> + <string name="pubkey_load_on_start">"Load key on start"</string> + <!-- Pubkey preference asking user whether the key use should be confirmed via prompt before it can be used for authentication --> + <string name="pubkey_confirm_use">"Confirm before use"</string> + + <!-- Note that the '\n' splits lines, so it's actually "create port forwards" --> + <string name="portforward_list_empty">"Tap \"Menu\" to create"\n"port forwards."</string> + <string name="portforward_edit">"Edit port forward"</string> + <string name="portforward_delete">"Delete port forward"</string> + + <string name="prompt_nickname">"Nickname:"</string> + <!-- An example string that could be used as a nickname for a pubkey. --> + <string name="prompt_nickname_hint_pubkey">"My work key"</string> + <!-- The source TCP port for port forwards. --> + <string name="prompt_source_port">"Source port:"</string> + <!-- The "host:port" combination used for port forward destinations. --> + <string name="prompt_destination">"Destination:"</string> + <string name="prompt_old_password">"Old password:"</string> + <string name="prompt_password">"Password:"</string> + <!-- Added after a "Password:" prompt to indicate user needs to confirm entry. --> + <string name="prompt_again">"(again)"</string> + <!-- Label for the user to select port forward type. --> + <string name="prompt_type">"Type:"</string> + <string name="prompt_password_can_be_blank">"Note: password can be blank"</string> + <!-- Prompt for the size of the private key in bits. --> + <string name="prompt_bits">"Bits:"</string> + + <!-- Prompt for the password to unlock a certain pubkey. --> + <string name="prompt_pubkey_password">"Password for key '%1$s'"</string> + + <!-- Prompt for whether to allow SSH Authentication Agent to use the specified key. Note that the '\n' means split the line so it's actually "host to use key" --> + <string name="prompt_allow_agent_to_use_key">"Allow remote host to"\n"use key '%1$s'?"</string> + + <!-- The header of the warning a user gets when the host key has changed. Note that this can be a very serious attack, so we try to be as "loud" to the user as possible. --> + <string name="host_verification_failure_warning_header">"WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!"</string> + <!-- The body of the warning a user gets when the host key has changed. Note that this can be a very serious attack, so we try to be as "loud" to the user as possible. --> + <string name="host_verification_failure_warning">"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"\n"Someone could be eavesdropping on you right now (man-in-the-middle attack)!"\n"It is also possible that the host key has just been changed."</string> + + <!-- Prompt user gets when the remote host has disconnected unexpectedly. --> + <string name="prompt_host_disconnected">"Host has disconnected."\n"Close session?"</string> + <!-- Prompt user must answer yes or no to when the remote host fails verification of encryption fingerprint --> + <string name="prompt_continue_connecting">"Are you sure you want"\n"to continue connecting?"</string> + + <!-- Sent to user when the remote public encryption key fingerprint doesn't match the local database --> + <string name="host_authenticity_warning">"The authenticity of host '%1$s' can't be established."</string> + <!-- First field is encryption algorithm. Second is the actual fingerprint in hex digits --> + <string name="host_fingerprint">"Host %1$s key fingerprint is %2$s"</string> + + <string name="alert_passwords_do_not_match_msg">"Passwords do not match!"</string> + <string name="alert_wrong_password_msg">"Wrong password!"</string> + <string name="alert_key_corrupted_msg">"Private key appears corrupt!"</string> + <string name="alert_sdcard_absent">"SD card is not inserted!"</string> + + <!-- Add a new item (e.g., host or pubkey) to the list. --> + <string name="button_add">"Add"</string> + <!-- Change an existing item's (e.g., host or pubkey) details. --> + <string name="button_change">"Change"</string> + <!-- Button that begins the generation of a public key pair. --> + <string name="button_generate">"Generate Key"</string> + <!-- Button that resizes the screen to the user-specified dimensions. --> + <string name="button_resize">"Resize"</string> + + <string name="alert_disconnect_msg">"Connection Lost"</string> + + <string name="msg_copyright">"Copyright © 2007-2008 Kenny Root http://the-b.org/, Jeffrey Sharkey http://jsharkey.org/"</string> + + <!-- The category title for terminal emulation preferences. --> + <string name="pref_emulation_category">"Terminal emulation"</string> + + <!-- Name for the emulation terminal type preference. --> + <string name="pref_emulation_title">"Emulation mode"</string> + <!-- Description of the emulation terminal type preference. --> + <string name="pref_emulation_summary">"Terminal emulation mode to use for PTY connections"</string> + + <!-- Name for the scrollback size preference --> + <string name="pref_scrollback_title">"Scrollback size"</string> + <!-- Description of the scrollback size preference --> + <string name="pref_scrollback_summary">"Size of scrollback buffer to keep in memory for each console"</string> + + <!-- The category title for user interface preferences --> + <string name="pref_ui_category">"User interface"</string> + + <!-- Name for the rotation mode preference --> + <string name="pref_rotation_title">"Rotation mode"</string> + <!-- Summary for the rotation mode preference --> + <string name="pref_rotation_summary">"How to change rotation when keyboard popped in/out"</string> + + <!-- Name for the titlebar hide preference --> + <string name="pref_titlebarhide_title">"Autohide title bar"</string> + <!-- Summary for the titlebar hide preference --> + <string name="pref_titlebarhide_summary">"Tap console to show title bar and access menu"</string> + + <!-- Name for the full screen preference --> + <string name="pref_fullscreen_title">"Full screen"</string> + <!-- Summary for the full screen preference --> + <string name="pref_fullscreen_summary">"Hide status bar while in console"</string> + + <!-- The category title for keyboard preferences --> + <string name="pref_keyboard_category">"Keyboard"</string> + + <!-- Name for the shifted numbers are f-keys preference --> + <string name="pref_shiftfkeys_title">"Shift+num are F-keys"</string> + <!-- Summary for the shifted numbers are f-keys preference --> + <string name="pref_shiftfkeys_summary">"On hardware keyboards, number keys send F1-F10 with shift"</string> + + <!-- Name for the ctrl'ed numbers are f-keys preference --> + <string name="pref_ctrlfkeys_title">"Ctrl+num are F-keys"</string> + <!-- Summary for the ctrl'ed numbers are f-keys preference --> + <string name="pref_ctrlfkeys_summary">"On software keyboards, number keys send F1-F10 with ctrl"</string> + + <!-- Name for the volume keys control font size preference --> + <string name="pref_volumefont_title">"Volume keys change font size"</string> + <!-- Summary for the volume keys control font size preference --> + <string name="pref_volumefont_summary">"Font size can also be changed in per-host settings"</string> + + <!-- Name for the memorize keys preference --> + <string name="pref_memkeys_title">"Remember keys in memory"</string> + <!-- Summary for the memorize keys preference --> + <string name="pref_memkeys_summary">"Keep unlocked keys in memory until backend service is terminated"</string> + + <!-- Name for the preference that forces the service to stay running in the background.--> + <string name="pref_conn_persist_title">"Persist connections"</string> + <!-- Summary for the preference that forces the service to stay running in the background. --> + <string name="pref_conn_persist_summary">"Force connections to stay connected while in background"</string> + + <!-- Name for the keyboard shortcuts preference --> + <string name="pref_keymode_title">"Directory shortcuts"</string> + <!-- Summary for the keyboard shortcuts preference --> + <string name="pref_keymode_summary">"Select how to use Alt for '/' and Shift for Tab"</string> + + <!-- Name for the sticky modifiers preference --> + <string name="pref_stickymodifiers_title">"Sticky modifiers"</string> + <!-- Summary for the sticky modifiers preference --> + <string name="pref_stickymodifiers_summary">"Modifier keys remain enabled until another key is pressed"</string> + + <!-- Sticky modifier preference value description for when only the Alt key should be a sticky modifier. --> + <string name="only_alt">"Only alt"</string> + + <!-- Name for the camera shortcut usage preference --> + <string name="pref_camera_title">"Camera shortcut"</string> + <!-- Summary for the camera shortcut usage preference --> + <string name="pref_camera_summary">"Select which shortcut to trigger when the camera button is pushed"</string> + + <!-- Name for the keep screen on preference --> + <string name="pref_keepalive_title">"Keep screen awake"</string> + <!-- Summary for the camera shortcut usage preference --> + <string name="pref_keepalive_summary">"Prevent the screen from turning off when working in a console"</string> + + <!-- Name for the Wi-Fi lock preference --> + <string name="pref_wifilock_title">"Keep Wi-Fi active"</string> + <!-- Summary for the Wi-Fi lock preference --> + <string name="pref_wifilock_summary">"Prevent Wi-Fi from turning off when a session is active"</string> + + <!-- Name for the haptic feedback (bumpy arrow) preference --> + <string name="pref_bumpyarrows_title">"Bumpy arrows"</string> + <!-- Summary for the haptic feedback (bumpy arrow) preference --> + <string name="pref_bumpyarrows_summary">"Vibrate when sending arrow keys from trackball; useful for laggy connections"</string> + + <!-- Category title for the Terminal Bell preferences --> + <string name="pref_bell_category">"Terminal bell"</string> + + <!-- Checkbox preference title for the audible terminal bell feature --> + <string name="pref_bell_title">"Audible bell"</string> + + <!-- Title for the slider preference to set the volume --> + <string name="pref_bell_volume_title">"Bell volume"</string> + + <!-- Checkbox preference title for the vibrate on terminal bell feature --> + <string name="pref_bell_vibrate_title">"Vibrate on bell"</string> + + <!-- Checkbox preference title for the receive notifications on terminal bell feature --> + <string name="pref_bell_notification_title">"Background notifications"</string> + <!-- Brief summary of the feature that is enabled when the checkbox preference for the receive notifications on terminal bell feature is checked --> + <string name="pref_bell_notification_summary">"Send notification when a terminal running in the background sounds a bell."</string> + + <!-- Preference selection to indicate use of right side of keyboard for special shortcuts. --> + <string name="list_keymode_right">"Use right-side keys"</string> + <!-- Preference selection to indicate use of left side of keyboard for special shortcuts. --> + <string name="list_keymode_left">"Use left-side keys"</string> + <!-- Preference selection to indicate never to use special shortcut keys. --> + <string name="list_keymode_none">"Disable"</string> + + <!-- Preference to not use pubkeys to authenticate to this host. --> + <string name="list_pubkeyids_none">"Do not use keys"</string> + <!-- Preference to use any pubkey to authenticate to this host. --> + <string name="list_pubkeyids_any">"Use any unlocked key"</string> + + <!-- Host nickname field preference title --> + <string name="hostpref_nickname_title">"Nickname"</string> + + <!-- Host color category preference title --> + <string name="hostpref_color_title">"Color category"</string> + + <!-- Host's default font size when opening the terminal in points (pt) --> + <string name="hostpref_fontsize_title">"Font size (pt)"</string> + + <!-- Host pubkey usage preference title --> + <string name="hostpref_pubkeyid_title">"Use pubkey authentication"</string> + + <!-- Preference title for the SSH Authentication Agent Forwarding for a host connection --> + <string name="hostpref_authagent_title">"Use SSH auth agent"</string> + + <!-- Host post-login automation preference title --> + <string name="hostpref_postlogin_title">"Post-login automation"</string> + <!-- Host post-login automation preference summary --> + <string name="hostpref_postlogin_summary">"Commands to run on remote server once authenticated"</string> + + <!-- Host compression preference title --> + <string name="hostpref_compression_title">"Compression"</string> + <!-- Summary for compression preference --> + <string name="hostpref_compression_summary">"This may help with slower networks"</string> + + <!-- Setting for whether we want a session to start up when we connect to a host --> + <string name="hostpref_wantsession_title">"Start shell session"</string> + <!-- Summary for field asking whether a shell session should be started up upon connection or not --> + <string name="hostpref_wantsession_summary">"Disable this preference to only use port forwards"</string> + + <!-- Setting for whether the host should be reconnected to automatically upon disconnect --> + <string name="hostpref_stayconnected_title">"Stay connected"</string> + <!-- Summary for preference asking whether the host should be reconnected to when it disconnects --> + <string name="hostpref_stayconnected_summary">"Try to reconnect to host if disconnected"</string> + + <!-- Setting for whether we should prompt to close after getting disconnected --> + <string name="hostpref_quickdisconnect_title">"Close on disconnect"</string> + <string name="hostpref_quickdisconnect_summary">"Close immediately after remote disconnect without prompting."</string> + + <!-- Setting for what key code is sent to the server when DEL key is pressed. --> + <string name="hostpref_delkey_title">"DEL Key"</string> + <!-- Summary for field asking what key code is sent to the server when DEL key is pressed. --> + <string name="hostpref_delkey_summary">"The key code sent when DEL key is pressed"</string> + + <!-- Host character encoding preference title --> + <string name="hostpref_encoding_title">"Encoding"</string> + <!-- Host character encoding preference summary --> + <string name="hostpref_encoding_summary">"Character encoding for the host"</string> + + <!-- Host preference category title for connection settings --> + <string name="hostpref_connection_category">"Connection settings"</string> + + <!-- Username field title for host editor preference --> + <string name="hostpref_username_title">"Username"</string> + + <!-- Hostname field title for host editor preference --> + <string name="hostpref_hostname_title">"Host"</string> + + <!-- Port field title for host editor preference --> + <string name="hostpref_port_title">"Port"</string> + + <!-- Displayed to indicate a host has never been connected to. --> + <string name="bind_never">"Never connected"</string> + <!-- The time that has elapsed since a host was connected to when it has been less than an hour. --> + <string name="bind_minutes">"%1$s minutes ago"</string> + <!-- The time that has elapsed since a host was connected to when it has been less than a day. --> + <string name="bind_hours">"%1$s hours ago"</string> + <!-- The time that has elapsed since a host was connected to when it has been a day or more. --> + <string name="bind_days">"%1$s days ago"</string> + + <!-- Message given when user copies from the terminal. --> + <string name="console_copy_done">"Copied %1$d bytes to clipboard"</string> + <!-- Instructions for how to copy from the terminal. The '\n' entries are to split lines to improve readability and prevent wrapping off the screen. --> + <string name="console_copy_start">"Touch and drag"\n"or use directional pad"\n"to select area to copy"</string> + + <!-- Button to close the disconnected terminal window. --> + <string name="console_menu_close">"Close"</string> + <!-- Button to begin copying from the terminal to the clipboard. --> + <string name="console_menu_copy">"Copy"</string> + <!-- Button to paste from the clipboard to the terminal. --> + <string name="console_menu_paste">"Paste"</string> + <!-- Button that brings user to the Port Forwards List. --> + <string name="console_menu_portforwards">"Port Forwards"</string> + <!-- Button that brings user to the terminal resizing dialog where they can force a size. --> + <string name="console_menu_resize">"Force Size"</string> + <!-- Button that brings up the list of URLs on the current screen --> + <string name="console_menu_urlscan">"URL Scan"</string> + + <!-- Button label to answer "Yes" to a yes/no prompt --> + <string name="button_yes">"Yes"</string> + <!-- Button label to answer "No" to a yes/no prompt --> + <string name="button_no">"No"</string> + + <!-- Selection for a "local" port forward. E.g., connections to a port listening locally is forwarded to the remote end's listening port. --> + <string name="portforward_local">"Local"</string> + <!-- Selection for a "remote" port forward. E.g., connections to a port listening remotely is forwarded to the local end's listening port. --> + <string name="portforward_remote">"Remote"</string> + <!-- Selection for a "dynamic" port forward. E.g., connections to a port listening locally is forwarded based on the SOCKS protocol to an arbitrary remote host and port. --> + <string name="portforward_dynamic">"Dynamic (SOCKS)"</string> + <!-- Button that commits the port forward to be made from the Port Forward Creation dialog. --> + <string name="portforward_pos">"Create port forward"</string> + + <string name="portforward_done">"Successfully created port forward"</string> + <string name="portforward_problem">"Problem creating port forward, maybe you're using ports under 1024 or port is already used?"</string> + + <string name="portforward_menu_add">"Add port forward"</string> + + <!-- The string to present in the quick-connect box to hint the user to the format for connecting to hosts. --> + <string name="hint_userhost">"user@hostname"</string> + + <!-- Hint given to user when the format they're using is incorrect in the quick-connect box. --> + <string name="list_format_error">"Use the format \"%1$s\""</string> + + <!-- Part of the formatting hints that will be used like: username@hostname:port --> + <string name="format_username">"username"</string> + <!-- Part of the formatting hints that will be used like: username@hostname:port --> + <string name="format_hostname">"hostname"</string> + <!-- Part of the formatting hints that will be used like: username@hostname:port --> + <string name="format_port">"port"</string> + + <string name="list_menu_pubkeys">"Manage Pubkeys"</string> + <!-- Selection choice to sort hosts by color. --> + <string name="list_menu_sortcolor">"Sort by color"</string> + <!-- Selection choice to sort hosts by nickname. --> + <string name="list_menu_sortname">"Sort by name"</string> + <string name="list_menu_settings">"Settings"</string> + + <string name="list_host_disconnect">"Disconnect"</string> + <string name="list_host_edit">"Edit host"</string> + <string name="list_host_portforwards">"Edit port forwards"</string> + <string name="list_host_delete">"Delete host"</string> + <!-- Note that the '\n' splits the lines so it's actually "quick-connect box below to connect" --> + <string name="list_host_empty">"Use the quick-connect box"\n"below to connect to a host."</string> + + <!-- Default screen rotation preference selection --> + <string name="list_rotation_default">"Default"</string> + <string name="list_rotation_land">"Force landscape"</string> + <string name="list_rotation_port">"Force portrait"</string> + <!-- Selection to indicate the rotation should be selected automatically based on the tilt sensor. --> + <string name="list_rotation_auto">"Automatic"</string> + + <!-- Selection to indicate pressing the Camera button should send "Ctrl+A then Space". --> + <string name="list_camera_ctrlaspace">"Ctrl+A then Space"</string> + <!-- Selection to indicate pressing the Camera button should send "Ctrl+A". --> + <string name="list_camera_ctrla">"Ctrl+A"</string> + <!-- Selection to indicate pressing the Camera button should send the "Esc" key. --> + <string name="list_camera_esc">"Esc"</string> + <!-- Selection to indicate pressing the Camera button should send "Esc+A". --> + <string name="list_camera_esc_a">"Esc+A"</string> + <!-- Selection to indicate pressing the Camera button should send nothing at all. --> + <string name="list_camera_none">"None"</string> + + <!-- Name for the backspace character --> + <string name="list_delkey_backspace">"Backspace"</string> + <!-- Name for the ASCII DEL character --> + <string name="list_delkey_del">"Delete"</string> + + <string name="delete_message">"Are you sure you want to delete '%1$s'?"</string> + <string name="delete_pos">"Yes, delete"</string> + <string name="delete_neg">"Cancel"</string> + + <!-- Button to agree to license terms. --> + <string name="wizard_agree">"Agree"</string> + <!-- Button to go to the next page in the first time start-up wizard. --> + <string name="wizard_next">"Next"</string> + <!-- Button to go to the previous page in the first time start-up wizard. --> + <string name="wizard_back">"Back"</string> + + <string name="terminal_no_hosts_connected">"No hosts currently connected"</string> + + <!-- Displayed in terminal when attempting to connect to a host. The first two + variables are host:port and the third is the protocol (e.g., SSH) --> + <string name="terminal_connecting">"Connecting to %1$s:%2$d via %3$s"</string> + + <!-- Displays the host key to the user in the terminal --> + <string name="terminal_sucess">"Verified host '%1$s' key: %2$s"</string> + <string name="terminal_failed">"Host key verification failed."</string> + + <!-- Displayed on the terminal describing the cryptographic algorithm names --> + <string name="terminal_using_s2c_algorithm">"Server-to-client algorithm: %1$s %2$s"</string> + <!-- Displayed on the terminal describing the cryptographic algorithm names --> + <string name="terminal_using_c2s_algorithm">"Client-to-server algorithm: %1$s %2$s"</string> + <!-- Displayed on the terminal describing the cryptographic algorithm names --> + <string name="terminal_using_algorithm">"Using algorithm: %1$s %2$s"</string> + + <string name="terminal_auth">"Trying to authenticate"</string> + + <string name="terminal_auth_pass">"Attempting 'password' authentication"</string> + <string name="terminal_auth_pass_fail">"Authentication method 'password' failed"</string> + + <string name="terminal_auth_pubkey_any">"Attempting 'publickey' authentication with any in-memory public keys"</string> + <string name="terminal_auth_pubkey_invalid">"Selected public key is invalid, try reselecting key in host editor"</string> + <string name="terminal_auth_pubkey_specific">"Attempting 'publickey' authentication with a specific public key"</string> + <string name="terminal_auth_pubkey_fail">"Authentication method 'publickey' with key '%1$s' failed"</string> + + <string name="terminal_auth_ki">"Attempting 'keyboard-interactive' authentication"</string> + <string name="terminal_auth_ki_fail">"Authentication method 'keyboard-interactive' failed"</string> + + <string name="terminal_auth_fail">"[Your host doesn't support 'password' or 'keyboard-interactive' authentication.]"</string> + + <string name="terminal_no_session">"Session will not be started due to host preference."</string> + <string name="terminal_enable_portfoward">"Enable port forward: %1$s"</string> + + <string name="local_shell_unavailable">"Failure! Local shell is unavailable on this phone."</string> + + <!-- Text sent to the user to alert them that a Terminal Bell is received in a background session --> + <string name="notification_text">"%1$s wants your attention."</string> + + <!-- Preference selection for SSH Authentication Agent to never use pubkeys --> + <string name="no">"No"</string> + <!-- Preference selection for SSH Authentication Agent to be able to use pubkeys "with confirmation" only --> + <string name="with_confirmation">"With confirmation"</string> + <!-- Preference selection for SSH Authentication Agent to be able to use pubkeys --> + <string name="yes">"Yes"</string> + + <!-- String displayed to user when they're asked to submit exceptions to the ConnectBot developers --> + <string name="exceptions_submit_message">"It appears ConnectBot had a problem last time it ran. Submit error report to ConnectBot developers?"</string> + + <!-- Menu selection to reset colors to their defaults. --> + <string name="menu_colors_reset">"Reset"</string> + + <!-- Displayed in the notification bar that connections are active --> + <string name="app_is_running">"ConnectBot is running"</string> + + <string name="color_red">"red"</string> + <string name="color_green">"green"</string> + <string name="color_blue">"blue"</string> + <string name="color_gray">"gray"</string> + + <!-- Very short label indicating the number next to it is "foreground color" number. --> + <string name="colors_fg_label">"FG: %1$d"</string> + + <!-- Very short label indicating the number next to it is "background color" number. --> + <string name="color_bg_label">"BG: %1$d"</string> + + <!-- Describes the image of the "connected to host" icon for accessibility purposes. --> + <string name="image_description_connected">"Connected."</string> + + <!-- Describes the icon of the "locked pubkey" icon for accessibility purposes. --> + <string name="image_description_key_is_locked">"Key is locked."</string> + + <!-- Describes the icon of the "send control character" button in the terminal view for + accessibility purposes. --> + <string name="image_description_toggle_control_character">Toggle control character.</string> + + <!-- Describes the icon of the "send escape character" button in the terminal view for + accessibility purposes. --> + <string name="image_description_send_escape_character">Send escape character.</string> + + <!-- Describes the icon of the "show keyboard" button in the terminal view for accessibility + purposes. --> + <string name="image_description_show_keyboard">Show keyboard.</string> +</resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..22e830b --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <style name="NoTitle" parent="android:Theme"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowContentOverlay">@null</item> + </style> +</resources> diff --git a/app/src/main/res/values/version.xml b/app/src/main/res/values/version.xml new file mode 100644 index 0000000..94227c9 --- /dev/null +++ b/app/src/main/res/values/version.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="msg_version" translatable="false">ConnectBot (working copy)</string> +</resources> diff --git a/app/src/main/res/xml/host_prefs.xml b/app/src/main/res/xml/host_prefs.xml new file mode 100644 index 0000000..a2fc1a4 --- /dev/null +++ b/app/src/main/res/xml/host_prefs.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + + <EditTextPreference + android:key="nickname" + android:title="@string/hostpref_nickname_title" + android:singleLine="true" + /> + + <ListPreference + android:key="color" + android:title="@string/hostpref_color_title" + android:entries="@array/list_colors" + android:entryValues="@array/list_color_values" + /> + + <EditTextPreference + android:key="fontsize" + android:title="@string/hostpref_fontsize_title" + android:inputType="number" + android:singleLine="true" + /> + +<!-- + <CheckBoxPreference + android:key="usekeys" + android:title="Use SSH keys" + /> +--> + + <ListPreference + android:key="pubkeyid" + android:title="@string/hostpref_pubkeyid_title" + android:entries="@array/list_pubkeyids" + android:entryValues="@array/list_pubkeyids_value" + /> + + <ListPreference + android:key="useauthagent" + android:title="@string/hostpref_authagent_title" + android:entries="@array/list_authagent" + android:entryValues="@array/list_authagent_values" + /> + + <EditTextPreference + android:key="postlogin" + android:title="@string/hostpref_postlogin_title" + android:summary="@string/hostpref_postlogin_summary" + /> + + <CheckBoxPreference + android:key="compression" + android:title="@string/hostpref_compression_title" + android:summary="@string/hostpref_compression_summary" + /> + + <CheckBoxPreference + android:key="wantsession" + android:title="@string/hostpref_wantsession_title" + android:summary="@string/hostpref_wantsession_summary" + /> + + <CheckBoxPreference + android:key="stayconnected" + android:title="@string/hostpref_stayconnected_title" + android:summary="@string/hostpref_stayconnected_summary" + /> + + <CheckBoxPreference + android:key="quickdisconnect" + android:title="@string/hostpref_quickdisconnect_title" + android:summary="@string/hostpref_quickdisconnect_summary" + /> + + <ListPreference + android:key="delkey" + android:title="@string/hostpref_delkey_title" + android:summary="@string/hostpref_delkey_summary" + android:entries="@array/list_delkey" + android:entryValues="@array/list_delkey_values" + /> + + <ListPreference + android:key="encoding" + android:title="@string/hostpref_encoding_title" + android:summary="@string/hostpref_encoding_summary" + /> + + <PreferenceCategory + android:title="@string/hostpref_connection_category"> + + <EditTextPreference + android:key="username" + android:title="@string/hostpref_username_title" + android:singleLine="true" + /> + + <EditTextPreference + android:key="hostname" + android:title="@string/hostpref_hostname_title" + android:singleLine="true" + /> + + <EditTextPreference + android:key="port" + android:title="@string/hostpref_port_title" + android:numeric="integer" + /> + + </PreferenceCategory> + +</PreferenceScreen> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml new file mode 100644 index 0000000..70bdb5b --- /dev/null +++ b/app/src/main/res/xml/preferences.xml @@ -0,0 +1,188 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + + <CheckBoxPreference + android:key="memkeys" + android:title="@string/pref_memkeys_title" + android:summary="@string/pref_memkeys_summary" + android:defaultValue="true" + /> + + <CheckBoxPreference + android:key="connPersist" + android:title="@string/pref_conn_persist_title" + android:summary="@string/pref_conn_persist_summary" + android:defaultValue="true" + /> + + <CheckBoxPreference + android:key="wifilock" + android:title="@string/pref_wifilock_title" + android:summary="@string/pref_wifilock_summary" + android:defaultValue="true" + /> + + <PreferenceCategory + android:title="@string/pref_emulation_category"> + + <ListPreference + android:key="emulation" + android:title="@string/pref_emulation_title" + android:summary="@string/pref_emulation_summary" + android:entries="@array/list_emulation_modes" + android:entryValues="@array/list_emulation_modes" + android:defaultValue="screen" + /> + + <EditTextPreference + android:key="scrollback" + android:title="@string/pref_scrollback_title" + android:summary="@string/pref_scrollback_summary" + android:defaultValue="140" + android:numeric="integer" + /> + + </PreferenceCategory> + + <PreferenceCategory + android:title="@string/pref_ui_category"> + + <ListPreference + android:key="rotation" + android:title="@string/pref_rotation_title" + android:summary="@string/pref_rotation_summary" + android:entries="@array/list_rotation" + android:entryValues="@array/list_rotation_values" + android:defaultValue="Default" + /> + + <CheckBoxPreference + android:key="titlebarhide" + android:title="@string/pref_titlebarhide_title" + android:summary="@string/pref_titlebarhide_summary" + android:defaultValue="false" + /> + + <CheckBoxPreference + android:key="fullscreen" + android:title="@string/pref_fullscreen_title" + android:summary="@string/pref_fullscreen_summary" + android:defaultValue="false" + /> + + <CheckBoxPreference + android:key="volumefont" + android:title="@string/pref_volumefont_title" + android:summary="@string/pref_volumefont_summary" + android:defaultValue="true" + /> + + <CheckBoxPreference + android:key="keepalive" + android:title="@string/pref_keepalive_title" + android:summary="@string/pref_keepalive_summary" + android:defaultValue="true" + /> + </PreferenceCategory> + + <PreferenceCategory + android:title="@string/pref_keyboard_category"> + + <CheckBoxPreference + android:key="shiftfkeys" + android:title="@string/pref_shiftfkeys_title" + android:summary="@string/pref_shiftfkeys_summary" + android:defaultValue="false" + /> + + <CheckBoxPreference + android:key="ctrlfkeys" + android:title="@string/pref_ctrlfkeys_title" + android:summary="@string/pref_ctrlfkeys_summary" + android:defaultValue="false" + /> + + <ListPreference + android:key="stickymodifiers" + android:title="@string/pref_stickymodifiers_title" + android:summary="@string/pref_stickymodifiers_summary" + android:entries="@array/list_stickymodifiers" + android:entryValues="@array/list_stickymodifiers_values" + android:defaultValue="no" + /> + + <ListPreference + android:key="keymode" + android:title="@string/pref_keymode_title" + android:summary="@string/pref_keymode_summary" + android:entries="@array/list_keymode" + android:entryValues="@array/list_keymode_values" + android:defaultValue="none" + /> + + <ListPreference + android:key="camera" + android:title="@string/pref_camera_title" + android:summary="@string/pref_camera_summary" + android:entries="@array/list_camera" + android:entryValues="@array/list_camera_values" + android:defaultValue="Ctrl+A then Space" + /> + + <CheckBoxPreference + android:key="bumpyarrows" + android:title="@string/pref_bumpyarrows_title" + android:summary="@string/pref_bumpyarrows_summary" + android:defaultValue="true" + /> + </PreferenceCategory> + + <PreferenceCategory + android:title="@string/pref_bell_category"> + + <CheckBoxPreference + android:key="bell" + android:title="@string/pref_bell_title" + android:defaultValue="true" + /> + + <org.connectbot.util.VolumePreference + android:key="bellVolume" + android:title="@string/pref_bell_volume_title" + /> + + <CheckBoxPreference + android:key="bellVibrate" + android:title="@string/pref_bell_vibrate_title" + android:defaultValue="true" + /> + + <CheckBoxPreference + android:key="bellNotification" + android:title="@string/pref_bell_notification_title" + android:summary="@string/pref_bell_notification_summary" + android:defaultValue="false" + /> + + </PreferenceCategory> + +</PreferenceScreen> |