From: NeilBrown Date: Wed, 12 Jul 2023 06:37:03 +0000 (+1000) Subject: Add lib-askpass X-Git-Url: http://git.neil.brown.name/?a=commitdiff_plain;h=ff7e9a8ce7dddae7a0ec15d760d9879825e8ee59;p=edlib.git Add lib-askpass AskPass will as for a password and not display what is entered. A new script "el-askpass" is provided that ssh can use to ask for a password through edlib. lib-shellcmd sets the environment so this script is used. Net result is that if a command run from edlib needs ssh to ask for a password, it can. Signed-off-by: NeilBrown --- diff --git a/DOC/TODO.md b/DOC/TODO.md index 4a670a54..7c0e070d 100644 --- a/DOC/TODO.md +++ b/DOC/TODO.md @@ -178,9 +178,13 @@ Module features ### lib-askpass -- [ ] New module which server can use to ask for a password. +- [X] New module which server can use to ask for a password. When an external program is run, we pass SSH_ASKPASS=client and SSH_ASKPASS_REQUIRE=force. +- [ ] have askpass tell shellmode (and others) about the SSH_ASKPASS* + environment, so they don't need to know it themselves +- [ ] start-agent command which runs ssh-agent can records the + environment reported for later shells. ### lib-linecount diff --git a/Makefile b/Makefile index f00fdf55..a794a2ee 100644 --- a/Makefile +++ b/Makefile @@ -86,17 +86,18 @@ SHOBJ = O/doc-text.o O/doc-dir.o O/doc-docs.o \ O/lib-renderline.o O/lib-x11selection-gtk.o O/lib-autosave.o \ O/lib-x11selection-xcb.o O/display-x11-xcb.o \ O/lib-linefilter.o O/lib-wiggle.o O/lib-aspell.o O/lib-calc.o \ - O/lib-menu.o O/lib-unicode-names.o \ + O/lib-menu.o O/lib-unicode-names.o O/lib-askpass.o \ O/lang-python.o \ O/mode-emacs.o O/emacs-search.o \ O/display-ncurses.o XOBJ = O/rexel.o WOBJ = O/libwiggle.a -BIN = edlib elc +BIN = edlib elc el-askpass bin/edlib : edlib bin/elc : python/lib-server.py +bin/el-askpass : python/lib-server.py # From python 3.8 on we need python3-embed to get the right libraries pypkg=$(shell pkg-config --atleast-version=3.8 python3 && echo python3-embed || echo python3) diff --git a/lib-askpass.c b/lib-askpass.c new file mode 100644 index 00000000..6ab786c1 --- /dev/null +++ b/lib-askpass.c @@ -0,0 +1,167 @@ +/* + * Copyright Neil Brown ©2023 + * May be distributed under terms of GPLv2 - see file:COPYING + * + * askpass - ask for a password. + * This doesn't yet have any focus on protecting the password + * from being swapped out. + * + * We place a popup in mid-display with a message and + * "HEAVY BALLOT X" for each character typed. + */ + +#include +#include + +#define PANE_DATA_TYPE struct apinfo +#include "core.h" + +struct apinfo { + char *msg safe; + struct buf b; + struct command *c; +}; +#include "core-pane.h" + +static struct map *askpass_map; +DEF_LOOKUP_CMD(askpass_handle, askpass_map); + +DEF_CMD(askpass_refresh_view) +{ + struct buf b; + struct apinfo *ai = &ci->home->data; + int shift = 0; + int i; + + buf_init(&b); + buf_concat(&b, ai->msg); + for (i = 0; i < utf8_strlen(buf_final(&ai->b)); i++) + buf_append(&b, 0x2718); /* HEAVY BALLOT X */ + call("render-line:set", ci->focus, b.len, NULL, buf_final(&b)); + for (i = 0; i < 10; i++) { + int cw; + attr_set_int(&ci->focus->attrs, "shift_left", shift); + call("render-line:measure", ci->focus, b.len); + cw = pane_attr_get_int(ci->focus, "curs_width", 1); + if (ci->home->parent->cx < ci->home->parent->w - cw) + break; + shift += 8 * cw; + } + free(buf_final(&b)); + return 1; +} + +DEF_CMD(askpass_key) +{ + const char *k = ksuffix(ci, "K-"); + struct apinfo *ai = &ci->home->data; + + buf_concat(&ai->b, k); + pane_damaged(ci->home, DAMAGED_VIEW); + return 1; +} + +DEF_CMD(askpass_bs) +{ + struct apinfo *ai = &ci->home->data; + + if (ai->b.len > 0) + ai->b.len = utf8_round_len(ai->b.b, ai->b.len-1); + pane_damaged(ci->home, DAMAGED_VIEW); + return 1; +} + +DEF_CMD(askpass_ignore) +{ + return 1; +} + +DEF_CMD(askpass_done) +{ + struct apinfo *ai = &ci->home->data; + + comm_call(ai->c, "cb", ci->focus, ai->b.len, NULL, + buf_final(&ai->b)); + memset(ai->b.b, 0, ai->b.size); + call("popup:close", ci->focus); + return 1; +} + +DEF_CMD(askpass_abort) +{ + struct apinfo *ai = &ci->home->data; + + memset(ai->b.b, 0, ai->b.size); + comm_call(ai->c, "cb", ci->focus, -1); + call("popup:close", ci->focus); + return 1; +} + +DEF_CMD(askpass_attach) +{ + struct pane *p, *p2; + + if (!ci->str || !ci->comm2) + return Enoarg; + p = call_ret(pane, "PopupTile", ci->focus, 0, NULL, "D2"); + if (!p) + return Efail; + p2 = call_ret(pane, "attach-view", p); + if (!p2) + goto fail; + p = p2; + + p2 = call_ret(pane, "attach-renderline", p); + if (!p2) + goto fail; + p = p2; + + p2 = pane_register(p, 0, &askpass_handle.c); + if (!p2) + goto fail; + p = p2; + + attr_set_str(&p->attrs, "pane-title", "Ask Password"); + + p->data.msg = strdup(ci->str); + p->data.c = command_get(ci->comm2); + buf_init(&p->data.b); + pane_damaged(p, DAMAGED_VIEW); + return 1; + +fail: + if (ci->focus->focus) + pane_close(ci->focus->focus); + return Efail; +} + +DEF_CMD(askpass_close) +{ + struct apinfo *ai = &ci->home->data; + + free(ai->msg); + ai->msg = safe_cast NULL; + free(buf_final(&ai->b)); + ai->b.b = safe_cast NULL; + command_put(ai->c); + ai->c = NULL; + return 1; +} + +void edlib_init(struct pane *ed safe) +{ + call_comm("global-set-command", ed, &askpass_attach, + 0, NULL, "AskPass"); + + askpass_map = key_alloc(); + key_add(askpass_map, "Close", &askpass_close); + key_add_prefix(askpass_map, "K-", &askpass_key); + key_add_prefix(askpass_map, "K:", &askpass_ignore); + key_add_prefix(askpass_map, "M:", &askpass_ignore); + key_add(askpass_map, "K:Enter", &askpass_done); + key_add(askpass_map, "K:Backspace", &askpass_bs); + key_add(askpass_map, "K:ESC", &askpass_abort); + key_add(askpass_map, "K:C-C", &askpass_abort); + key_add(askpass_map, "K:C-G", &askpass_abort); + key_add_prefix(askpass_map, "Refresh:view", &askpass_refresh_view); +} diff --git a/modules.ini b/modules.ini index 6a141782..e6246536 100644 --- a/modules.ini +++ b/modules.ini @@ -155,3 +155,4 @@ lib-shellcmd = attach-shellcmd lib-unicode-names = Unicode-names lib-menu = attach-menu +lib-askpass = AskPass diff --git a/python/lib-server.py b/python/lib-server.py index 786eed19..ee75cacb 100755 --- a/python/lib-server.py +++ b/python/lib-server.py @@ -174,6 +174,17 @@ if sys.argv[0] == "": self.sock = None self.close() return edlib.Efalse + if cmd == "askpass": + def cb(key, str1, **a): + if str1: + self.sock.send(str1.encode()) + self.sock.close() + self.sock = None + + p = self.choose_pane() + if p: + p.call("AskPass", arg, cb) + return 1 self.sock.send(b"Unknown") return 1 @@ -317,6 +328,19 @@ if sys.argv[0] == "": edlib.editor.call("global-set-command", "interactive-cmd-server-start", server_rebind) +elif 'askpass' in sys.argv[0]: + msg = sys.argv[1] + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + s.connect(sockpath) + except OSError: + print("Cannot connect to ",sockpath) + sys.exit(1) + + s.send(b"askpass\0" + msg.encode()) + ret = s.recv(1000) + print(ret.decode('utf-8', 'ignore')) + sys.exit(0) else: term = False file = None diff --git a/python/lib-shellcmd.py b/python/lib-shellcmd.py index 5f87d481..8975fdba 100644 --- a/python/lib-shellcmd.py +++ b/python/lib-shellcmd.py @@ -59,6 +59,12 @@ class ShellPane(edlib.Pane): self.call("doc:replace", "Cmd: %s\nCwd: %s\n\n" % (cmd,cwd)) env = os.environ.copy() env['PWD'] = cwd + askp = self.call("xdg-find-edlib-file", + "el-askpass", "bin", ret='str') + if askp: + env['SSH_ASKPASS'] = askp + env['SSH_ASKPASS_REQUIRE'] = 'force' + try: self.pipe = subprocess.Popen(cmd, shell=True, close_fds=True, cwd=cwd, env=env,