public void addRoute(String dest, String mask, String gateway, String device) {
    CIDRIP route = new CIDRIP(dest, mask);
    boolean include = isAndroidTunDevice(device);

    NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32), false);

    if (mLocalIP == null) {
      VpnStatus.logError(
          "Local IP address unset but adding route?! This is broken! Please contact author with log");
      return;
    }
    NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP, true);
    if (localNet.containsNet(gatewayIP)) include = true;

    if (gateway != null && (gateway.equals("255.255.255.255") || gateway.equals(mRemoteGW)))
      include = true;

    if (route.len == 32 && !mask.equals("255.255.255.255")) {
      VpnStatus.logWarning(R.string.route_not_cidr, dest, mask);
    }

    if (route.normalise())
      VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp);

    mRoutes.addIP(route, include);
  }
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  private void setAllowedVpnPackages(Builder builder) {
    boolean atLeastOneAllowedApp = false;
    for (String pkg : mProfile.mAllowedAppsVpn) {
      try {
        if (mProfile.mAllowedAppsVpnAreDisallowed) {
          builder.addDisallowedApplication(pkg);
        } else {
          builder.addAllowedApplication(pkg);
          atLeastOneAllowedApp = true;
        }
      } catch (PackageManager.NameNotFoundException e) {
        mProfile.mAllowedAppsVpn.remove(pkg);
        VpnStatus.logInfo(R.string.app_no_longer_exists, pkg);
      }
    }

    if (!mProfile.mAllowedAppsVpnAreDisallowed && !atLeastOneAllowedApp) {
      VpnStatus.logDebug(R.string.no_allowed_app, getPackageName());
      try {
        builder.addAllowedApplication(getPackageName());
      } catch (PackageManager.NameNotFoundException e) {
        VpnStatus.logError("This should not happen: " + e.getLocalizedMessage());
      }
    }

    if (mProfile.mAllowedAppsVpnAreDisallowed) {
      VpnStatus.logDebug(
          R.string.disallowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn));
    } else {
      VpnStatus.logDebug(
          R.string.allowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn));
    }
  }
  @Override
  public void run() {
    try {
      Log.i(TAG, "Starting openvpn");
      startOpenVPNThreadArgs(mArgv, mProcessEnv);
      Log.i(TAG, "Giving up");
    } catch (Exception e) {
      VpnStatus.logException("Starting OpenVPN Thread", e);
      Log.e(TAG, "OpenVPNThread Got " + e.toString());
    } finally {
      int exitvalue = 0;
      try {
        if (mProcess != null) exitvalue = mProcess.waitFor();
      } catch (IllegalThreadStateException ite) {
        VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage());
      } catch (InterruptedException ie) {
        VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage());
      }
      if (exitvalue != 0) {
        VpnStatus.logError("Process exited with exit value " + exitvalue);
        if (mBrokenPie) {
          /* This will probably fail since the NoPIE binary is probably not written */
          String[] noPieArgv = VPNLaunchHelper.replacePieWithNoPie(mArgv);

          // We are already noPIE, nothing to gain
          if (!noPieArgv.equals(mArgv)) {
            mArgv = noPieArgv;
            VpnStatus.logInfo("PIE Version could not be executed. Trying no PIE version");
            run();
            return;
          }
        }
      }

      VpnStatus.updateStateString(
          "NOPROCESS",
          "No process running.",
          R.string.state_noprocess,
          ConnectionStatus.LEVEL_NOTCONNECTED);
      if (mDumpPath != null) {
        try {
          BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log"));
          SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.GERMAN);
          for (LogItem li : VpnStatus.getlogbuffer()) {
            String time = timeformat.format(new Date(li.getLogtime()));
            logout.write(time + " " + li.getString(mService) + "\n");
          }
          logout.close();
          VpnStatus.logError(R.string.minidump_generated);
        } catch (IOException e) {
          VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage());
        }
      }

      mService.processDied();
      Log.i(TAG, "Exiting");
    }
  }
  private void endVpnService() {
    synchronized (mProcessLock) {
      mProcessThread = null;
    }
    VpnStatus.removeByteCountListener(this);
    unregisterDeviceStateReceiver();
    ProfileManager.setConntectedVpnProfileDisconnected(this);
    if (!mStarting) {
      stopForeground(!mNotificationAlwaysVisible);

      if (!mNotificationAlwaysVisible) {
        stopSelf();
        VpnStatus.removeStateListener(this);
      }
    }
  }
  @Override
  public void updateState(String state, String logmessage, int resid, ConnectionStatus level) {
    // If the process is not running, ignore any state,
    // Notification should be invisible in this state

    doSendBroadcast(state, level);
    if (mProcessThread == null && !mNotificationAlwaysVisible) return;

    boolean lowpriority = false;
    // Display byte count only after being connected

    {
      if (level == LEVEL_WAITING_FOR_USER_INPUT) {
        // The user is presented a dialog of some kind, no need to inform the user
        // with a notifcation
        return;
      } else if (level == LEVEL_CONNECTED) {
        mDisplayBytecount = true;
        mConnecttime = System.currentTimeMillis();
        lowpriority = true;
      } else {
        mDisplayBytecount = false;
      }

      // Other notifications are shown,
      // This also mean we are no longer connected, ignore bytecount messages until next
      // CONNECTED
      // Does not work :(
      String msg = getString(resid);
      showNotification(
          msg + " " + VpnStatus.getCleanLogMessage(level, logmessage), msg, lowpriority, 0, level);
    }
  }
  private void addLocalNetworksToRoutes() {

    // Add local network interfaces
    String[] localRoutes = NativeUtils.getIfconfig();

    // The format of mLocalRoutes is kind of broken because I don't really like JNI
    for (int i = 0; i < localRoutes.length; i += 3) {
      String intf = localRoutes[i];
      String ipAddr = localRoutes[i + 1];
      String netMask = localRoutes[i + 2];

      if (intf == null || intf.equals("lo") || intf.startsWith("tun") || intf.startsWith("rmnet"))
        continue;

      if (ipAddr == null || netMask == null) {
        VpnStatus.logError(
            "Local routes are broken?! (Report to author) " + TextUtils.join("|", localRoutes));
        continue;
      }

      if (ipAddr.equals(mLocalIP.mIp)) continue;

      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !mProfile.mAllowLocalLAN) {
        mRoutes.addIPSplit(new CIDRIP(ipAddr, netMask), true);

      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mProfile.mAllowLocalLAN)
        mRoutes.addIP(new CIDRIP(ipAddr, netMask), false);
    }
  }
  public void setLocalIP(String local, String netmask, int mtu, String mode) {
    mLocalIP = new CIDRIP(local, netmask);
    mMtu = mtu;
    mRemoteGW = null;

    long netMaskAsInt = CIDRIP.getInt(netmask);

    if (mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) {
      // get the netmask as IP

      int masklen;
      long mask;
      if ("net30".equals(mode)) {
        masklen = 30;
        mask = 0xfffffffc;
      } else {
        masklen = 31;
        mask = 0xfffffffe;
      }

      // Netmask is Ip address +/-1, assume net30/p2p with small net
      if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask)) {
        mLocalIP.len = masklen;
      } else {
        mLocalIP.len = 32;
        if (!"p2p".equals(mode)) VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode);
      }
    }
    if (("p2p".equals(mode) && mLocalIP.len < 32) || ("net30".equals(mode) && mLocalIP.len < 30)) {
      VpnStatus.logWarning(R.string.ip_looks_like_subnet, local, netmask, mode);
    }

    /* Workaround for Lollipop, it  does not route traffic to the VPNs own network mask */
    if (mLocalIP.len <= 31 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      CIDRIP interfaceRoute = new CIDRIP(mLocalIP.mIp, mLocalIP.len);
      interfaceRoute.normalise();
      addRoute(interfaceRoute);
    }

    // Configurations are sometimes really broken...
    mRemoteGW = netmask;
  }
Exemple #8
0
  public static String getCertificateFriendlyName(X509Certificate cert) {
    X500Principal principal = cert.getSubjectX500Principal();
    byte[] encodedSubject = principal.getEncoded();
    String friendlyName = null;

    /* Hack so we do not have to ship a whole Spongy/bouncycastle */
    Exception exp = null;
    try {
      Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name");
      Method getInstance = X509NameClass.getMethod("getInstance", Object.class);

      Hashtable defaultSymbols =
          (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass);

      if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1"))
        defaultSymbols.put("1.2.840.113549.1.9.1", "eMail");

      Object subjectName = getInstance.invoke(X509NameClass, encodedSubject);

      Method toString = X509NameClass.getMethod("toString", boolean.class, Hashtable.class);

      friendlyName = (String) toString.invoke(subjectName, true, defaultSymbols);

    } catch (ClassNotFoundException e) {
      exp = e;
    } catch (NoSuchMethodException e) {
      exp = e;
    } catch (InvocationTargetException e) {
      exp = e;
    } catch (IllegalAccessException e) {
      exp = e;
    } catch (NoSuchFieldException e) {
      exp = e;
    }
    if (exp != null) VpnStatus.logException("Getting X509 Name from certificate", exp);

    /* Fallback if the reflection method did not work */
    if (friendlyName == null) friendlyName = principal.getName();

    // Really evil hack to decode email address
    // See: http://code.google.com/p/android/issues/detail?id=21531

    String[] parts = friendlyName.split(",");
    for (int i = 0; i < parts.length; i++) {
      String part = parts[i];
      if (part.startsWith("1.2.840.113549.1.9.1=#16")) {
        parts[i] = "email=" + ia5decode(part.replace("1.2.840.113549.1.9.1=#16", ""));
      }
    }
    friendlyName = TextUtils.join(",", parts);
    return friendlyName;
  }
  synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) {
    // Registers BroadcastReceiver to track network connection changes.
    IntentFilter filter = new IntentFilter();
    filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
    filter.addAction(Intent.ACTION_SCREEN_OFF);
    filter.addAction(Intent.ACTION_SCREEN_ON);
    mDeviceStateReceiver = new DeviceStateReceiver(magnagement);
    registerReceiver(mDeviceStateReceiver, filter);
    VpnStatus.addByteCountListener(mDeviceStateReceiver);

    /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
    addLollipopCMListener(); */
  }
Exemple #10
0
  public static String getCertificateFriendlyName(Context c, String filename) {
    if (!TextUtils.isEmpty(filename)) {
      try {
        X509Certificate cert = (X509Certificate) getCertificateFromFile(filename);
        String friendlycn = getCertificateFriendlyName(cert);
        friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn;
        return friendlycn;

      } catch (Exception e) {
        VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage());
      }
    }
    return c.getString(R.string.cannotparsecert);
  }
  @Override
  public void onDestroy() {
    synchronized (mProcessLock) {
      if (mProcessThread != null) {
        mManagement.stopVPN();
      }
    }

    if (mDeviceStateReceiver != null) {
      this.unregisterReceiver(mDeviceStateReceiver);
    }
    // Just in case unregister for state
    VpnStatus.removeStateListener(this);
  }
  public void addRoutev6(String network, String device) {
    String[] v6parts = network.split("/");
    boolean included = isAndroidTunDevice(device);

    // Tun is opened after ROUTE6, no device name may be present

    try {
      Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0];
      int mask = Integer.parseInt(v6parts[1]);
      mRoutesv6.addIPv6(ip, mask, included);

    } catch (UnknownHostException e) {
      VpnStatus.logException(e);
    }
  }
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void jbNotificationExtras(
      boolean lowpriority, android.app.Notification.Builder nbuilder) {
    try {
      if (lowpriority) {
        Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class);
        // PRIORITY_MIN == -2
        setpriority.invoke(nbuilder, -2);

        Method setUsesChronometer =
            nbuilder.getClass().getMethod("setUsesChronometer", boolean.class);
        setUsesChronometer.invoke(nbuilder, true);
      }

      Intent disconnectVPN = new Intent(this, DisconnectVPN.class);
      disconnectVPN.setAction(DISCONNECT_VPN);
      PendingIntent disconnectPendingIntent = PendingIntent.getActivity(this, 0, disconnectVPN, 0);

      nbuilder.addAction(
          android.R.drawable.ic_menu_close_clear_cancel,
          getString(R.string.cancel_connection),
          disconnectPendingIntent);

      Intent pauseVPN = new Intent(this, OpenVPNService.class);
      if (mDeviceStateReceiver == null || !mDeviceStateReceiver.isUserPaused()) {
        pauseVPN.setAction(PAUSE_VPN);
        PendingIntent pauseVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0);
        nbuilder.addAction(
            android.R.drawable.ic_media_pause, getString(R.string.pauseVPN), pauseVPNPending);

      } else {
        pauseVPN.setAction(RESUME_VPN);
        PendingIntent resumeVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0);
        nbuilder.addAction(
            android.R.drawable.ic_media_play, getString(R.string.resumevpn), resumeVPNPending);
      }

      // ignore exception
    } catch (NoSuchMethodException
        | IllegalArgumentException
        | InvocationTargetException
        | IllegalAccessException e) {
      VpnStatus.logException(e);
    }
  }
  synchronized void unregisterDeviceStateReceiver() {
    if (mDeviceStateReceiver != null)
      try {
        VpnStatus.removeByteCountListener(mDeviceStateReceiver);
        this.unregisterReceiver(mDeviceStateReceiver);
      } catch (IllegalArgumentException iae) {
        // I don't know why  this happens:
        // java.lang.IllegalArgumentException: Receiver not registered:
        // de.blinkt.openvpn.NetworkSateReceiver@41a61a10
        // Ignore for now ...
        iae.printStackTrace();
      }
    mDeviceStateReceiver = null;

    /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
    removeLollipopCMListener();*/

  }
  public ParcelFileDescriptor openTun() {

    // Debug.startMethodTracing(getExternalFilesDir(null).toString() + "/opentun.trace", 40* 1024 *
    // 1024);

    Builder builder = new Builder();

    VpnStatus.logInfo(R.string.last_openvpn_tun_config);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mProfile.mAllowLocalLAN) {
      allowAllAFFamilies(builder);
    }

    if (mLocalIP == null && mLocalIPv6 == null) {
      VpnStatus.logError(getString(R.string.opentun_no_ipaddr));
      return null;
    }

    if (mLocalIP != null) {
      addLocalNetworksToRoutes();
      try {
        builder.addAddress(mLocalIP.mIp, mLocalIP.len);
      } catch (IllegalArgumentException iae) {
        VpnStatus.logError(R.string.dns_add_error, mLocalIP, iae.getLocalizedMessage());
        return null;
      }
    }

    if (mLocalIPv6 != null) {
      String[] ipv6parts = mLocalIPv6.split("/");
      try {
        builder.addAddress(ipv6parts[0], Integer.parseInt(ipv6parts[1]));
      } catch (IllegalArgumentException iae) {
        VpnStatus.logError(R.string.ip_add_error, mLocalIPv6, iae.getLocalizedMessage());
        return null;
      }
    }

    for (String dns : mDnslist) {
      try {
        builder.addDnsServer(dns);
      } catch (IllegalArgumentException iae) {
        VpnStatus.logError(R.string.dns_add_error, dns, iae.getLocalizedMessage());
      }
    }

    String release = Build.VERSION.RELEASE;
    if ((Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT
            && !release.startsWith("4.4.3")
            && !release.startsWith("4.4.4")
            && !release.startsWith("4.4.5")
            && !release.startsWith("4.4.6"))
        && mMtu < 1280) {
      VpnStatus.logInfo(
          String.format(
              Locale.US,
              "Forcing MTU to 1280 instead of %d to workaround Android Bug #70916",
              mMtu));
      builder.setMtu(1280);
    } else {
      builder.setMtu(mMtu);
    }

    Collection<ipAddress> positiveIPv4Routes = mRoutes.getPositiveIPList();
    Collection<ipAddress> positiveIPv6Routes = mRoutesv6.getPositiveIPList();

    ipAddress multicastRange = new ipAddress(new CIDRIP("224.0.0.0", 3), true);

    for (NetworkSpace.ipAddress route : positiveIPv4Routes) {
      try {

        if (multicastRange.containsNet(route))
          VpnStatus.logDebug(R.string.ignore_multicast_route, route.toString());
        else builder.addRoute(route.getIPv4Address(), route.networkMask);
      } catch (IllegalArgumentException ia) {
        VpnStatus.logError(
            getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage());
      }
    }

    for (NetworkSpace.ipAddress route6 : positiveIPv6Routes) {
      try {
        builder.addRoute(route6.getIPv6Address(), route6.networkMask);
      } catch (IllegalArgumentException ia) {
        VpnStatus.logError(
            getString(R.string.route_rejected) + route6 + " " + ia.getLocalizedMessage());
      }
    }

    if ("samsung".equals(Build.BRAND)
        && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
        && mDnslist.size() >= 1) {
      // Check if the first DNS Server is in the VPN range
      try {
        ipAddress dnsServer = new ipAddress(new CIDRIP(mDnslist.get(0), 32), true);
        boolean dnsIncluded = false;
        for (ipAddress net : positiveIPv4Routes) {
          if (net.containsNet(dnsServer)) {
            dnsIncluded = true;
          }
        }
        if (!dnsIncluded) {
          String samsungwarning =
              String.format(
                  "Warning Samsung Android 5.0+ devices ignore DNS servers outside the VPN range. To enable DNS add a custom route to your DNS Server (%s) or change to a DNS inside your VPN range",
                  mDnslist.get(0));
          VpnStatus.logWarning(samsungwarning);
        }
      } catch (Exception e) {
        VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0));
      }
    }

    if (mDomain != null) builder.addSearchDomain(mDomain);

    VpnStatus.logInfo(R.string.local_ip_info, mLocalIP.mIp, mLocalIP.len, mLocalIPv6, mMtu);
    VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain);
    VpnStatus.logInfo(
        R.string.routes_info_incl,
        TextUtils.join(", ", mRoutes.getNetworks(true)),
        TextUtils.join(", ", mRoutesv6.getNetworks(true)));
    VpnStatus.logInfo(
        R.string.routes_info_excl,
        TextUtils.join(", ", mRoutes.getNetworks(false)),
        TextUtils.join(", ", mRoutesv6.getNetworks(false)));
    VpnStatus.logDebug(
        R.string.routes_debug,
        TextUtils.join(", ", positiveIPv4Routes),
        TextUtils.join(", ", positiveIPv6Routes));
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      setAllowedVpnPackages(builder);
    }

    String session = mProfile.mName;
    if (mLocalIP != null && mLocalIPv6 != null)
      session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6);
    else if (mLocalIP != null) session = getString(R.string.session_ipv4string, session, mLocalIP);

    builder.setSession(session);

    // No DNS Server, log a warning
    if (mDnslist.size() == 0) VpnStatus.logInfo(R.string.warn_no_dns);

    mLastTunCfg = getTunConfigString();

    // Reset information
    mDnslist.clear();
    mRoutes.clear();
    mRoutesv6.clear();
    mLocalIP = null;
    mLocalIPv6 = null;
    mDomain = null;

    builder.setConfigureIntent(getLogPendingIntent());

    try {
      // Debug.stopMethodTracing();
      ParcelFileDescriptor tun = builder.establish();
      if (tun == null)
        throw new NullPointerException(
            "Android establish() method returned null (Really broken network configuration?)");
      return tun;
    } catch (Exception e) {
      VpnStatus.logError(R.string.tun_open_error);
      VpnStatus.logError(getString(R.string.error) + e.getLocalizedMessage());
      if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        VpnStatus.logError(R.string.tun_error_helpful);
      }
      return null;
    }
  }
  private void startOpenVPNThreadArgs(String[] argv, Map<String, String> env) {
    LinkedList<String> argvlist = new LinkedList<String>();

    Collections.addAll(argvlist, argv);

    ProcessBuilder pb = new ProcessBuilder(argvlist);
    // Hack O rama

    String lbpath = genLibraryPath(argv, pb);

    pb.environment().put("LD_LIBRARY_PATH", lbpath);

    // Add extra variables
    for (Entry<String, String> e : env.entrySet()) {
      pb.environment().put(e.getKey(), e.getValue());
    }
    pb.redirectErrorStream(true);
    try {
      mProcess = pb.start();

      // openvpn被配置为从stdin接受配置文件
      // 写入配置文件
      Writer writer = new OutputStreamWriter(mProcess.getOutputStream(), "UTF-8");
      VpnProfile startProfile = VPNLaunchHelper.getStartProfile();
      String configString = startProfile.getConfigFile(VPNLaunchHelper.getStartContext(), false);
      writer.write(configString);
      writer.close();

      InputStream in = mProcess.getInputStream();
      BufferedReader br = new BufferedReader(new InputStreamReader(in));

      while (true) {
        String logline = br.readLine();
        if (logline == null) return;

        if (logline.startsWith(DUMP_PATH_STRING))
          mDumpPath = logline.substring(DUMP_PATH_STRING.length());

        if (logline.startsWith(BROKEN_PIE_SUPPORT) || logline.contains(BROKEN_PIE_SUPPORT2))
          mBrokenPie = true;

        // 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com'

        Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)");
        Matcher m = p.matcher(logline);
        if (m.matches()) {
          int flags = Integer.parseInt(m.group(3), 16);
          String msg = m.group(4);
          int logLevel = flags & 0x0F;

          VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO;

          if ((flags & M_FATAL) != 0) logStatus = VpnStatus.LogLevel.ERROR;
          else if ((flags & M_NONFATAL) != 0) logStatus = VpnStatus.LogLevel.WARNING;
          else if ((flags & M_WARN) != 0) logStatus = VpnStatus.LogLevel.WARNING;
          else if ((flags & M_DEBUG) != 0) logStatus = VpnStatus.LogLevel.VERBOSE;

          if (msg.startsWith("MANAGEMENT: CMD")) logLevel = Math.max(4, logLevel);

          VpnStatus.logMessageOpenVPN(logStatus, logLevel, msg);
        } else {
          VpnStatus.logInfo("P:" + logline);
        }
      }

    } catch (IOException e) {
      VpnStatus.logException("Error reading from output of OpenVPN process", e);
      stopProcess();
    }
  }
 @Override
 public void onRevoke() {
   VpnStatus.logInfo(R.string.permission_revoked);
   mManagement.stopVPN();
   endVpnService();
 }
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {

    if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false))
      mNotificationAlwaysVisible = true;

    VpnStatus.addStateListener(this);
    VpnStatus.addByteCountListener(this);

    if (intent != null && PAUSE_VPN.equals(intent.getAction())) {
      if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(true);
      return START_NOT_STICKY;
    }

    if (intent != null && RESUME_VPN.equals(intent.getAction())) {
      if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(false);
      return START_NOT_STICKY;
    }

    if (intent != null && START_SERVICE.equals(intent.getAction())) return START_NOT_STICKY;
    if (intent != null && START_SERVICE_STICKY.equals(intent.getAction())) {
      return START_REDELIVER_INTENT;
    }

    /* The intent is null when the service has been restarted */
    if (intent == null) {
      mProfile = ProfileManager.getLastConnectedProfile(this, false);
      VpnStatus.logInfo(R.string.service_restarted);

      /* Got no profile, just stop */
      if (mProfile == null) {
        Log.d("OpenVPN", "Got no last connected profile on null intent. Stopping");
        stopSelf(startId);
        return START_NOT_STICKY;
      }
      /* Do the asynchronous keychain certificate stuff */
      mProfile.checkForRestart(this);

      /* Recreate the intent */
      intent = mProfile.getStartServiceIntent(this);

    } else {
      String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID");
      mProfile = ProfileManager.get(this, profileUUID);
    }

    // Extract information from the intent.
    String prefix = getPackageName();
    String[] argv = intent.getStringArrayExtra(prefix + ".ARGV");
    String nativeLibraryDirectory = intent.getStringExtra(prefix + ".nativelib");

    String startTitle = getString(R.string.start_vpn_title, mProfile.mName);
    String startTicker = getString(R.string.start_vpn_ticker, mProfile.mName);
    showNotification(startTitle, startTicker, false, 0, LEVEL_CONNECTING_NO_SERVER_REPLY_YET);

    // Set a flag that we are starting a new VPN
    mStarting = true;
    // Stop the previous session by interrupting the thread.
    if (mManagement != null && mManagement.stopVPN())
      // an old was asked to exit, wait 1s
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // ignore
      }

    synchronized (mProcessLock) {
      if (mProcessThread != null) {
        mProcessThread.interrupt();
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          // ignore
        }
      }
    }
    // An old running VPN should now be exited
    mStarting = false;

    // Start a new session by creating a new thread.
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

    mOvpn3 = prefs.getBoolean("ovpn3", false);
    if (!"ovpn3".equals(BuildConfig.FLAVOR)) mOvpn3 = false;

    // Open the Management Interface
    if (!mOvpn3) {

      // start a Thread that handles incoming messages of the managment socket
      OpenVpnManagementThread ovpnManagementThread = new OpenVpnManagementThread(mProfile, this);
      if (ovpnManagementThread.openManagementInterface(this)) {

        Thread mSocketManagerThread = new Thread(ovpnManagementThread, "OpenVPNManagementThread");
        mSocketManagerThread.start();
        mManagement = ovpnManagementThread;
        VpnStatus.logInfo("started Socket Thread");
      } else {
        return START_NOT_STICKY;
      }
    }

    Runnable processThread;
    if (mOvpn3) {

      OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core();
      processThread = (Runnable) mOpenVPN3;
      mManagement = mOpenVPN3;

    } else {
      HashMap<String, String> env = new HashMap<>();
      processThread = new OpenVPNThread(this, argv, env, nativeLibraryDirectory);
    }

    synchronized (mProcessLock) {
      mProcessThread = new Thread(processThread, "OpenVPNProcessThread");
      mProcessThread.start();
    }
    if (mDeviceStateReceiver != null) unregisterDeviceStateReceiver();

    registerDeviceStateReceiver(mManagement);

    ProfileManager.setConnectedVpnProfile(this, mProfile);
    /* TODO: At the moment we have no way to handle asynchronous PW input
     * Fixing will also allow to handle challenge/response authentication */
    if (mProfile.needUserPWInput(true) != 0) return START_NOT_STICKY;

    return START_STICKY;
  }