From ac90fb21d29bfffc31d5f8f7417efb394566c0ba Mon Sep 17 00:00:00 2001 From: elluisian Date: Wed, 21 Aug 2024 17:22:38 +0200 Subject: [PATCH] Implemented configurable listening address When applied, this commit tries to implement the feature requests reported on the issues #43 and #227. Basically, with this, it is now possible to make the server listen only on certain addresses. This increases security and allows the typical "SSH usage", that is, SSH + publickey auth + local port forwarding + VNC listening on localhost only. To achieve this: - on droidvnc-ng.c, vncServerStart was modified in order to provide a further "jstring listenIf" parameter. It uses rfbScreenInfo*'s listenInterface property. - A TableRow was added in order to allow users to input the desired address to use ("Listening Address"). - On droidvnc-ng.c, the method vncServerGetListenInterface was introduced in order to track if the server is currenctly set to listen to 0.0.0.0 or not (this is used to properly show what addresses are available to use on the UI). Please note that, if an invalid address/host is set, by default, it is assumed the address 0.0.0.0 is requested. --- app/src/main/cpp/droidvnc-ng.c | 21 +++++++- .../christianbeier/droidvnc_ng/Constants.java | 1 + .../christianbeier/droidvnc_ng/Defaults.kt | 4 ++ .../droidvnc_ng/MainActivity.java | 42 ++++++++++++++- .../droidvnc_ng/MainService.java | 53 ++++++++++++++++++- .../droidvnc_ng/OnBootReceiver.java | 1 + app/src/main/res/layout/activity_main.xml | 33 ++++++++++++ app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values/strings.xml | 2 + 13 files changed, 159 insertions(+), 3 deletions(-) diff --git a/app/src/main/cpp/droidvnc-ng.c b/app/src/main/cpp/droidvnc-ng.c index 7724920d..5f6f2a00 100644 --- a/app/src/main/cpp/droidvnc-ng.c +++ b/app/src/main/cpp/droidvnc-ng.c @@ -288,7 +288,7 @@ JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncS } -JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncStartServer(JNIEnv *env, jobject thiz, jint width, jint height, jint port, jstring desktopname, jstring password, jstring httpRootDir) { +JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncStartServer(JNIEnv *env, jobject thiz, jint width, jint height, jstring listenIf, jint port, jstring desktopname, jstring password, jstring httpRootDir) { int argc = 0; @@ -315,6 +315,17 @@ JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncS theScreen->setXCutTextUTF8 = onCutText; theScreen->newClientHook = onClientConnected; + in_addr_t address = 0; // Default is 0.0.0.0 + if (listenIf != NULL) { + const char *listenIfNative = (*env)->GetStringUTFChars(env, listenIf, NULL); + if (!rfbStringToAddr((char*)listenIfNative, &address)) { + address = 0; // If problems arise, assume 0.0.0.0 + } + (*env)->ReleaseStringUTFChars(env, listenIf, listenIfNative); + } + + // With the listenInterface property one can define where the server will be available + theScreen->listenInterface = address; theScreen->port = port; theScreen->ipv6port = port; @@ -491,4 +502,12 @@ JNIEXPORT jint JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncGetFr JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncIsActive(JNIEnv *env, jobject thiz) { return theScreen && rfbIsActive(theScreen); +} + +JNIEXPORT jint JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncGetListenInterface(JNIEnv *env, jobject thiz) { + if (!theScreen) { + return -1; + } + + return theScreen->listenInterface; } \ No newline at end of file diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java b/app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java index 517cc2bd..30a7ec6a 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java @@ -25,6 +25,7 @@ public class Constants { /* user settings */ + public static final String PREFS_KEY_SETTINGS_LISTEN_INTERFACE = "settings_listen_interface"; public static final String PREFS_KEY_SETTINGS_PORT = "settings_port"; public static final String PREFS_KEY_SETTINGS_PASSWORD = "settings_password" ; public static final String PREFS_KEY_SETTINGS_START_ON_BOOT = "settings_start_on_boot" ; diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt b/app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt index e21c31f0..e846d576 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt @@ -40,6 +40,10 @@ class Defaults { private const val PREFS_KEY_DEFAULTS_ACCESS_KEY = "defaults_access_key" } + @EncodeDefault + var listenInterface = "0.0.0.0" + private set + @EncodeDefault var port = 5900 private set diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java b/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java index 817e3976..77c70a65 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java @@ -103,6 +103,7 @@ protected void onCreate(Bundle savedInstanceState) { mButtonToggle.setOnClickListener(view -> { Intent intent = new Intent(MainActivity.this, MainService.class); + intent.putExtra(MainService.EXTRA_LISTEN_INTERFACE, prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface())); intent.putExtra(MainService.EXTRA_PORT, prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort())); intent.putExtra(MainService.EXTRA_PASSWORD, prefs.getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword())); intent.putExtra(MainService.EXTRA_FILE_TRANSFER, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_FILE_TRANSFER, mDefaults.getFileTransfer())); @@ -306,6 +307,35 @@ protected void onCreate(Bundle savedInstanceState) { }); + final EditText listenInterface = findViewById(R.id.settings_listen_interface); + listenInterface.setText(prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface())); + listenInterface.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + // only save new value if it differs from the default and was not saved before + if(!(prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, null) == null && charSequence.toString().equals(mDefaults.getListenInterface()))) { + SharedPreferences.Editor ed = prefs.edit(); + ed.putString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, charSequence.toString()); + ed.apply(); + } + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + listenInterface.setOnFocusChangeListener((v, hasFocus) -> { + // move cursor to end of text + listenInterface.setSelection(listenInterface.getText().length()); + }); + + final EditText port = findViewById(R.id.settings_port); if(prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort()) < 0) { port.setHint(R.string.main_activity_settings_port_not_listening); @@ -757,7 +787,15 @@ private void onServerStarted() { if(MainService.getPort() >= 0) { HashMap> spans = new HashMap<>(); // uhh there must be a nice functional way for this - ArrayList hosts = MainService.getIPv4s(); + ArrayList hosts = null; + + if (MainService.isListeningOnAnyInterface()) { + hosts = MainService.getIPv4s(); + } else { + hosts = new ArrayList<>(); + hosts.add(MainService.getListenInterface()); + } + StringBuilder sb = new StringBuilder(); sb.append(getString(R.string.main_activity_address)).append(" "); for (int i = 0; i < hosts.size(); ++i) { @@ -798,6 +836,7 @@ public void onClick(@NonNull View widget) { findViewById(R.id.outbound_buttons).setVisibility(View.VISIBLE); // indicate that changing these settings does not have an effect when the server is running + findViewById(R.id.settings_listen_interface).setEnabled(false); findViewById(R.id.settings_port).setEnabled(false); findViewById(R.id.settings_password).setEnabled(false); findViewById(R.id.settings_access_key).setEnabled(false); @@ -823,6 +862,7 @@ private void onServerStopped() { findViewById(R.id.outbound_buttons).setVisibility(View.GONE); // indicate that changing these settings does have an effect when the server is stopped + findViewById(R.id.settings_listen_interface).setEnabled(true); findViewById(R.id.settings_port).setEnabled(true); findViewById(R.id.settings_password).setEnabled(true); findViewById(R.id.settings_access_key).setEnabled(true); diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java b/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java index 8af10220..cb8571fc 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java @@ -73,6 +73,7 @@ public class MainService extends Service { public static final String ACTION_CONNECT_REPEATER = "net.christianbeier.droidvnc_ng.ACTION_CONNECT_REPEATER"; public static final String EXTRA_REQUEST_ID = "net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID"; public static final String EXTRA_REQUEST_SUCCESS = "net.christianbeier.droidvnc_ng.EXTRA_REQUEST_SUCCESS"; + public static final String EXTRA_LISTEN_INTERFACE = "net.christianbeier.droidvnc_ng.EXTRA_LISTEN_INTERFACE"; public static final String EXTRA_HOST = "net.christianbeier.droidvnc_ng.EXTRA_HOST"; public static final String EXTRA_PORT = "net.christianbeier.droidvnc_ng.EXTRA_PORT"; public static final String EXTRA_REPEATER_ID = "net.christianbeier.droidvnc_ng.EXTRA_REPEATER_ID"; @@ -104,6 +105,10 @@ public class MainService extends Service { final static String ACTION_HANDLE_NOTIFICATION_RESULT = "action_handle_notification_result"; + // Used to correctly signal if the server is listening on the "any address" (0.0.0.0) + private static final String PREFS_KEY_SERVER_LAST_LISTEN_WAS_ANY = "server_last_listen_was_any" ; + + private static final String PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE = "server_last_listen_interface" ; private static final String PREFS_KEY_SERVER_LAST_PORT = "server_last_port" ; private static final String PREFS_KEY_SERVER_LAST_PASSWORD = "server_last_password" ; private static final String PREFS_KEY_SERVER_LAST_FILE_TRANSFER = "server_last_file_transfer" ; @@ -166,9 +171,10 @@ public void onServiceUnregistered(NsdServiceInfo nsdServiceInfo) { } @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private native boolean vncStartServer(int width, int height, int port, String desktopName, String password, String httpRootDir); + private native boolean vncStartServer(int width, int height, String listenIf, int port, String desktopName, String password, String httpRootDir); private native boolean vncStopServer(); private native boolean vncIsActive(); + private native int vncGetListenInterface(); private native long vncConnectReverse(String host, int port); private native long vncConnectRepeater(String host, int port, String repeaterIdentifier); static native boolean vncNewFramebuffer(int width, int height); @@ -331,16 +337,21 @@ public int onStartCommand(Intent intent, int flags, int startId) startScreenCapture(); } else { DisplayMetrics displayMetrics = Utils.getDisplayMetrics(this, Display.DEFAULT_DISPLAY); + String listenIf = PreferenceManager.getDefaultSharedPreferences(this).getString(PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE, mDefaults.getListenInterface()).toLowerCase(); int port = PreferenceManager.getDefaultSharedPreferences(this).getInt(PREFS_KEY_SERVER_LAST_PORT, mDefaults.getPort()); // get device name String name = Utils.getDeviceName(this); boolean status = vncStartServer(displayMetrics.widthPixels, displayMetrics.heightPixels, + listenIf, port, name, PreferenceManager.getDefaultSharedPreferences(this).getString(PREFS_KEY_SERVER_LAST_PASSWORD, mDefaults.getPassword()), getFilesDir().getAbsolutePath() + File.separator + "novnc"); + + this.updateLastListenWasAnyFlag(); + Intent answer = new Intent(ACTION_START); answer.putExtra(EXTRA_REQUEST_ID, PreferenceManager.getDefaultSharedPreferences(this).getString(PREFS_KEY_SERVER_LAST_START_REQUEST_ID, null)); answer.putExtra(EXTRA_REQUEST_SUCCESS, status); @@ -374,15 +385,19 @@ public int onStartCommand(Intent intent, int flags, int startId) if (mResultCode != 0 && mResultData != null || (Build.VERSION.SDK_INT >= 30 && PreferenceManager.getDefaultSharedPreferences(this).getBoolean(PREFS_KEY_SERVER_LAST_FALLBACK_SCREEN_CAPTURE, false))) { DisplayMetrics displayMetrics = Utils.getDisplayMetrics(this, Display.DEFAULT_DISPLAY); + String listenIf = PreferenceManager.getDefaultSharedPreferences(this).getString(PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE, mDefaults.getListenInterface()).toLowerCase(); int port = PreferenceManager.getDefaultSharedPreferences(this).getInt(PREFS_KEY_SERVER_LAST_PORT, mDefaults.getPort()); String name = Utils.getDeviceName(this); boolean status = vncStartServer(displayMetrics.widthPixels, displayMetrics.heightPixels, + listenIf, port, name, PreferenceManager.getDefaultSharedPreferences(this).getString(PREFS_KEY_SERVER_LAST_PASSWORD, mDefaults.getPassword()), getFilesDir().getAbsolutePath() + File.separator + "novnc"); + this.updateLastListenWasAnyFlag(); + Intent answer = new Intent(ACTION_START); answer.putExtra(EXTRA_REQUEST_ID, PreferenceManager.getDefaultSharedPreferences(this).getString(PREFS_KEY_SERVER_LAST_START_REQUEST_ID, null)); answer.putExtra(EXTRA_REQUEST_SUCCESS, status); @@ -445,6 +460,7 @@ public int onStartCommand(Intent intent, int flags, int startId) // Step 0: persist given arguments to be able to recover from possible crash later final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences.Editor ed = prefs.edit(); + ed.putString(PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE, intent.getStringExtra(EXTRA_LISTEN_INTERFACE) != null ? intent.getStringExtra(EXTRA_LISTEN_INTERFACE) : prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface())); ed.putInt(PREFS_KEY_SERVER_LAST_PORT, intent.getIntExtra(EXTRA_PORT, prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort()))); ed.putString(PREFS_KEY_SERVER_LAST_PASSWORD, intent.getStringExtra(EXTRA_PASSWORD) != null ? intent.getStringExtra(EXTRA_PASSWORD) : prefs.getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword())); ed.putBoolean(PREFS_KEY_SERVER_LAST_FILE_TRANSFER, intent.getBooleanExtra(EXTRA_FILE_TRANSFER, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_FILE_TRANSFER, mDefaults.getFileTransfer()))); @@ -744,6 +760,41 @@ private void sendBroadcastToOthersAndUs(Intent intent) { } } + + /** + * It updates the flag that signals if the server is listening on the "any interface" (0.0.0.0) + * @return void + */ + private void updateLastListenWasAnyFlag() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor ed = prefs.edit(); + ed.putBoolean(PREFS_KEY_SERVER_LAST_LISTEN_WAS_ANY, this.vncGetListenInterface() == 0); + ed.apply(); + } + + + + static boolean isListeningOnAnyInterface() { + try { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(instance); + return prefs.getBoolean(PREFS_KEY_SERVER_LAST_LISTEN_WAS_ANY, false); + } catch (Exception e) { + return false; + } + } + + + static String getListenInterface() { + try { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(instance); + return prefs.getString(PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE, new Defaults(instance).getListenInterface()); + } catch (Exception e) { + return null; + } + } + + + static boolean isServerActive() { try { return instance.vncIsActive(); diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/OnBootReceiver.java b/app/src/main/java/net/christianbeier/droidvnc_ng/OnBootReceiver.java index 78de35bb..c45eb50b 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/OnBootReceiver.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/OnBootReceiver.java @@ -54,6 +54,7 @@ public void onReceive(Context context, Intent arg1) Intent intent = new Intent(context, MainService.class); intent.setAction(MainService.ACTION_START); + intent.putExtra(MainService.EXTRA_LISTEN_INTERFACE, prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, defaults.getListenInterface())); intent.putExtra(MainService.EXTRA_PORT, prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, defaults.getPort())); intent.putExtra(MainService.EXTRA_PASSWORD, prefs.getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, defaults.getPassword())); intent.putExtra(MainService.EXTRA_FILE_TRANSFER, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_FILE_TRANSFER, defaults.getFileTransfer())); diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c6adcf0c..afdee0b0 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -35,7 +35,40 @@ android:layout_weight="2" android:layout_column="0" android:padding="10dp" +<<<<<<< HEAD android:hyphenationFrequency="full" +======= + android:text="@string/main_activity_settings_listening_address" /> + + + + + + + + + >>>>>> 4710b51 (Implemented configurable listening address) android:text="@string/main_activity_settings_port" /> droidVNC-NG Admin-Panel Einstellungen + Höradresse Port Eingehende Verbindungen deaktiviert Passwort diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 19876f14..9d563f2b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,6 +1,7 @@ Amministrazione di droidVNC-NG Impostazioni + Indirizzo d\'ascolto Porta Connessioni in entrata disabilitate Password diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 1850aeff..3a64040d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -2,6 +2,7 @@ droidVNC-NG droidVNC-NG管理パネル 設定 + リスニングアドレス ポート インバウンド接続は無効です パスワード diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 26bf3e24..039961f6 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,6 +1,7 @@ Painel Administrativo do droidVNC-NG Configurações + Endereço de escuta Porta Conexões de entrada desativadas Senha diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 19192988..496dd0b2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,6 +1,7 @@ droidVNC-NG管理面板 设置 + 監聽地址 端口 禁用传入连接 密码 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b693ab56..8191d9c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,9 @@ droidVNC-NG + 0.0.0.0 droidVNC-NG Admin Panel Settings + Listening Address Port Inbound connections disabled Password