diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2a246a41..12465060 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -69,6 +69,11 @@ along with SwiFTP. If not, see .
android:parentActivityName=".gui.MainActivity"
android:theme="@style/AppThemeDark" />
+
+
diff --git a/app/src/main/java/be/ppareit/swiftp/FsSettings.java b/app/src/main/java/be/ppareit/swiftp/FsSettings.java
index 61749370..2fd9ba35 100644
--- a/app/src/main/java/be/ppareit/swiftp/FsSettings.java
+++ b/app/src/main/java/be/ppareit/swiftp/FsSettings.java
@@ -138,6 +138,14 @@ public static int getPortNumber() {
return port;
}
+ public static int getAnonMaxConNumber() {
+ final SharedPreferences sp = getSharedPreferences();
+ String s = sp.getString("anon_max", "1");
+ int i = Integer.parseInt(s);
+ Log.v(TAG, "Using anon max connections: " + i);
+ return i;
+ }
+
public static boolean shouldTakeFullWakeLock() {
final SharedPreferences sp = getSharedPreferences();
return sp.getBoolean("stayAwake", false);
diff --git a/app/src/main/java/be/ppareit/swiftp/gui/ManageAnonActivity.java b/app/src/main/java/be/ppareit/swiftp/gui/ManageAnonActivity.java
new file mode 100644
index 00000000..07248821
--- /dev/null
+++ b/app/src/main/java/be/ppareit/swiftp/gui/ManageAnonActivity.java
@@ -0,0 +1,136 @@
+package be.ppareit.swiftp.gui;
+
+
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.core.app.NavUtils;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.preference.PreferenceManager;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import net.vrallev.android.cat.Cat;
+
+import be.ppareit.swiftp.App;
+import be.ppareit.swiftp.FsSettings;
+import be.ppareit.swiftp.R;
+import be.ppareit.swiftp.utils.ChrootPicker;
+
+public class ManageAnonActivity extends AppCompatActivity {
+
+ private static final int ACTION_OPEN_DOCUMENT_TREE = 42;
+ ChrootPicker chrootPicker = null;
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(FsSettings.getTheme());
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.anon_layout);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ chrootPicker = new ChrootPicker();
+ TextView chroot = findViewById(R.id.anon_chroot);
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(App.getAppContext());
+ String chrootString = sp.getString("anonChroot", "");
+ chroot.setText(chrootString);
+ chroot.setOnTouchListener((v, event) -> {
+ sp.edit().remove("anonChroot").apply();
+ sp.edit().remove("anonUriString").apply();
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ chrootPicker.showFolderPicker(chroot.getText().toString(), this, null);
+ }
+ return true;
+ });
+ chrootPicker.setOnTextEventListener(s -> {
+ chroot.setText(s);
+ sp.edit().putString("anonChroot", s).apply();
+ });
+ chrootPicker.setOnActionTreeEventListener(() -> {
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ startActivityForResult(intent, ACTION_OPEN_DOCUMENT_TREE);
+ });
+
+ EditText anonMaxCon = findViewById(R.id.anon_max);
+ anonMaxCon.setText(String.valueOf(FsSettings.getAnonMaxConNumber()));
+ anonMaxCon.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ sp.edit().putString("anon_max", s.toString()).apply();
+ }
+ });
+
+ CheckBox anonCB = findViewById(R.id.anon_enable);
+ anonCB.setChecked(sp.getBoolean("allow_anonymous", false));
+ anonCB.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ SharedPreferences sp1 = PreferenceManager.getDefaultSharedPreferences(App.getAppContext());
+ if (isChecked ) {
+ if (sp.getString("anonChroot", "").isEmpty()) {
+ // Deny as workaround to problems like app crashing with scoped storage.
+ anonCB.setChecked(false);
+ final ObjectAnimator loading = ObjectAnimator.ofObject((TextView) chroot, "backgroundColor",
+ new ArgbEvaluator(), Color.RED, Color.TRANSPARENT).setDuration(1000);
+ loading.start();
+ return;
+ }
+ sp1.edit().putBoolean("allow_anonymous", true).apply();
+ }
+ else sp1.edit().putBoolean("allow_anonymous", false).apply();
+ });
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+ super.onActivityResult(requestCode, resultCode, resultData);
+ Cat.d("onActivityResult called");
+ if (requestCode == ACTION_OPEN_DOCUMENT_TREE && resultCode == Activity.RESULT_OK) {
+ if (resultData == null) return;
+ Uri treeUri = resultData.getData();
+ if (treeUri == null) return;
+ String path = treeUri.getPath();
+ Cat.d("Action Open Document Tree on path " + path);
+ chrootPicker.save(this.getApplicationContext(), treeUri);
+ String uriString = treeUri.getPath();
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(App.getAppContext());
+ sp.edit().putString("anonUriString", uriString).apply();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ppareit/swiftp/gui/PreferenceFragment.java b/app/src/main/java/be/ppareit/swiftp/gui/PreferenceFragment.java
index 23e37a13..0a88e742 100644
--- a/app/src/main/java/be/ppareit/swiftp/gui/PreferenceFragment.java
+++ b/app/src/main/java/be/ppareit/swiftp/gui/PreferenceFragment.java
@@ -110,6 +110,12 @@ public void onCreate(Bundle savedInstanceState) {
return true;
});
+ Preference manageAnonPref = findPref("manage_anon");
+ manageAnonPref.setOnPreferenceClickListener((preference) -> {
+ startActivity(new Intent(getActivity(), ManageAnonActivity.class));
+ return true;
+ });
+
EditTextPreference portNumberPref = findPref("portNum");
portNumberPref.setSummary(String.valueOf(FsSettings.getPortNumber()));
portNumberPref.setOnPreferenceChangeListener((preference, newValue) -> {
diff --git a/app/src/main/java/be/ppareit/swiftp/gui/UserEditFragment.java b/app/src/main/java/be/ppareit/swiftp/gui/UserEditFragment.java
index 2a4d1018..ea926d23 100644
--- a/app/src/main/java/be/ppareit/swiftp/gui/UserEditFragment.java
+++ b/app/src/main/java/be/ppareit/swiftp/gui/UserEditFragment.java
@@ -1,12 +1,15 @@
package be.ppareit.swiftp.gui;
-import android.app.AlertDialog;
+import android.app.Activity;
import android.app.Fragment;
+import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
-import android.os.Environment;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -14,17 +17,22 @@
import android.widget.TextView;
import android.widget.Toast;
-import java.io.File;
+import net.vrallev.android.cat.Cat;
import be.ppareit.swiftp.FsSettings;
import be.ppareit.swiftp.R;
import be.ppareit.swiftp.server.FtpUser;
+import be.ppareit.swiftp.utils.ChrootPicker;
public class UserEditFragment extends Fragment {
+ private static final int ACTION_OPEN_DOCUMENT_TREE = 42;
+
private FtpUser item;
private OnEditFinishedListener editFinishedListener;
- private boolean isShowingFolderPicker = false;
+ private TextView chroot = null;
+ private String uriString = "";
+ private ChrootPicker chrootPicker = null;
public static UserEditFragment newInstance(@Nullable FtpUser item, @NonNull OnEditFinishedListener listener) {
UserEditFragment fragment = new UserEditFragment();
@@ -40,14 +48,21 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
View root = inflater.inflate(R.layout.user_edit_layout, container, false);
EditText username = (EditText) root.findViewById(R.id.user_edit_name);
EditText password = (EditText) root.findViewById(R.id.user_edit_password);
- TextView chroot = (TextView) root.findViewById(R.id.user_edit_chroot);
+ chrootPicker = new ChrootPicker();
+ chroot = (TextView) root.findViewById(R.id.user_edit_chroot);
chroot.setText(FsSettings.getDefaultChrootDir().getPath());
chroot.setOnFocusChangeListener((v, hasFocus) -> {
- if (!hasFocus)
- return;
- showFolderPicker(chroot);
+ if (!hasFocus) return;
+ chrootPicker.showFolderPicker(chroot.getText().toString(), null, getContext());
+ });
+ chroot.setOnClickListener(v -> {
+ chrootPicker.showFolderPicker(chroot.getText().toString(), null, getContext());
+ });
+ chrootPicker.setOnTextEventListener(s -> chroot.setText(s));
+ chrootPicker.setOnActionTreeEventListener(() -> {
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ startActivityForResult(intent, ACTION_OPEN_DOCUMENT_TREE);
});
- chroot.setOnClickListener(view -> showFolderPicker(chroot));
if (item != null) {
username.setText(item.getUsername());
@@ -60,7 +75,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
String newPassword = password.getText().toString();
String newChroot = chroot.getText().toString();
if (validateInput(newUsername, newPassword)) {
- editFinishedListener.onEditActionFinished(item, new FtpUser(newUsername, newPassword, newChroot));
+ editFinishedListener.onEditActionFinished(item, new FtpUser(newUsername, newPassword, newChroot, uriString));
getActivity().onBackPressed();
}
});
@@ -68,30 +83,20 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
return root;
}
- private void showFolderPicker(TextView chrootView) {
- if (isShowingFolderPicker)
- return;
- isShowingFolderPicker = true;
- final File startDir;
- if (chrootView.getText().toString().isEmpty()) {
- startDir = Environment.getExternalStorageDirectory();
- } else {
- startDir = new File((chrootView.getText().toString()));
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+ Cat.d("onActivityResult called");
+ if (requestCode == ACTION_OPEN_DOCUMENT_TREE && resultCode == Activity.RESULT_OK) {
+ if (resultData == null) return;
+ Uri treeUri = resultData.getData();
+ if (treeUri == null) return;
+ String path = treeUri.getPath();
+ Cat.d("Action Open Document Tree on path " + path);
+ // *************************************
+ // The order following here is critical. They must stay ordered as they are.
+ chrootPicker.save(this.getContext(), treeUri);
+ uriString = treeUri.getPath();
}
- AlertDialog folderPicker = new FolderPickerDialogBuilder(getActivity(), startDir)
- .setSelectedButton(R.string.select, path -> {
- final File root = new File(path);
- if (!root.canRead()) {
- showToast(R.string.notice_cant_read_write);
- } else if (!root.canWrite()) {
- showToast(R.string.notice_cant_write);
- }
- chrootView.setText(path);
- })
- .setNegativeButton(R.string.cancel, null)
- .create();
- folderPicker.setOnDismissListener(dialog -> isShowingFolderPicker = false);
- folderPicker.show();
}
private boolean validateInput(String username, String password) {
diff --git a/app/src/main/java/be/ppareit/swiftp/server/CmdPASS.java b/app/src/main/java/be/ppareit/swiftp/server/CmdPASS.java
index 7f2821b1..0a53eb8f 100644
--- a/app/src/main/java/be/ppareit/swiftp/server/CmdPASS.java
+++ b/app/src/main/java/be/ppareit/swiftp/server/CmdPASS.java
@@ -19,9 +19,15 @@
package be.ppareit.swiftp.server;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
import android.util.Log;
+
+import be.ppareit.swiftp.App;
import be.ppareit.swiftp.FsSettings;
import be.ppareit.swiftp.Util;
+import be.ppareit.swiftp.utils.AnonymousLimit;
+import be.ppareit.swiftp.utils.Logging;
public class CmdPASS extends FtpCmd implements Runnable {
private static final String TAG = CmdPASS.class.getSimpleName();
@@ -44,8 +50,35 @@ public void run() {
return;
}
if (attemptUsername.equals("anonymous") && FsSettings.allowAnonymous()) {
- Log.i(TAG, "Guest logged in with email: " + attemptPassword);
- sessionThread.writeString("230 Guest login ok, read only access.\r\n");
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(App.getAppContext());
+ final int anonMaxCon = Integer.parseInt(sp.getString("anon_max", "1"));
+ Logging logging = new Logging();
+ final int newCount = AnonymousLimit.incrementAndGet();
+ logging.appendLog("anon CURRENT client conn count: " + (newCount - 1));
+ logging.appendLog("anon MAX conn count: " + anonMaxCon);
+ if (newCount > anonMaxCon) {
+ Log.i(TAG, "Failed authentication, too many anonymous users connected.");
+ Util.sleepIgnoreInterrupt(1000); // sleep to foil brute force attack
+ sessionThread.writeString("421 too many anonymous users connected.\r\n");
+ sessionThread.authAttempt(false);
+ } else {
+ Log.i(TAG, "Guest logged in with email: " + attemptPassword);
+ sessionThread.writeString("230 Guest login ok, read only access.\r\n");
+ final String anonChroot = sp.getString("anonChroot", "/storage/emulated/0" /*backwards compat*/);
+ final String anonUriString = sp.getString("anonUriString", "");
+ if (!anonChroot.isEmpty()) {
+ sessionThread.setChrootDir(anonChroot);
+ if (!anonUriString.isEmpty()) {
+ SessionThread.putUriString(Thread.currentThread().getName(), anonUriString);
+ } else if (Util.useScopedStorage()) {
+ // Protect against app crashes/problems
+ Log.i(TAG, "Failed authentication, too many anonymous users connected.");
+ Util.sleepIgnoreInterrupt(1000); // sleep to foil brute force attack
+ sessionThread.writeString("421 too many anonymous users connected.\r\n");
+ sessionThread.authAttempt(false);
+ }
+ }
+ }
return;
}
FtpUser user = FsSettings.getUser(attemptUsername);
@@ -59,6 +92,9 @@ public void run() {
sessionThread.writeString("230 Access granted\r\n");
sessionThread.authAttempt(true);
sessionThread.setChrootDir(user.getChroot());
+ if (Util.useScopedStorage()) {
+ SessionThread.putUriString(Thread.currentThread().getName(), user.getUriString());
+ }
} else {
Log.i(TAG, "Failed authentication, incorrect password");
Util.sleepIgnoreInterrupt(1000); // sleep to foil brute force attack
diff --git a/app/src/main/java/be/ppareit/swiftp/server/SessionThread.java b/app/src/main/java/be/ppareit/swiftp/server/SessionThread.java
index 7a485ff8..2b9ede2c 100644
--- a/app/src/main/java/be/ppareit/swiftp/server/SessionThread.java
+++ b/app/src/main/java/be/ppareit/swiftp/server/SessionThread.java
@@ -35,6 +35,7 @@
import be.ppareit.swiftp.App;
import be.ppareit.swiftp.FsSettings;
+import be.ppareit.swiftp.utils.AnonymousLimit;
public class SessionThread extends Thread {
@@ -256,6 +257,7 @@ public void run() {
Cat.i("Connection was dropped");
}
closeSocket();
+ AnonymousLimit.decrement();
}
public void closeSocket() {
diff --git a/app/src/main/java/be/ppareit/swiftp/utils/AnonymousLimit.java b/app/src/main/java/be/ppareit/swiftp/utils/AnonymousLimit.java
new file mode 100644
index 00000000..6dfce0ac
--- /dev/null
+++ b/app/src/main/java/be/ppareit/swiftp/utils/AnonymousLimit.java
@@ -0,0 +1,15 @@
+package be.ppareit.swiftp.utils;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AnonymousLimit {
+ static AtomicInteger anonUsersConnected = new AtomicInteger(0);
+
+ public static int incrementAndGet() {
+ return anonUsersConnected.incrementAndGet();
+ }
+
+ public static void decrement() {
+ if (anonUsersConnected.get() >= 1) anonUsersConnected.decrementAndGet();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ppareit/swiftp/utils/ChrootPicker.java b/app/src/main/java/be/ppareit/swiftp/utils/ChrootPicker.java
new file mode 100644
index 00000000..853c73e6
--- /dev/null
+++ b/app/src/main/java/be/ppareit/swiftp/utils/ChrootPicker.java
@@ -0,0 +1,124 @@
+package be.ppareit.swiftp.utils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.documentfile.provider.DocumentFile;
+
+import java.io.File;
+
+import be.ppareit.swiftp.FsSettings;
+import be.ppareit.swiftp.R;
+import be.ppareit.swiftp.Util;
+import be.ppareit.swiftp.gui.FolderPickerDialogBuilder;
+
+public class ChrootPicker {
+
+ public ChrootPicker() {
+ }
+
+ public interface OnTextEventListener {
+ void OnEvent(String s);
+ }
+
+ public interface OnActionEventListener {
+ void OnEvent();
+ }
+
+ public OnTextEventListener onTextEventListener;
+ public OnActionEventListener onActionEventListener;
+
+ public void setOnTextEventListener(OnTextEventListener onTextEventListener) {
+ this.onTextEventListener = onTextEventListener;
+ }
+
+ public void setOnActionTreeEventListener(OnActionEventListener onActionEventListener) {
+ this.onActionEventListener = onActionEventListener;
+ }
+
+ private boolean isShowingFolderPicker = false;
+
+ public void save(Context context, Uri treeUri) {
+ // *************************************
+ // The order following here is critical. They must stay ordered as they are.
+ setPermissionToUseExternalStorage(treeUri, context);
+ scopedStorageChrootOverride(treeUri);
+ }
+
+ private void setPermissionToUseExternalStorage(Uri treeUri, Context context) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ FsSettings.setExternalStorageUri(treeUri.toString());
+ context.getContentResolver()
+ .takePersistableUriPermission(treeUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ }
+ } catch (SecurityException e) {
+ // Harden code against crash: May reach here by adding exact same picker location but
+ // being removed at same time.
+ }
+ }
+
+ private void scopedStorageChrootOverride(Uri treeUri) {
+ if (Util.useScopedStorage()) {
+ DocumentFile df = FileUtil.getDocumentFileFromUri(treeUri);
+ if (df == null) return;
+ String newPath = "/storage/emulated/0/";
+ String treePath = treeUri.getPath();
+ if (treePath == null) return;
+ if (treePath.contains("primary:"))
+ treePath = treePath.substring(treePath.indexOf(":") + 1);
+ else if (treePath.contains(":")) {
+ newPath = "/storage/";
+ treePath = treePath.replace("/tree/", "");
+ treePath = treePath.replace(":", "/");
+ }
+ newPath += treePath;
+ if (onTextEventListener != null) onTextEventListener.OnEvent(newPath);
+ }
+ }
+
+ public void showFolderPicker(String s, @Nullable Activity a, Context fragment /*Fragment use*/) {
+ if (Util.useScopedStorage()) {
+ if (onActionEventListener != null) onActionEventListener.OnEvent();
+ return;
+ }
+ if (isShowingFolderPicker)
+ return;
+ isShowingFolderPicker = true;
+ final File startDir;
+ if (s.isEmpty()) {
+ startDir = Environment.getExternalStorageDirectory();
+ } else {
+ startDir = new File(s);
+ }
+ AlertDialog folderPicker = new FolderPickerDialogBuilder(a != null ? a : fragment, startDir)
+ .setSelectedButton(R.string.select, path -> {
+ final File root = new File(path);
+ if (!root.canRead()) {
+ showToast(R.string.notice_cant_read_write,
+ a != null ? a : fragment);
+ } else if (!root.canWrite()) {
+ showToast(R.string.notice_cant_write,
+ a != null ? a : fragment);
+ }
+ if (onTextEventListener != null) onTextEventListener.OnEvent(path);
+ })
+ .setNegativeButton(R.string.cancel, null)
+ .create();
+ folderPicker.setOnDismissListener(dialog -> isShowingFolderPicker = false);
+ folderPicker.show();
+ }
+
+ private void showToast(int errorResId, Context context) {
+ Toast.makeText(context, errorResId, Toast.LENGTH_LONG).show();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/anon_layout.xml b/app/src/main/res/layout/anon_layout.xml
new file mode 100644
index 00000000..d819cffa
--- /dev/null
+++ b/app/src/main/res/layout/anon_layout.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index dc66cfee..72f4532d 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -89,4 +89,6 @@ Gemeldete Probleme und Verbesserungsvorschläge sind unter https://github.com/pp
Gemischtes Thema
Thema...
+ Max. gleichzeitige Verbindungen
+ Anonym verwalten…
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 8bae480d..7237ca8b 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -143,5 +143,7 @@ https://github.com/ppareit/swiftp/issues .\n\n
Φωτεινό Θέμα
Ανάμεικτο Θέμα
+ Μέγιστες ταυτόχρονες συνδέσεις
+ Διαχείριση ανώνυμων…
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index e01237ed..185b4100 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -166,5 +166,7 @@ implementarán, mira el registro de incidencias en https://github.com/ppareit/sw
En estos dispositivos, toda lo guardado está disponible utilizando la jerarquía de archivos.
+ Conexiones simultáneas máximas
+ Administrar anónimo…
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index cb8d23d5..9db38183 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -148,4 +148,6 @@ https://github.com/ppareit/swiftp/issues .\n\n
Thème mixte
Thème...
+ Nombre maximum de connexions simultanées
+ Gérer les anonymes…
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 75689352..e12272f6 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -93,4 +93,6 @@ https://github.com/ppareit/swiftp/issues .\n\n
Tema misto
Tema...
+ Numero massimo di connessioni simultanee
+ Gestisci anonimo…
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 68299ef8..b44d436b 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -127,4 +127,6 @@ https://github.com/ppareit/swiftp/issues を参照してください。\n\n
混合テーマ
テーマ...
+ 最大同時接続数
+ 匿名で管理する…
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 624f0a52..6678ea3e 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -95,4 +95,6 @@ van de volgende versie staan op https://github.com/ppareit/swiftp/issues .\n\n
Gemengd thema
Thema...
+ Maximaal gelijktijdige verbindingen
+ Beheer anoniem…
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 622afa3b..291443ee 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -192,4 +192,6 @@ na stronie https://github.com/ppareit/swiftp/issues .\n\n
Na tych urządzeniach cały sklep jest dostępny przy użyciu hierarchii plików.
+ Maksymalna liczba jednoczesnych połączeń
+ Zarządzaj anonimowo…
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index e5560b8c..186509a6 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -108,4 +108,6 @@ https://github.com/ppareit/swiftp/issues .\n\n
Смешанная тема
Тема...
+ Максимальное количество одновременных подключений
+ Управление анонимностью…
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index 80834db1..f05f553c 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -194,4 +194,6 @@ https://github.com/ppareit/swiftp/issues .\n\n
Në këto pajisje, e gjithë dyqani është në dispozicion duke përdorur hierarkinë e skedarit.
+ Lidhjet maksimale të njëkohshme
+ Menaxho anonim…
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 53fa7851..475cc905 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -127,4 +127,6 @@ https://github.com/ppareit/swiftp/issues .\n\n
микед Тема
Тема...
+ Максимално истовремене везе
+ Управљајте анонимно…
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 3519dae6..7a63e081 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -165,5 +165,6 @@ https://github.com/ppareit/swiftp/issues .\n\n
На цих пристроях вся пам\'ять доступна за допомогою ієрархії файлів.
-
+ Максимальна кількість одночасних підключень
+ Керувати анонімно…
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index c3153a07..5aef6ace 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -82,4 +82,6 @@ https://github.com/ppareit/swiftp/issues 提交您的建议。\n\n
警告:当前存储暂不可用,您可能需要取消挂载。
FTP Server Widget
+ 最大同时连接数
+ 管理匿名…
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 08d506a6..21444d23 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -182,4 +182,6 @@ https://github.com/ppareit/swiftp/issues 提交您的建議。\n\n
在這些裝置上,可以使用檔案階層標準來使用所有存儲空間。
+ 最大同時連線數
+ 管理匿名…
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2398f273..8c49f3c7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -193,4 +193,6 @@ https://github.com/ppareit/swiftp/issues .\n\n
On these devices, all store is available using the file hierarchy.
+ Max simultaneous connections
+ Manage anonymous…
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index b1417a93..de3d41b6 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -38,11 +38,9 @@ along with SwiFTP. If not, see .
android:key="manage_users"
android:title="@string/manage_users_label" />
-
+