214 lines
8.2 KiB
Kotlin
214 lines
8.2 KiB
Kotlin
package de.polyfish0.pamauth.services
|
|
|
|
import android.app.ActivityManager
|
|
import android.app.ForegroundServiceStartNotAllowedException
|
|
import android.app.NotificationChannel
|
|
import android.app.NotificationManager
|
|
import android.app.PendingIntent
|
|
import android.app.Service
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.SharedPreferences
|
|
import android.content.pm.ServiceInfo
|
|
import android.graphics.PixelFormat
|
|
import android.net.nsd.NsdManager
|
|
import android.net.nsd.NsdServiceInfo
|
|
import android.os.Build
|
|
import android.os.Handler
|
|
import android.os.IBinder
|
|
import android.os.Looper
|
|
import android.os.PowerManager
|
|
import android.util.Log
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.Window
|
|
import android.view.WindowManager
|
|
import androidx.core.app.NotificationCompat
|
|
import androidx.core.app.ServiceCompat
|
|
import androidx.lifecycle.LifecycleService
|
|
import de.polyfish0.pamauth.R
|
|
import de.polyfish0.pamauth.activities.TransparentBiometricActivity
|
|
import de.polyfish0.pamauth.data.PAMRequestData
|
|
import de.polyfish0.pamauth.utils.CallbackManager
|
|
import kotlinx.serialization.json.Json
|
|
import java.net.ServerSocket
|
|
import java.net.Socket
|
|
import kotlin.uuid.ExperimentalUuidApi
|
|
import kotlin.uuid.Uuid
|
|
import androidx.core.content.edit
|
|
|
|
class PAMServerService : LifecycleService() {
|
|
private val socketMap = mutableMapOf<String, Socket>()
|
|
private lateinit var notificationManager: NotificationManager
|
|
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
|
|
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
|
|
|
getSharedPreferences("pam", Context.MODE_PRIVATE).edit {
|
|
putBoolean("serviceRunning", true)
|
|
}
|
|
|
|
try {
|
|
val channel = NotificationChannel(
|
|
"PAMServer",
|
|
"PAM Authentication Server",
|
|
NotificationManager.IMPORTANCE_MAX
|
|
).apply {
|
|
description = "Handles PAM authentication server foreground service"
|
|
}
|
|
|
|
notificationManager.createNotificationChannel(channel)
|
|
|
|
val notification = NotificationCompat.Builder(this, "PAMServer")
|
|
.setContentTitle("PAM Authentication")
|
|
.setContentText("PAM Server is running")
|
|
.setSmallIcon(android.R.drawable.ic_lock_lock)
|
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
.setOngoing(true)
|
|
.build()
|
|
|
|
ServiceCompat.startForeground(
|
|
this,
|
|
100,
|
|
notification,
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
|
} else {
|
|
0
|
|
}
|
|
)
|
|
} catch (e: Exception) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
|
&& e is ForegroundServiceStartNotAllowedException
|
|
) {
|
|
// App not in a valid state to start foreground service
|
|
// (e.g. started from bg)
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onBind(intent: Intent): IBinder? {
|
|
super.onBind(intent)
|
|
return null
|
|
}
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
super.onStartCommand(intent, flags, startId)
|
|
startServer()
|
|
|
|
return START_STICKY
|
|
}
|
|
|
|
private fun registerService(localPort: Int) {
|
|
val serviceInfo = NsdServiceInfo().apply {
|
|
serviceName = "PAMAuthServer"
|
|
serviceType = "_pamAuthServer._tcp"
|
|
port = localPort
|
|
}
|
|
|
|
val registrationListener = object : NsdManager.RegistrationListener {
|
|
override fun onServiceRegistered(NsdServiceInfo: NsdServiceInfo) {
|
|
// Save the service name. Android may have changed it in order to
|
|
// resolve a conflict, so update the name you initially requested
|
|
// with the name Android actually used.
|
|
}
|
|
|
|
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
|
|
// Registration failed! Put debugging code here to determine why.
|
|
}
|
|
|
|
override fun onServiceUnregistered(arg0: NsdServiceInfo) {
|
|
// Service has been unregistered. This only happens when you call
|
|
// NsdManager.unregisterService() and pass in this listener.
|
|
}
|
|
|
|
override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
|
|
// Unregistration failed. Put debugging code here to determine why.
|
|
}
|
|
}
|
|
|
|
(getSystemService(NSD_SERVICE) as NsdManager).apply {
|
|
registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
|
|
}
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
getSharedPreferences("pam", Context.MODE_PRIVATE).edit {
|
|
putBoolean("serviceRunning", false)
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalUuidApi::class)
|
|
private fun startServer() {
|
|
Thread {
|
|
try {
|
|
val serverSocket = ServerSocket(0).also { socket ->
|
|
Log.d("PAM", "$socket")
|
|
registerService(socket.localPort)
|
|
}
|
|
while (true) {
|
|
val client = serverSocket.accept()
|
|
|
|
Thread {
|
|
val input = client.getInputStream().bufferedReader()
|
|
val data = Json.decodeFromString<PAMRequestData>(input.readLine())
|
|
|
|
val callbackID = Uuid.random().toString()
|
|
CallbackManager.put(callbackID, ::test)
|
|
socketMap[callbackID] = client
|
|
|
|
val intent = Intent(applicationContext, TransparentBiometricActivity::class.java).apply {
|
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
|
|
|
putExtra("computerName", data.computerName)
|
|
putExtra("process", data.process)
|
|
putExtra("callbackID", callbackID)
|
|
}
|
|
if(hasAppTask(applicationContext)) {
|
|
startActivity(intent)
|
|
}else {
|
|
val notification = NotificationCompat.Builder(applicationContext, "PAMServer")
|
|
.setContentTitle("PAM Authentication request")
|
|
.setContentText("The computer \"${data.computerName}\" has requested an authentication for the process \"${data.process}\"")
|
|
.setSmallIcon(android.R.drawable.ic_lock_lock)
|
|
.setContentIntent(
|
|
PendingIntent.getActivity(
|
|
applicationContext,
|
|
0,
|
|
intent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
)
|
|
)
|
|
.setAutoCancel(true)
|
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
.build()
|
|
|
|
notificationManager.notify(callbackID.hashCode(), notification)
|
|
}
|
|
|
|
}.start()
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e("PAM", "Server error", e)
|
|
}
|
|
}.start()
|
|
}
|
|
|
|
fun test(result: Boolean, callbackID: String) {
|
|
Thread {
|
|
val client = socketMap.remove(callbackID)?: throw RuntimeException("No client with callbackID \"$callbackID\" registered")
|
|
client.getOutputStream().write("Result: $result".toByteArray())
|
|
client.close()
|
|
}.start()
|
|
}
|
|
|
|
fun hasAppTask(context: Context): Boolean {
|
|
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
|
val tasks = am.appTasks
|
|
return tasks.any { it.taskInfo.baseIntent.component?.packageName == context.packageName }
|
|
}
|
|
} |