/**
* Copyright (C) 2012 Iordan Iordanov
* Copyright (C) 2009 Michael A. MacDonald
*
* This 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 software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
package com.iiordanov.bVNC;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import net.sqlcipher.database.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import android.widget.ImageView.ScaleType;
import com.antlersoft.android.dbimpl.NewInstance;
import com.iiordanov.bVNC.input.TouchMouseSwipePanInputHandler;
import com.iiordanov.bVNC.*;
import com.iiordanov.freebVNC.*;
import com.iiordanov.aRDP.*;
import com.iiordanov.freeaRDP.*;
import com.iiordanov.aSPICE.*;
import com.iiordanov.freeaSPICE.*;
/**
* @author Iordan Iordanov
* @author Michael A. MacDonald
* @author David Warden
*
*/
public class ConnectionBean extends AbstractConnectionBean implements Comparable<ConnectionBean> {
private static final String TAG = "ConnectionBean";
static Context c = null;
protected boolean m_isReadyForConnection = true; // saved connections are OK
protected boolean m_saved = false;
private int idHashAlgorithm;
private String idHash;
private String masterPassword;
static final NewInstance<ConnectionBean> newInstance=new NewInstance<ConnectionBean>() {
public ConnectionBean get() { return new ConnectionBean(c); }
};
ConnectionBean(Context context)
{
set_Id(0);
setAddress("");
setPassword("");
setKeepPassword(true);
setNickname("");
setConnectionType(Constants.CONN_TYPE_PLAIN);
setSshServer("");
setSshPort(Constants.DEFAULT_SSH_PORT);
setSshUser("");
setSshPassword("");
setKeepSshPassword(false);
setSshPubKey("");
setSshPrivKey("");
setSshPassPhrase("");
setUseSshPubKey(false);
setSshHostKey("");
setSshRemoteCommandOS(0);
setSshRemoteCommandType(0);
setSshRemoteCommand("");
setSshRemoteCommandTimeout(5);
setAutoXType(0);
setAutoXCommand("");
setAutoXEnabled(false);
setAutoXResType(0);
setAutoXWidth(0);
setAutoXHeight(0);
setAutoXSessionProg("");
setAutoXSessionType(0);
setAutoXUnixpw(false);
setAutoXUnixAuth(false);
setAutoXRandFileNm("");
setUseSshRemoteCommand(false);
setUserName("");
setRdpDomain("");
setPort(Constants.DEFAULT_PROTOCOL_PORT);
setCaCert("");
setCaCertPath("");
setTlsPort(-1);
setCertSubject("");
setColorModel(COLORMODEL.C24bit.nameString());
setPrefEncoding(RfbProto.EncodingTight);
setScaleMode(ScaleType.MATRIX);
setInputMode(TouchMouseSwipePanInputHandler.TOUCH_ZOOM_MODE);
setUseDpadAsArrows(true);
setRotateDpad(false);
setUsePortrait(false);
setUseLocalCursor(false);
setRepeaterId("");
setExtraKeysToggleType(1);
setMetaListId(1);
setRdpResType(0);
setRdpWidth(0);
setRdpHeight(0);
setRdpColor(0);
setRemoteFx(false);
setDesktopBackground(false);
setFontSmoothing(false);
setDesktopComposition(false);
setWindowContents(false);
setMenuAnimation(false);
setVisualStyles(false);
setConsoleMode(false);
setRedirectSdCard(false);
setEnableSound(false);
setEnableRecording(false);
setRemoteSoundType(Constants.REMOTE_SOUND_ON_DEVICE);
setViewOnly(false);
setLayoutMap("English (US)");
c = context;
// These two are not saved in the database since we always save the cert data.
setIdHashAlgorithm(Constants.ID_HASH_SHA1);
setIdHash("");
}
public int getIdHashAlgorithm() {
return idHashAlgorithm;
}
public void setIdHashAlgorithm(int idHashAlgorithm) {
this.idHashAlgorithm = idHashAlgorithm;
}
public String getIdHash() {
return idHash;
}
public void setIdHash(String idHash) {
this.idHash = idHash;
}
boolean isNew()
{
return get_Id()== 0;
}
public synchronized void save(SQLiteDatabase database) {
ContentValues values = Gen_getValues();
values.remove(GEN_FIELD__ID);
// Never save the SSH password and passphrase.
values.put(GEN_FIELD_SSHPASSWORD, "");
values.put(GEN_FIELD_SSHPASSPHRASE, "");
if (!getKeepPassword()) {
values.put(GEN_FIELD_PASSWORD, "");
}
if (isNew()) {
set_Id(database.insert(GEN_TABLE_NAME, null, values));
} else {
database.update(GEN_TABLE_NAME, values, GEN_FIELD__ID + " = ?", new String[] { Long.toString(get_Id()) });
}
}
public boolean isReadyForConnection()
{
return m_isReadyForConnection;
}
public boolean isSaved()
{
return m_saved;
}
ScaleType getScaleMode()
{
return ScaleType.valueOf(getScaleModeAsString());
}
void setScaleMode(ScaleType value)
{
setScaleModeAsString(value.toString());
}
static ConnectionBean createLoadFromUri(Uri dataUri, Context ctx)
{
ConnectionBean connection = new ConnectionBean(ctx);
if (dataUri == null) return connection;
Database database = new Database(ctx);
String host = dataUri.getHost();
// Intent generated by connection shortcut widget
if (host != null && host.startsWith(Utils.getConnectionString(ctx))) {
int port = 0;
int idx = host.indexOf(':');
if (idx != -1) {
try {
port = Integer.parseInt(host.substring(idx + 1));
}
catch (NumberFormatException nfe) { }
host = host.substring(0, idx);
}
if (connection.Gen_read(database.getReadableDatabase(), port))
{
MostRecentBean bean = getMostRecent(database.getReadableDatabase());
if (bean != null)
{
bean.setConnectionId(connection.get_Id());
bean.Gen_update(database.getWritableDatabase());
database.close();
}
}
return connection;
}
// search based on nickname
SQLiteDatabase queryDb = database.getReadableDatabase();
String connectionName = dataUri.getQueryParameter(Constants.PARAM_CONN_NAME);
Cursor nickCursor = null;
if (connectionName != null)
nickCursor = queryDb.query(GEN_TABLE_NAME, new String[] { GEN_FIELD__ID }, GEN_FIELD_NICKNAME + " = ?", new String[] { connectionName }, null, null, null);
if (nickCursor != null && nickCursor.moveToFirst())
{
// there could be many values, so we will just pick one
Log.i(TAG, String.format(Locale.US, "Loding connection info from nickname: %s", connectionName));
connection.Gen_populate(nickCursor, connection.Gen_columnIndices(nickCursor));
nickCursor.close();
database.close();
return connection;
}
if (nickCursor != null)
nickCursor.close();
// search based on hostname
Cursor hostCursor = null;
if (host != null)
hostCursor = queryDb.query(GEN_TABLE_NAME, new String[] { GEN_FIELD__ID }, GEN_FIELD_ADDRESS + " = ?", new String[] { host }, null, null, null);
if (hostCursor != null && hostCursor.moveToFirst())
{
Log.i(TAG, String.format(Locale.US, "Loding connection info from hostname: %s", host));
connection.Gen_populate(hostCursor, connection.Gen_columnIndices(hostCursor));
hostCursor.close();
database.close();
return connection;
}
if (hostCursor != null)
hostCursor.close();
database.close();
return connection;
}
void parseFromUri(Uri dataUri) {
Log.i(TAG, "Parsing VNC URI.");
if (dataUri == null) {
m_isReadyForConnection = false;
m_saved = true;
return;
}
String host = dataUri.getHost();
if (host != null) {
setAddress(host);
// by default, the connection name is the host name
String nickName = getNickname();
if (Utils.isNullOrEmptry(nickName)) {
setNickname(host);
}
// default to use same host for ssh
if (Utils.isNullOrEmptry(getSshServer())) {
setSshServer(host);
}
}
final int PORT_NONE = -1;
int port = dataUri.getPort();
if (port != PORT_NONE && !isValidPort(port)) {
throw new IllegalArgumentException("The specified VNC port is not valid.");
}
setPort(port);
// handle legacy android-vnc-viewer parsing vnc://host:port/colormodel/password
List<String> path = dataUri.getPathSegments();
if (path.size() >= 1) {
setColorModel(path.get(0));
}
if (path.size() >= 2) {
setPassword(path.get(1));
}
// query based parameters
String connectionName = dataUri.getQueryParameter(Constants.PARAM_CONN_NAME);
if (connectionName != null) {
setNickname(connectionName);
}
ArrayList<String> supportedUserParams = new ArrayList<String>() {{
add(Constants.PARAM_RDP_USER); add(Constants.PARAM_SPICE_USER); add(Constants.PARAM_VNC_USER);
}};
for (String userParam : supportedUserParams) {
String username = dataUri.getQueryParameter(userParam);
if (username != null) {
setUserName(username);
break;
}
}
ArrayList<String> supportedPwdParams = new ArrayList<String>() {{
add(Constants.PARAM_RDP_PWD); add(Constants.PARAM_SPICE_PWD); add(Constants.PARAM_VNC_PWD);
}};
for (String pwdParam : supportedPwdParams) {
String password = dataUri.getQueryParameter(pwdParam);
if (password != null) {
setPassword(password);
break;
}
}
setKeepPassword(false); // we should not store the password unless it is encrypted
String securityTypeParam = dataUri.getQueryParameter(Constants.PARAM_SECTYPE);
int secType = 0; //invalid
if (securityTypeParam != null) {
secType = Integer.parseInt(securityTypeParam); // throw if invalid
switch (secType) {
case Constants.SECTYPE_NONE:
case Constants.SECTYPE_VNC:
setConnectionType(Constants.CONN_TYPE_PLAIN);
break;
case Constants.SECTYPE_INTEGRATED_SSH:
setConnectionType(Constants.CONN_TYPE_SSH);
break;
case Constants.SECTYPE_ULTRA:
setConnectionType(Constants.CONN_TYPE_ULTRAVNC);
break;
case Constants.SECTYPE_TLS:
setConnectionType(Constants.CONN_TYPE_ANONTLS);
break;
case Constants.SECTYPE_VENCRYPT:
setConnectionType(Constants.CONN_TYPE_VENCRYPT);
break;
case Constants.SECTYPE_TUNNEL:
setConnectionType(Constants.CONN_TYPE_STUNNEL);
break;
default:
throw new IllegalArgumentException("The specified security type is invalid or unsupported.");
}
}
// ssh parameters
String sshHost = dataUri.getQueryParameter(Constants.PARAM_SSH_HOST);
if (sshHost != null) {
setSshServer(sshHost);
}
String sshPortParam = dataUri.getQueryParameter(Constants.PARAM_SSH_PORT);
if (sshPortParam != null) {
int sshPort = Integer.parseInt(sshPortParam);
if (!isValidPort(sshPort))
throw new IllegalArgumentException("The specified SSH port is not valid.");
setSshPort(sshPort);
}
String sshUser = dataUri.getQueryParameter(Constants.PARAM_SSH_USER);
if (sshUser != null) {
setSshUser(sshUser);
}
String sshPassword = dataUri.getQueryParameter(Constants.PARAM_SSH_PWD);
if (sshPassword != null) {
setSshPassword(sshPassword);
}
// security hashes
String idHashAlgParam = dataUri.getQueryParameter(Constants.PARAM_ID_HASH_ALG);
if (idHashAlgParam != null) {
int idHashAlg = Integer.parseInt(idHashAlgParam); // throw if invalid
switch (idHashAlg) {
case Constants.ID_HASH_MD5:
case Constants.ID_HASH_SHA1:
case Constants.ID_HASH_SHA256:
setIdHashAlgorithm(idHashAlg);
break;
default:
// we are given a bad parameter
throw new IllegalArgumentException("The specified hash algorithm is invalid or unsupported.");
}
}
String idHash = dataUri.getQueryParameter(Constants.PARAM_ID_HASH);
if (idHash != null) {
setIdHash(idHash);
}
String viewOnlyParam = dataUri.getQueryParameter(Constants.PARAM_VIEW_ONLY);
if (viewOnlyParam != null) setViewOnly(Boolean.parseBoolean(viewOnlyParam));
String scaleModeParam = dataUri.getQueryParameter(Constants.PARAM_SCALE_MODE);
if (scaleModeParam != null) setScaleMode(ScaleType.valueOf(scaleModeParam));
String extraKeysToggleParam = dataUri.getQueryParameter(Constants.PARAM_EXTRAKEYS_TOGGLE);
if (extraKeysToggleParam != null) setExtraKeysToggleType(Integer.parseInt(extraKeysToggleParam));
// color model
String colorModelParam = dataUri.getQueryParameter(Constants.PARAM_COLORMODEL);
if (colorModelParam != null) {
int colorModel = Integer.parseInt(colorModelParam); // throw if invalid
switch (colorModel) {
case Constants.COLORMODEL_BLACK_AND_WHITE:
setColorModel(COLORMODEL.C2.nameString());
break;
case Constants.COLORMODEL_GREYSCALE:
setColorModel(COLORMODEL.C4.nameString());
break;
case Constants.COLORMODEL_8_COLORS:
setColorModel(COLORMODEL.C8.nameString());
break;
case Constants.COLORMODEL_64_COLORS:
setColorModel(COLORMODEL.C64.nameString());
break;
case Constants.COLORMODEL_256_COLORS:
setColorModel(COLORMODEL.C256.nameString());
break;
// use the best currently available model
case Constants.COLORMODEL_16BIT:
setColorModel(COLORMODEL.C24bit.nameString());
break;
case Constants.COLORMODEL_24BIT:
setColorModel(COLORMODEL.C24bit.nameString());
break;
case Constants.COLORMODEL_32BIT:
setColorModel(COLORMODEL.C24bit.nameString());
break;
default:
// we are given a bad parameter
throw new IllegalArgumentException("The specified color model is invalid or unsupported.");
}
}
String saveConnectionParam = dataUri.getQueryParameter(Constants.PARAM_SAVE_CONN);
boolean saveConnection = true;
if (saveConnectionParam != null) {
saveConnection = Boolean.parseBoolean(saveConnectionParam); // throw if invalid
}
// Parse a passed-in TLS port number.
String tlsPortParam = dataUri.getQueryParameter(Constants.PARAM_TLS_PORT);
if (tlsPortParam != null) {
int tlsPort = Integer.parseInt(tlsPortParam);
if (!isValidPort(tlsPort))
throw new IllegalArgumentException("The specified TLS port is not valid.");
setTlsPort(tlsPort);
}
// Parse a CA Cert path parameter
String caCertPath = dataUri.getQueryParameter(Constants.PARAM_CACERT_PATH);
if (caCertPath != null) {
setCaCertPath(caCertPath);
}
// Parse a Cert subject
String certSubject = dataUri.getQueryParameter(Constants.PARAM_CERT_SUBJECT);
if (certSubject != null) {
setCertSubject(certSubject);
}
// Parse a keyboard layout parameter
String keyboardLayout = dataUri.getQueryParameter(Constants.PARAM_KEYBOARD_LAYOUT);
if(keyboardLayout != null) {
setLayoutMap(keyboardLayout);
}
// if we are going to save the connection, we will do so here
// it may make sense to confirm overwriting data but is probably unnecessary
if (saveConnection) {
Database database = new Database(c);
save(database.getWritableDatabase());
database.close();
m_saved = true;
}
// we do not currently use API keys
// check if we need to show data-entry screen
// it may be possible to prompt for data later
m_isReadyForConnection = true;
if (Utils.isNullOrEmptry(getAddress())) {
m_isReadyForConnection = false;
Log.i(TAG, "URI missing remote address.");
}
int connType = getConnectionType();
if (secType == Constants.SECTYPE_VNC || connType == Constants.CONN_TYPE_STUNNEL
|| connType == Constants.CONN_TYPE_SSH) {
// we can infer a password is required
// while we could have implemented tunnel/ssh without one
// the user can supply a blank value and the server will not
// request it and it is better to support the common case
if (Utils.isNullOrEmptry(getPassword())) {
m_isReadyForConnection = false;
Log.i(TAG, "URI missing VNC password.");
}
}
if (connType == Constants.CONN_TYPE_SSH) {
// the below should not occur
if (Utils.isNullOrEmptry(getSshServer()))
m_isReadyForConnection = false;
// we probably need either a username/password or a key
// however the main screen doesn't validate this
}
// some other types probably require a username/password
// however main screen doesn't validate this
}
boolean isValidPort(int port) {
final int PORT_MAX = 65535;
if (port <= 0 || port > PORT_MAX)
return false;
return true;
}
@Override
public String toString() {
if (isNew()) {
return c.getString(R.string.new_connection);
}
String result = new String("");
// Add the nickname if it has been set.
if (!getNickname().equals("")) {
result += getNickname()+":";
}
// If this is an VNC over SSH connection, add the SSH server:port in parentheses
if (getConnectionType() == Constants.CONN_TYPE_SSH) {
result += "(" + getSshServer() + ":" + getSshPort() + ")" + ":";
}
// Add the VNC server and port.
result += getAddress()+":"+getPort();
return result;
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(ConnectionBean another) {
int result = getNickname().compareTo(another.getNickname());
if (result == 0) {
result = getConnectionType() - another.getConnectionType();
}
if (result == 0) {
result = getAddress().compareTo(another.getAddress());
}
if ( result == 0) {
result = getPort() - another.getPort();
}
if (result == 0) {
result = getSshServer().compareTo(another.getSshServer());
}
if (result == 0) {
result = getSshPort() - another.getSshPort();
}
return result;
}
/**
* Return the object representing the app global state in the database, or null
* if the object hasn't been set up yet
* @param db App's database -- only needs to be readable
* @return Object representing the single persistent instance of MostRecentBean, which
* is the app's global state
*/
public static MostRecentBean getMostRecent(SQLiteDatabase db) {
ArrayList<MostRecentBean> recents = new ArrayList<MostRecentBean>(1);
MostRecentBean.getAll(db, MostRecentBean.GEN_TABLE_NAME, recents, MostRecentBean.GEN_NEW);
if (recents.size() == 0)
return null;
return recents.get(0);
}
public void saveAndWriteRecent(boolean saveEmpty, Database database) {
// We need server address or SSH server to be filled out to save. Otherwise,
// we keep adding empty connections.
// However, if there is partial data from a URI, we can present the edit screen.
// Alternately, perhaps we could process some extra data
if ((getConnectionType() == Constants.CONN_TYPE_SSH && getSshServer().equals("")
|| getAddress().equals("")) && !saveEmpty) {
return;
}
SQLiteDatabase db = database.getWritableDatabase();
db.beginTransaction();
try {
save(db);
MostRecentBean mostRecent = getMostRecent(db);
if (mostRecent == null) {
mostRecent = new MostRecentBean();
mostRecent.setConnectionId(get_Id());
mostRecent.Gen_insert(db);
} else {
mostRecent.setConnectionId(get_Id());
mostRecent.Gen_update(db);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
db.close();
}
if (db.isOpen()) {
db.close();
}
}
}