maxchehab / Remote-Linux-Unlocker

Android application and Linux daemon to login and out of computer remotely.
28 stars 6 forks source link

cannot unlock.. #3

Open adzweig opened 5 years ago

adzweig commented 5 years ago

Hi Max I tried our your application to steer my Ubuntu 18.04 machine from a Samsung Galaxy tab S4 under Android 8.1.0.

The pairing went smooth and I was able to lock the computer. However then I could not unlock the screen. Do you have any idea about the potential reason for this? I have been looking for a way to remotely log in my ubuntu machine from android, and so far have not found any other app than yours... Thus I'dbe keen to get the unlock feature working.

regards

Adrian

appositeit commented 5 years ago

A patch for this is incoming.

From 18.04 Ubuntu changed back to the Gnome desktop, and a bunch of the tooling used in the unlocker-daemon.py broke.

To fix this we need to:

The original script looks for the first 'real' user in /etc/shadow. I look for the first process run by a non-daemon user process with a DBUS_SESSION_BUS_ADDRESS inn their environment and use that BUS_ADDRESS and user's uid to seteuid.

The orginal script shells out to control the screensaver, I've modified it to use python dbus commands to query screenlock status and lock/unlock the screensaver.

My modified version is below. I think it should work with various window managers, but it's only been tested on gnome. Behaviour is undefined if there are several user sessions with window managers running.

'''This daemon listens on a designated port (61599) and can respond to requests
to get the status of the screensaver and lock/unlock the screensaver.

It's low on security, but high on convenience!

If multiple users are running graphical sessions it's more or less random which
user will be unlocked. See get_bus_and_uid for discussion.
'''

import socket, sys, json, subprocess, os
import dbus
from pprint import pprint
import pgrep
import psutil

PORT = 61599

def get_bus_and_uid():
    '''Find the first running process with a DBUS session address run by
    a non-daemon user, and return both the DBUS_SESSION_BUS_ADDRESS and the
    uid.'''

    DBUS_ADDRESS = 'DBUS_SESSION_BUS_ADDRESS'
    UIDS = 'uids'

    # Find all non-daemon users.
    all_users = [i.split(':') for i in open('/etc/shadow').readlines()]
    users = [i[0] for i in all_users if i[1] not in ('!', '*')]

    # Find the first non-daemon user process with a DBUS_SESSION_BUS_ADDRESS
    # in it's environment.
    user_address = {}
    for proc in psutil.process_iter():
        try:
            pinfo = proc.as_dict(attrs=['pid', 'username', UIDS])
        except psutil.NoSuchProcess:
            pass
        user = pinfo['username']
        # Ignore process run by daemons.
        if user not in users:
            continue
        environ = psutil.Process(pid=pinfo['pid']).environ()
        if DBUS_ADDRESS in environ:
            # pinfo[uids] returns real, effective and saved uids.
            return environ[DBUS_ADDRESS], pinfo[UIDS][0]
    return None, None

def is_json(myjson):
    try:
        json_object = json.loads(myjson)
    except ValueError, e:
        return False
    return True

def get_interface():
    session_bus = dbus.SessionBus()
    screensaver_list = ['org.gnome.ScreenSaver',
                        'org.cinnamon.ScreenSaver',
                        'org.kde.screensaver',
                        'org.freedesktop.ScreenSaver']
    for each in screensaver_list:
        try:
            object_path = '/{0}'.format(each.replace('.', '/'))
            get_object = session_bus.get_object(each, object_path)
            return dbus.Interface(get_object, each)
        except dbus.exceptions.DBusException:
            pass

def is_locked():
    interface = get_interface()
    return bool(interface.GetActive())

def lock(state):
    interface = get_interface()
    return bool(interface.SetActive(state))

def authenticate_key(key):
    with open(os.path.dirname(os.path.realpath(__file__)) + '/keys.db') as file:
        for line in file:
            if line.strip() == key:
                return True
                break

    return False

# Set the environment variables and uid of our user session.
DBUS_ADDRESS, UID = get_bus_and_uid()
if not DBUS_ADDRESS:
    sys.exit('No DBUS_SESSION_BUS_ADDRESS found.')
os.environ['DBUS_SESSION_BUS_ADDRESS'] = DBUS_ADDRESS
os.seteuid(UID)

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# Bind the socket to the port
server_address = ('', PORT)
print >>sys.stderr, 'starting up on %s port %s' % server_address
sock.bind(server_address)

# Listen for incoming connections
sock.listen(1)

while True:
    # Wait for a connection
    print >>sys.stderr, 'waiting for a connection'
    connection, client_address = sock.accept()

    try:
        print >>sys.stderr, 'connection from', client_address

        # Receive the data in small chunks and retransmit it
        while True:
            data = connection.recv(256).strip()
            print >>sys.stderr, 'received "%s"' % data
            if is_json(data):
                data = json.loads(data)
                if data["command"] == "lock" and data["key"] and authenticate_key(data["key"]):
                    print >>sys.stderr, 'client requesting lock'
                    lock(True)
                    connection.sendall('{"status":"success"')
                    break
                elif data["command"] == "unlock" and data["key"] and authenticate_key(data["key"]):
                    print >>sys.stderr, 'client requesting unlock'
                    lock(False)
                    connection.sendall('{"status":"success"')
                    break
                elif data["command"] == "status" and data["key"] and authenticate_key(data["key"]):
                    print >>sys.stderr, 'client requesting echo'
                    response = '{"status":"success","hostname":"' + socket.gethostname() +  '","isLocked":"' + str(is_locked()) + '"}';
                    print >> sys.stderr, response
                    connection.sendall(response)
                    break

            else:
                print >>sys.stderr, 'no more data from', client_address
                break

    finally:
        # Clean up the connection
        connection.close()
appositeit commented 5 years ago

Pull request is here:

https://github.com/maxchehab/Remote-Linux-Unlocker/pull/5