Skip to content

Commit

Permalink
Merge branch 'master' into playstore
Browse files Browse the repository at this point in the history
  • Loading branch information
emanuele-f committed Dec 4, 2023
2 parents 8730901 + a1b6ec3 commit 5d3c8cc
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 40 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

Releases available at https://github.com/emanuele-f/PCAPdroid/releases

## [1.6.9] - 2023-12-04
- Fix root capture start on some devices
- Fix root permission incorrectly requested on read from pcap file
- Fix capture control modal incorrectly shown
- Fix possible crash on null CaptureSettings
- Fix possible SecurityException while opening PCAP file

## [1.6.8] - 2023-09-01
- Fix root capture/pcap loading in playstore build due to AAB packaging
- Fix possible permission denied error on PCAP file open

## [1.6.7] - 2023-08-31
- Fix root capture start failure due to short timeout
- Fix NumberFormatException in PCAP open / CSV export
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ android {
applicationId "com.emanuelef.remote_capture"
minSdkVersion 21
targetSdkVersion 33
versionCode 69
versionName "1.6.7"
versionCode 72
versionName "1.6.9"

buildConfigField "long", "BUILD_TIME", System.currentTimeMillis() + "L"
}
Expand Down
33 changes: 24 additions & 9 deletions app/src/main/java/com/emanuelef/remote_capture/CaptureHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ public class CaptureHelper {
private static final String TAG = "CaptureHelper";
private final ComponentActivity mActivity;
private final ActivityResultLauncher<Intent> mLauncher;
private final boolean mResolveHosts;
private CaptureSettings mSettings;
private CaptureStartListener mListener;

public CaptureHelper(ComponentActivity activity) {
public CaptureHelper(ComponentActivity activity, boolean resolve_hosts) {
mActivity = activity;
mResolveHosts = resolve_hosts;
mLauncher = activity.registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), this::captureServiceResult);
}
Expand All @@ -70,7 +72,7 @@ private void startCaptureOk() {
mListener.onCaptureStartResult(true);
}

private String resolveHost(String host) {
private static String resolveHost(String host) {
Log.d(TAG, "Resolving host: " + host);

try {
Expand All @@ -80,29 +82,42 @@ private String resolveHost(String host) {
return null;
}

private String doResolveHosts() {
private static String doResolveHosts(CaptureSettings settings) {
// NOTE: hosts must be resolved before starting the VPN and in a separate thread
String resolved;

if(mSettings.socks5_enabled) {
if ((resolved = resolveHost(mSettings.socks5_proxy_address)) == null)
return mSettings.socks5_proxy_address;
else if (!resolved.equals(mSettings.socks5_proxy_address)) {
if(settings == null)
return null;

if(settings.socks5_enabled) {
if ((resolved = resolveHost(settings.socks5_proxy_address)) == null)
return settings.socks5_proxy_address;
else if (!resolved.equals(settings.socks5_proxy_address)) {
Log.i(TAG, "Resolved SOCKS5 proxy address: " + resolved);
mSettings.socks5_proxy_address = resolved;
settings.socks5_proxy_address = resolved;
}
}

return null;
}

private void resolveHosts() {
if (!mResolveHosts) {
startCaptureOk();
return;
}

final Handler handler = new Handler(Looper.getMainLooper());

(new Thread(() -> {
String failed_host = doResolveHosts();
String failed_host = doResolveHosts(mSettings);

handler.post(() -> {
if(mSettings == null) {
mListener.onCaptureStartResult(false);
return;
}

if(failed_host == null)
startCaptureOk();
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,7 @@ public static String getInterfaceName(int ifidx) {
// Inside the mCaptureThread
@Override
public void run() {
if(mSettings.root_capture || mSettings.readFromPcap()) {
if(mSettings.root_capture) {
// Check for INTERACT_ACROSS_USERS, required to query apps of other users/work profiles
if(checkCallingOrSelfPermission(Utils.INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
boolean success = Utils.rootGrantPermission(this, Utils.INTERACT_ACROSS_USERS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
getWindow().addFlags(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
super.onCreate(savedInstanceState);

mCapHelper = new CaptureHelper(this);
mCapHelper = new CaptureHelper(this, false);
mCapHelper.setListener(success -> {
setResult(success ? RESULT_OK : RESULT_CANCELED, null);
finish();
Expand Down Expand Up @@ -200,12 +200,17 @@ public void onBackPressed() {
abort();
}

private void abort() {
Utils.showToast(this, R.string.ctrl_consent_denied);
private void abort(boolean show_toast) {
if(show_toast)
Utils.showToast(this, R.string.ctrl_consent_denied);
setResult(RESULT_CANCELED, null);
finish();
}

private void abort() {
abort(true);
}

// Check if the capture is requesting to send traffic to a remote server.
// For security reasons, this is only allowed if such server is already configured by
// the user in the app prefs.
Expand Down Expand Up @@ -325,7 +330,7 @@ private void getPeerInfo() {
String package_name = getCallingPackage();
if((package_name == null) || !package_name.equals(BuildConfig.APPLICATION_ID + ".debug")) {
Log.w(TAG, "getPeerInfo: package name mismatch");
abort();
abort(false);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,11 @@ protected void onCreate(Bundle savedInstanceState) {
int appver = Prefs.getAppVersion(mPrefs);
if(appver <= 0) {
// First run, start on-boarding
// only refresh app version on on-boarding done
Intent intent = new Intent(MainActivity.this, OnBoardingActivity.class);
startActivity(intent);
finish();
// only refresh app version on on-boarding done
return;
} else
Prefs.refreshAppVersion(mPrefs);

Expand All @@ -167,7 +168,7 @@ public void onPurchasesReady() {
initAppState();
checkPermissions();

mCapHelper = new CaptureHelper(this);
mCapHelper = new CaptureHelper(this, true);
mCapHelper.setListener(success -> {
if(!success) {
Log.w(TAG, "Capture start failed");
Expand Down Expand Up @@ -938,7 +939,7 @@ private void pcapFileOpenResult(final ActivityResult result) {
executor.execute(() -> {
try (InputStream in_stream = getContentResolver().openInputStream(uri)) {
Utils.copy(in_stream, out);
} catch (IOException e) {
} catch (IOException | SecurityException e) {
e.printStackTrace();

runOnUiThread(() -> {
Expand Down
34 changes: 22 additions & 12 deletions app/src/main/jni/common/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,15 @@ void hexdump(const char *buf, size_t bufsize) {
/* ******************************************************* */

// Start a sub-process, running a command with some arguments, either as root or as the current user.
// On success, the out_fd parameter will receive an open file descriptor to read the command output
// If out_fd is not NULL, on success the out_fd parameter will receive an open file descriptor
// to read the command output.
// Returns the pid of the child process, or -1 on failure
// NOTE: the caller MUST call waitpid or equivalent to prevent process zombification and close the out_fd
int start_subprocess(const char *prog, const char *args, bool as_root, int* out_fd) {
int in_p[2], out_p[2];
pid_t pid;

if((pipe(in_p) != 0) || (pipe(out_p) != 0)) {
if((pipe(in_p) != 0) || (out_fd && (pipe(out_p) != 0))) {
log_f("pipe failed[%d]: %s", errno, strerror(errno));
return -1;
}
Expand All @@ -217,23 +218,29 @@ int start_subprocess(const char *prog, const char *args, bool as_root, int* out_
char *argp[] = {"sh", "-c", as_root ? "su" : "sh", NULL};

close(in_p[1]);
close(out_p[0]);

dup2(in_p[0], STDIN_FILENO);
dup2(out_p[1], STDOUT_FILENO);
dup2(out_p[1], STDERR_FILENO);

if(out_fd) {
close(out_p[0]);

dup2(out_p[1], STDOUT_FILENO);
dup2(out_p[1], STDERR_FILENO);
}

execve(_PATH_BSHELL, argp, environ);
fprintf(stderr, "execve failed[%d]: %s", errno, strerror(errno));
exit(1);
} else if(pid > 0) {
// parent
*out_fd = out_p[0];
if(out_fd) {
*out_fd = out_p[0];
close(out_p[1]);
}

close(in_p[0]);
close(out_p[1]);

// write "su" command input
log_d("run_shell_cmd[%d]: %s %s", pid, prog, args);
log_d("start_subprocess[%d]: %s %s", pid, prog, args);
write(in_p[1], prog, strlen(prog));
write(in_p[1], " ", 1);
write(in_p[1], args, strlen(args));
Expand All @@ -243,8 +250,11 @@ int start_subprocess(const char *prog, const char *args, bool as_root, int* out_
log_f("fork() failed[%d]: %s", errno, strerror(errno));
close(in_p[0]);
close(in_p[1]);
close(out_p[0]);
close(out_p[1]);

if(out_fd) {
close(out_p[0]);
close(out_p[1]);
}
return -1;
}

Expand Down Expand Up @@ -286,4 +296,4 @@ int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_e

close(out_fd);
return rv;
}
}
14 changes: 6 additions & 8 deletions app/src/main/jni/core/capture_libpcap.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,19 +190,18 @@ static int connectPcapd(pcapdroid_t *pd) {
getuid(), pd->pcap.capture_interface, pd->tls_decryption.enabled ? -1 : pd->app_filter,
bpf, pd->pcap.daemonize ? " -d" : "");

int pcapd_out;
pid = start_subprocess(pcapd, args, pd->pcap.as_root, &pcapd_out);
if(pid <= 0)
pid = start_subprocess(pcapd, args, pd->pcap.as_root, NULL);
if(pid <= 0) {
log_e("start_subprocess failed");
goto cleanup;
close(pcapd_out);
}

if(pd->pcap.daemonize) {
// when running as a daemon, child exits early
// note: this will block until user grants/denies root permission
int rv;
if((waitpid(pid, &rv, 0) == pid) && (rv != 0)) {
if(WIFEXITED(rv))
log_w("pcapd exited with code %d", WEXITSTATUS(rv));
log_w("pcapd exited with code %d", rv);

log_f(PD_ERR_PCAPD_START);
goto cleanup;
Expand Down Expand Up @@ -234,8 +233,7 @@ static int connectPcapd(pcapdroid_t *pd) {
// check if the child process terminated incorrectly
int rv;
if(!pd->pcap.daemonize && (waitpid(pid, &rv, WNOHANG) == pid)) {
if(WIFEXITED(rv))
log_w("pcapd exited with code %d", WEXITSTATUS(rv));
log_w("pcapd exited with code %d", rv);
pid = -1;

log_f(PD_ERR_PCAPD_START);
Expand Down
2 changes: 1 addition & 1 deletion docs/app_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ As shown above, the capture settings can be specified by using intent extras. Th
| http_server_port | int | | | the HTTP server port in http_server mode |
| pcap_uri | string | | | the URI for the PCAP dump in pcap_file mode (overrides pcap_name) |
| socks5_enabled | bool | | vpn | true to redirect the TCP connections to a SOCKS5 proxy |
| socks5_proxy_ip_address | string | | vpn | the SOCKS5 proxy host |
| socks5_proxy_ip_address | string | | vpn | the SOCKS5 proxy IP address |
| socks5_proxy_port | int | | vpn | the SOCKS5 proxy port |
| root_capture | bool | | | true to capture packets in root mode, false to use the VPNService |
| pcapdroid_trailer | bool | | | true to enable the PCAPdroid trailer |
Expand Down

0 comments on commit 5d3c8cc

Please sign in to comment.