diff options
Diffstat (limited to 'lib/greet/client.vala')
-rw-r--r-- | lib/greet/client.vala | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/lib/greet/client.vala b/lib/greet/client.vala new file mode 100644 index 0000000..ee220b5 --- /dev/null +++ b/lib/greet/client.vala @@ -0,0 +1,267 @@ +namespace AstalGreet { +/** + * Shorthand for creating a session, posting the password, + * and starting the session with the given `cmd` + * which is parsed with [[email protected]_parse_argv]. + * + * @param username User to login to + * @param password Password of the user + * @param cmd Command to start the session with + */ +public async void login( + string username, + string password, + string cmd +) throws GLib.Error { + yield login_with_env(username, password, cmd, {}); +} + +/** + * Same as [[email protected]] but allow for setting additonal env + * in the form of `name=value` pairs. + * + * @param username User to login to + * @param password Password of the user + * @param cmd Command to start the session with + * @param env Additonal env vars to set for the session + */ +public async void login_with_env( + string username, + string password, + string cmd, + string[] env +) throws GLib.Error { + string[] argv; + Shell.parse_argv(cmd, out argv); + try { + yield new CreateSession(username).send(); + yield new PostAuthMesssage(password).send(); + yield new StartSession(argv, env).send(); + } catch (GLib.Error err) { + yield new CancelSession().send(); + throw err; + } +} + +/** + * Base Request type. + */ +public abstract class Request : Object { + protected abstract string type_name { get; } + + private string serialize() { + var node = Json.gobject_serialize(this); + var obj = node.get_object(); + obj.set_string_member("type", obj.get_string_member("type-name")); + obj.remove_member("type-name"); + + return Json.to_string(node, false); + } + + private int bytes_to_int(Bytes bytes) { + uint8[] data = (uint8[]) bytes.get_data(); + int value = 0; + + for (int i = 0; i < data.length; i++) { + value = (value << 8) | data[i]; + } + + return value; + } + + /** + * Send this request to greetd. + */ + public async Response send() throws GLib.Error { + var sock = Environment.get_variable("GREETD_SOCK"); + if (sock == null) { + throw new IOError.NOT_FOUND("greetd socket not found"); + } + + var addr = new UnixSocketAddress(sock); + var socket = new SocketClient(); + var conn = socket.connect(addr); + var payload = serialize(); + var ostream = new DataOutputStream(conn.get_output_stream()) { + byte_order = DataStreamByteOrder.HOST_ENDIAN, + }; + + ostream.put_int32(payload.length, null); + ostream.put_string(payload, null); + ostream.close(null); + + var istream = conn.get_input_stream(); + + var response_head = yield istream.read_bytes_async(4, Priority.DEFAULT, null); + var response_length = bytes_to_int(response_head); + var response_body = yield istream.read_bytes_async(response_length, Priority.DEFAULT, null); + + var response = (string)response_body.get_data(); + conn.close(null); + + var parser = new Json.Parser(); + parser.load_from_data(response); + var obj = parser.get_root().get_object(); + var type = obj.get_string_member("type"); + + print(@"$type: $response\n"); + + switch (type) { + case Success.TYPE: return new Success(obj); + case Error.TYPE: return new Error(obj); + case AuthMessage.TYPE: return new AuthMessage(obj); + default: throw new IOError.NOT_FOUND("unknown response type"); + } + } +} + +/** + * Creates a session and initiates a login attempted for the given user. + * The session is ready to be started if a success is returned. + */ +public class CreateSession : Request { + protected override string type_name { get { return "create_session"; } } + public string username { get; set; } + + public CreateSession(string username) { + Object(username: username); + } +} + +/** + * Answers an authentication message. + * If the message was informative (info, error), + * then a response does not need to be set in this message. + * The session is ready to be started if a success is returned. + */ +public class PostAuthMesssage : Request { + protected override string type_name { get { return "post_auth_message_response"; } } + public string response { get; set; } + + public PostAuthMesssage(string response) { + Object(response: response); + } +} + +/** + * Requests for the session to be started using the provided command line, + * adding the supplied environment to that created by PAM. + * The session will start after the greeter process terminates + */ +public class StartSession : Request { + protected override string type_name { get { return "start_session"; } } + public string[] cmd { get; set; } + public string[] env { get; set; } + + public StartSession(string[] cmd, string[] env = {}) { + Object(cmd: cmd, env: env); + } +} + +/** + * Cancels the session that is currently under configuration. + */ +public class CancelSession : Request { + internal override string type_name { get { return "cancel_session"; } } +} + +/** + * Base Response type. + */ +public abstract class Response : Object { + // nothing to do +} + +/** + * Indicates that the request succeeded. + */ +public class Success : Response { + internal const string TYPE = "success"; + + internal Success(Json.Object obj) { + // nothing to do + } +} + +/** + * Indicates that the request succeeded. + */ +public class Error : Response { + internal const string TYPE = "error"; + + public enum Type { + /** + * Indicates that authentication failed. + * This is not a fatal error, and is likely caused by incorrect credentials. + */ + AUTH_ERROR, + /** + * A general error. + * See the error description for more information. + */ + ERROR; + + internal static Type from_string(string str) throws IOError { + switch (str) { + case "auth_error": return Type.AUTH_ERROR; + case "error": return Type.ERROR; + default: throw new IOError.FAILED(@"unknown error_type: $str"); + } + } + } + + public Type error_type { get; private set; } + public string description { get; private set; } + + internal Error(Json.Object obj) throws IOError { + error_type = Type.from_string(obj.get_string_member("error_type")); + description = obj.get_string_member("description"); + } +} + +/** + * Indicates that the request succeeded. + */ +public class AuthMessage : Response { + internal const string TYPE = "auth_message"; + + public enum Type { + /** + * Indicates that input from the user should be + * visible when they answer this question. + */ + VISIBLE, + /** + * Indicates that input from the user should be + * considered secret when they answer this question. + */ + SECRET, + /** + * Indicates that this message is informative, not a question. + */ + INFO, + /** + * Indicates that this message is an error, not a question. + */ + ERROR; + + internal static Type from_string(string str) throws IOError { + switch (str) { + case "visible": return VISIBLE; + case "secret": return Type.SECRET; + case "info": return Type.INFO; + case "error": return Type.ERROR; + default: throw new IOError.FAILED(@"unknown message_type: $str"); + } + } + } + + public Type message_type { get; private set; } + public string message { get; private set; } + + internal AuthMessage(Json.Object obj) throws IOError { + message_type = Type.from_string(obj.get_string_member("auth_message_type")); + message = obj.get_string_member("auth_message"); + } +} +} |