Show logs inside the app

The app now shows its own logs along with the root and the mitm addon
ones.

Closes #282
This commit is contained in:
emanuele-f 2022-11-24 13:04:21 +01:00
parent cbdc4967ca
commit 77560bdebd
14 changed files with 291 additions and 69 deletions

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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());
}

View File

@ -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)) {

View File

@ -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) {

View File

@ -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;
}

View File

@ -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) {

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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;
}
}

View File

@ -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;
}

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -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" />
</ScrollView>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/reload"
android:title="@string/update_now"
android:orderInCategory="5"
android:icon="@drawable/ic_refresh"
app:showAsAction="ifRoom" />
<item
android:id="@+id/copy_to_clipboard"
android:title="@string/copy_to_clipboard"
android:orderInCategory="10"
android:icon="@drawable/ic_content_copy"
app:showAsAction="ifRoom" />
<item
android:id="@+id/share"
android:title="@string/share"
android:orderInCategory="20"
android:icon="@drawable/ic_share"
app:showAsAction="ifRoom" />
</menu>

View File

@ -18,10 +18,9 @@
android:title="@string/malware_detection"
android:icon="@drawable/ic_bug" />
<item
android:id="@+id/open_root_log"
android:title="@string/root_log"
android:icon="@drawable/ic_text_snippet"
android:visible="false" />
android:id="@+id/open_log"
android:title="@string/app_log"
android:icon="@drawable/ic_text_snippet" />
<item
android:id="@+id/paid_features"
android:visible="false"

View File

@ -9,6 +9,7 @@
<string name="whois_lookup" translatable="false">WHOIS</string>
<string name="installation_id" translatable="false">Installation ID</string>
<string name="license_code" translatable="false">License code</string>
<string name="root" translatable="false">root</string>
<string name="app_license" translatable="false">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 <a href='https://www.gnu.org/licenses/gpl-3.0-standalone.html'>GNU General Public License or later</a> for
@ -126,7 +127,8 @@
<string name="root_capture">Capture as root</string>
<string name="root_capture_summary">Allows PCAPdroid to run with other VPN apps</string>
<string name="donate">Donate</string>
<string name="root_log">Root log</string>
<string name="app_log">Log</string>
<string name="no_data">No data</string>
<string name="search">Search…</string>
<string name="app_val">App: %1$s</string>
<string name="ip_address_val">IP address: %1$s</string>
@ -278,6 +280,7 @@
<string name="cert_reinstall_required">The CA certificate is not installed, run the mitm setup wizard</string>
<string name="mitm_addon_bad_version">Bad PCAPdroid mitm addon version. Uninstall it and retry</string>
<string name="mitm_addon_new_version">The PCAPdroid mitm addon must be upgraded</string>
<string name="mitm_addon">mitm addon</string>
<string name="upgrade_action">Upgrade</string>
<string name="export_failed">Export failed</string>
<string name="not_encrypted">Not encrypted</string>