diff --git a/app/src/main/java/com/emanuelef/remote_capture/Log.java b/app/src/main/java/com/emanuelef/remote_capture/Log.java index c93459ee..53e1f3f7 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/Log.java +++ b/app/src/main/java/com/emanuelef/remote_capture/Log.java @@ -24,18 +24,20 @@ import androidx.annotation.Nullable; public class Log { public static final int LOG_LEVEL_INFO = 4; + public static final String DEFAULT_LOGGER_PATH = "pcapdroid.log"; + public static final String ROOT_LOGGER_PATH = "pcapd.log"; + public static final String MITM_LOGGER_PATH = "mitmaddon.log"; public static int DEFAULT_LOGGER; public static int MITMADDON_LOGGER; - public static void init(String basedir) { - DEFAULT_LOGGER = CaptureService.initLogger(basedir + "/pcapdroid.log", LOG_LEVEL_INFO); - MITMADDON_LOGGER = CaptureService.initLogger(basedir + "/mitmaddon.log", LOG_LEVEL_INFO); - android.util.Log.e("tag", basedir); + public static void init(String cachedir) { + DEFAULT_LOGGER = CaptureService.initLogger(cachedir + "/" + DEFAULT_LOGGER_PATH, LOG_LEVEL_INFO); + MITMADDON_LOGGER = CaptureService.initLogger(cachedir + "/" + MITM_LOGGER_PATH, LOG_LEVEL_INFO); } public static void writeLog(int logger, int level, @Nullable String tag, @NonNull String message) { if(!PCAPdroid.isUnderTest()) - CaptureService.writeLog(logger, level, "[" + tag + "] " + message); + CaptureService.writeLog(logger, level, ((tag != null) ? ("[" + tag + "] ") : "") + message); } public static void d(@Nullable String tag, @NonNull String message) { @@ -47,18 +49,44 @@ public class Log { writeLog(DEFAULT_LOGGER, android.util.Log.INFO, tag, message); } + public static void i(int logger, @NonNull String message) { + writeLog(logger, android.util.Log.INFO, null, message); + } + public static void w(@Nullable String tag, @NonNull String message) { android.util.Log.w(tag, message); writeLog(DEFAULT_LOGGER, android.util.Log.WARN, tag, message); } + public static void w(int logger, @NonNull String message) { + writeLog(logger, android.util.Log.WARN, null, message); + } + public static void e(@Nullable String tag, @NonNull String message) { android.util.Log.e(tag, message); writeLog(DEFAULT_LOGGER, android.util.Log.ERROR, tag, message); } + public static void e(int logger, @NonNull String message) { + writeLog(logger, android.util.Log.ERROR, null, message); + } + public static void wtf(@Nullable String tag, @NonNull String message) { android.util.Log.wtf(tag, message); writeLog(DEFAULT_LOGGER, android.util.Log.ASSERT, tag, message); // ANDROID_LOG_FATAL } + + public static void level(int logger, int level, @NonNull String message) { + switch (level) { + case android.util.Log.INFO: + Log.i(logger, message); + break; + case android.util.Log.WARN: + Log.w(logger, message); + break; + case android.util.Log.ERROR: + Log.e(logger, message); + break; + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/emanuelef/remote_capture/MitmAddon.java b/app/src/main/java/com/emanuelef/remote_capture/MitmAddon.java index e05f6490..0ddba0ca 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/MitmAddon.java +++ b/app/src/main/java/com/emanuelef/remote_capture/MitmAddon.java @@ -48,8 +48,8 @@ import java.io.IOException; import java.lang.ref.WeakReference; public class MitmAddon { - public static final long PACKAGE_VERSION_CODE = 9; - public static final String PACKAGE_VERSION_NAME = "v0.9"; + public static final long PACKAGE_VERSION_CODE = 10; + public static final String PACKAGE_VERSION_NAME = "v0.10"; public static final String REPOSITORY = "https://github.com/emanuele-f/PCAPdroid-mitm"; private static final String TAG = "MitmAddon"; private final Context mContext; diff --git a/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java b/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java index 7be64528..ace5e1c8 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java +++ b/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java @@ -91,6 +91,7 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener WEBSOCKET_CLIENT_MSG, WEBSOCKET_SERVER_MSG, MASTER_SECRET, + LOG, } private static class PendingMessage { @@ -234,7 +235,9 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener if(type == MsgType.MASTER_SECRET) logMasterSecret(msg); - else if(type == MsgType.RUNNING) { + else if(type == MsgType.LOG) { + handleLog(msg); + } else if(type == MsgType.RUNNING) { Log.i(TAG, "MITM proxy is running"); proxyRunning.postValue(true); } else { @@ -347,6 +350,8 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener return MsgType.WEBSOCKET_SERVER_MSG; case "secret": return MsgType.MASTER_SECRET; + case "log": + return MsgType.LOG; default: return MsgType.UNKNOWN; } @@ -362,6 +367,19 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener mKeylog.write(0xa); } + private void handleLog(byte[] message) { + try { + String msg = new String(message, StandardCharsets.US_ASCII); + + // format: 1:message + if (msg.length() < 3) + return; + + int lvl = Integer.parseInt(msg.substring(0, 1)); + Log.level(Log.MITMADDON_LOGGER, lvl, msg.substring(2)); + } catch (NumberFormatException ignored) {} + } + public boolean isProxyRunning() { return Boolean.TRUE.equals(proxyRunning.getValue()); } diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/BaseActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/BaseActivity.java index b20a61e7..49c8bc18 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/BaseActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/BaseActivity.java @@ -41,6 +41,10 @@ class BaseActivity extends AppCompatActivity { return null; } + protected Fragment getFragmentAtPos(int pos) { + return getSupportFragmentManager().findFragmentByTag("f" + pos); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if(mBackAction && (item.getItemId() == android.R.id.home)) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/ConnectionDetailsActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/ConnectionDetailsActivity.java index f3c02ffb..34705e9d 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/ConnectionDetailsActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/ConnectionDetailsActivity.java @@ -73,7 +73,7 @@ public class ConnectionDetailsActivity extends BaseActivity implements Connectio super.onCreate(savedInstanceState); setTitle(R.string.connection_details); displayBackAction(); - setContentView(R.layout.activity_connection_details); + setContentView(R.layout.tabs_activity_fixed); int incr_id = getIntent().getIntExtra(CONN_ID_KEY, -1); if(incr_id != -1) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/LogviewActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/LogviewActivity.java index 1b953acf..6488d991 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/LogviewActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/LogviewActivity.java @@ -23,70 +23,112 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.core.view.MenuProvider; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; +import com.emanuelef.remote_capture.Log; import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.Utils; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; +import com.emanuelef.remote_capture.fragments.LogviewFragment; +import com.google.android.material.tabs.TabLayoutMediator; public class LogviewActivity extends BaseActivity implements MenuProvider { private static final String TAG = "LogviewActivity"; - private String mLogText; + private ViewPager2 mPager; + private StateAdapter mPagerAdapter; + + private static final int POS_APP_LOG = 0; + private static final int POS_ROOT_LOG = 1; + private static final int POS_MITM_LOG = 2; + private static final int NUM_POS = 3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setTitle(R.string.root_log); - setContentView(R.layout.logview_activity); + setTitle(R.string.app_log); + setContentView(R.layout.tabs_activity_fixed); addMenuProvider(this); - TextView logView = findViewById(R.id.log); - mLogText = readLog(); - - logView.setText(!mLogText.isEmpty() ? mLogText : getString(R.string.error)); + mPager = findViewById(R.id.pager); + setupTabs(); } - private String readLog() { - try { - String logpath = getCacheDir().getPath() + "/pcapd.log"; - BufferedReader reader = new BufferedReader(new FileReader(logpath)); + private void setupTabs() { + mPagerAdapter = new StateAdapter(this); + mPager.setAdapter(mPagerAdapter); - StringBuilder builder = new StringBuilder(); - String line; + new TabLayoutMediator(findViewById(R.id.tablayout), mPager, (tab, position) -> + tab.setText(getString(mPagerAdapter.getPageTitle(position))) + ).attach(); + } - while((line = reader.readLine()) != null) { - builder.append(line); - builder.append("\n"); - } + private static class StateAdapter extends FragmentStateAdapter { + final String mCacheDir; - return builder.toString(); - } catch (IOException e) { - e.printStackTrace(); + StateAdapter(final FragmentActivity fa) { + super(fa); + mCacheDir = fa.getCacheDir().getAbsolutePath(); } - return ""; + @NonNull + @Override + public Fragment createFragment(int pos) { + switch (pos) { + case POS_APP_LOG: + return LogviewFragment.newInstance(mCacheDir + "/" + Log.DEFAULT_LOGGER_PATH); + case POS_ROOT_LOG: + return LogviewFragment.newInstance(mCacheDir + "/" + Log.ROOT_LOGGER_PATH); + case POS_MITM_LOG: + default: + return LogviewFragment.newInstance(mCacheDir + "/" + Log.MITM_LOGGER_PATH); + } + } + + @Override + public int getItemCount() { + return NUM_POS; + } + + public int getPageTitle(final int pos) { + switch (pos) { + case POS_APP_LOG: + return R.string.app; + case POS_ROOT_LOG: + return R.string.root; + case POS_MITM_LOG: + default: + return R.string.mitm_addon; + } + } } @Override public void onCreateMenu(@NonNull Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.copy_share_menu, menu); + inflater.inflate(R.menu.log_menu, menu); } @Override public boolean onMenuItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); + LogviewFragment fragment = (LogviewFragment) getFragmentAtPos(mPager.getCurrentItem()); + if(fragment == null) + return false; - if(id == R.id.copy_to_clipboard) { - Utils.copyToClipboard(this, mLogText); + String logText = fragment.getLog(); + + if(id == R.id.reload) { + fragment.reloadLog(); + return true; + } else if(id == R.id.copy_to_clipboard) { + Utils.copyToClipboard(this, logText); return true; } else if(id == R.id.share) { - Utils.shareText(this, getString(R.string.root_log), mLogText); + Utils.shareText(this, getString(R.string.app_log), logText); return true; } diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java index 646e2c3f..d9352d6b 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java @@ -211,8 +211,6 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig protected void onResume() { super.onResume(); - Menu navMenu = mNavView.getMenu(); - navMenu.findItem(R.id.open_root_log).setVisible(Prefs.isRootCaptureEnabled(mPrefs)); checkPaidDrawerEntries(); } @@ -483,7 +481,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig } else if(id == R.id.firewall) { Intent intent = new Intent(MainActivity.this, FirewallActivity.class); startActivity(intent); - } else if(id == R.id.open_root_log) { + } else if(id == R.id.open_log) { Intent intent = new Intent(MainActivity.this, LogviewActivity.class); startActivity(intent); } else if (id == R.id.action_donate) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/LogviewFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/LogviewFragment.java new file mode 100644 index 00000000..7258e3be --- /dev/null +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/LogviewFragment.java @@ -0,0 +1,93 @@ +/* + * This file is part of PCAPdroid. + * + * PCAPdroid 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 3 of the License, or + * (at your option) any later version. + * + * PCAPdroid 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 PCAPdroid. If not, see . + * + * Copyright 2020-22 - Emanuele Faranda + */ + +package com.emanuelef.remote_capture.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.emanuelef.remote_capture.R; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +public class LogviewFragment extends Fragment { + private static final String TAG = "LogviewFragment"; + private String mLogPath; + private String mLogText; + private TextView mLogView; + + public static LogviewFragment newInstance(String path) { + LogviewFragment fragment = new LogviewFragment(); + Bundle args = new Bundle(); + args.putSerializable("path", path); + fragment.setArguments(args); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.logview_fragment, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + Bundle args = getArguments(); + assert args != null; + mLogPath = args.getString("path"); + assert(mLogPath != null); + + mLogView = view.findViewById(R.id.log); + reloadLog(); + } + + public void reloadLog() { + try { + BufferedReader reader = new BufferedReader(new FileReader(mLogPath)); + + StringBuilder builder = new StringBuilder(); + String line; + + while((line = reader.readLine()) != null) { + builder.append(line); + builder.append("\n"); + } + + mLogText = builder.toString(); + } catch (IOException e) { + e.printStackTrace(); + mLogText = ""; + } + + mLogView.setText(!mLogText.isEmpty() ? mLogText : getString(R.string.no_data)); + } + + public String getLog() { + return mLogText; + } +} diff --git a/app/src/main/jni/core/log_writer.c b/app/src/main/jni/core/log_writer.c index cba27e85..9a761c63 100644 --- a/app/src/main/jni/core/log_writer.c +++ b/app/src/main/jni/core/log_writer.c @@ -11,12 +11,16 @@ static int num_loggers = 0; struct log_writer { FILE *f; + char *path; int id; int level; + bool errored; }; static void pd_destroy_logger(struct log_writer *logger) { - fclose(logger->f); + if(logger->f) + fclose(logger->f); + pd_free(logger->path); pd_free(logger); } @@ -33,26 +37,17 @@ void pd_close_loggers() { } int pd_init_logger(const char *path, int min_lvl) { - FILE *f = fopen(path, "w"); int rv; - - if(!f) { -#ifdef ANDROID - __android_log_print(ANDROID_LOG_ERROR, logtag, - "pd_init_logger %s failed[%d]: %s", path, errno, strerror(errno)); -#endif - return -errno; - } - struct log_writer *logger = (struct log_writer*) pd_calloc(1, sizeof(struct log_writer)); - if(!logger) { - rv = -errno; - fclose(f); - return rv; - } + if(!logger) + return -errno; logger->level = min_lvl; - logger->f = f; + logger->path = pd_strdup(path); + if(!logger->path) { + pd_free(logger); + return -errno; + } pthread_mutex_lock(&mutex); loggers = pd_realloc(loggers, sizeof(void*) * (num_loggers + 1)); @@ -60,8 +55,8 @@ int pd_init_logger(const char *path, int min_lvl) { pd_destroy_logger(logger); rv = -1; } else { - loggers[num_loggers++] = logger; - logger->id = rv = num_loggers; + loggers[num_loggers] = logger; + logger->id = rv = num_loggers++; } pthread_mutex_unlock(&mutex); @@ -73,7 +68,6 @@ int pd_log_write(int logger_id, int lvl, const char *msg) { char dtbuf[64]; struct tm tm; time_t tnow = time(NULL); - strftime(dtbuf, sizeof(dtbuf), "%d/%b/%Y %H:%M:%S", localtime_r(&tnow, &tm)); pthread_mutex_lock(&mutex); @@ -88,7 +82,22 @@ int pd_log_write(int logger_id, int lvl, const char *msg) { if(logger->level > lvl) goto unlock; - if(ferror(logger->f)) { + if(!logger->f && !logger->errored) { + // only overwrite the file when writing to it + logger->f = fopen(logger->path, "w"); + + if(!logger->f) { +#ifdef ANDROID + __android_log_print(ANDROID_LOG_ERROR, logtag, + "pd_init_logger %s failed[%d]: %s", logger->path, errno, strerror(errno)); +#endif + rv = -errno; + logger->errored = true; + goto unlock; + } + } + + if(!logger->f || ferror(logger->f)) { rv = -EINVAL; goto unlock; } diff --git a/app/src/main/res/layout/logview_activity.xml b/app/src/main/res/layout/logview_fragment.xml similarity index 81% rename from app/src/main/res/layout/logview_activity.xml rename to app/src/main/res/layout/logview_fragment.xml index baab217a..7cfc799a 100644 --- a/app/src/main/res/layout/logview_activity.xml +++ b/app/src/main/res/layout/logview_fragment.xml @@ -1,7 +1,6 @@ @@ -10,5 +9,8 @@ android:id="@+id/log" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_margin="5dp" + android:textSize="13sp" + tools:text="[i] log..." android:textIsSelectable="true" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_connection_details.xml b/app/src/main/res/layout/tabs_activity_fixed.xml similarity index 100% rename from app/src/main/res/layout/activity_connection_details.xml rename to app/src/main/res/layout/tabs_activity_fixed.xml diff --git a/app/src/main/res/menu/log_menu.xml b/app/src/main/res/menu/log_menu.xml new file mode 100644 index 00000000..9f325adf --- /dev/null +++ b/app/src/main/res/menu/log_menu.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/nav_items.xml b/app/src/main/res/menu/nav_items.xml index 6df017bf..05f0c6a4 100644 --- a/app/src/main/res/menu/nav_items.xml +++ b/app/src/main/res/menu/nav_items.xml @@ -18,10 +18,9 @@ android:title="@string/malware_detection" android:icon="@drawable/ic_bug" /> + android:id="@+id/open_log" + android:title="@string/app_log" + android:icon="@drawable/ic_text_snippet" /> WHOIS Installation ID License code + root PCAPdroid 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 or later for @@ -126,7 +127,8 @@ Capture as root Allows PCAPdroid to run with other VPN apps Donate - Root log + Log + No data Search… App: %1$s IP address: %1$s @@ -278,6 +280,7 @@ The CA certificate is not installed, run the mitm setup wizard Bad PCAPdroid mitm addon version. Uninstall it and retry The PCAPdroid mitm addon must be upgraded + mitm addon Upgrade Export failed Not encrypted