ask-password: add minimal framework to allow services query SSL/harddisk passphrases from the user

This commit is contained in:
Lennart Poettering 2010-09-17 01:26:29 +02:00
parent 1ebdf5b684
commit 490aed5849
10 changed files with 794 additions and 5 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
systemd-reply-password
systemd-ask-password-agent
systemd-ask-password
systemd-kmsg-syslogd
systemd-remount-api-vfs
test-hostname

View File

@ -25,6 +25,7 @@ dbusinterfacedir=@dbusinterfacedir@
udevrulesdir=@udevrulesdir@
pamlibdir=@pamlibdir@
pkgconfigdatadir=$(datadir)/pkgconfig
polkitpolicydir=$(datadir)/polkit-1/actions
# Our own, non-special dirs
pkgsysconfdir=$(sysconfdir)/systemd
@ -56,14 +57,16 @@ AM_CPPFLAGS = \
rootbin_PROGRAMS = \
systemd \
systemctl \
systemd-notify
systemd-notify \
systemd-ask-password
bin_PROGRAMS = \
systemd-cgls
if HAVE_GTK
bin_PROGRAMS += \
systemadm
systemadm \
systemd-ask-password-agent
endif
rootlibexec_PROGRAMS = \
@ -76,7 +79,8 @@ rootlibexec_PROGRAMS = \
systemd-modules-load \
systemd-remount-api-vfs \
systemd-kmsg-syslogd \
systemd-vconsole-setup
systemd-vconsole-setup \
systemd-reply-password
noinst_PROGRAMS = \
test-engine \
@ -282,6 +286,9 @@ dist_doc_DATA = \
pkgconfigdata_DATA = \
systemd.pc
dist_polkitpolicy_DATA = \
src/org.freedesktop.systemd1.policy
noinst_LTLIBRARIES = \
libsystemd-basic.la \
libsystemd-core.la
@ -665,6 +672,18 @@ systemd_notify_SOURCES = \
systemd_notify_LDADD = \
libsystemd-basic.la
systemd_ask_password_SOURCES = \
src/ask-password.c
systemd_ask_password_LDADD = \
libsystemd-basic.la
systemd_reply_password_SOURCES = \
src/reply-password.c
systemd_reply_password_LDADD = \
libsystemd-basic.la
systemd_cgls_SOURCES = \
src/cgls.c \
src/cgroup-show.c \
@ -699,6 +718,30 @@ systemadm_LDADD = \
$(DBUSGLIB_LIBS) \
$(GTK_LIBS)
systemd_ask_password_agent_SOURCES = \
src/ask-password-agent.vala
systemd_ask_password_agent_CFLAGS = \
$(AM_CFLAGS) \
$(DBUSGLIB_CFLAGS) \
$(GTK_CFLAGS) \
-Wno-unused-variable \
-Wno-unused-function \
-Wno-shadow \
-Wno-format-nonliteral
systemd_ask_password_agent_VALAFLAGS = \
--pkg=dbus-glib-1 \
--pkg=posix \
--pkg=gtk+-2.0 \
--pkg=linux \
--pkg=gio-unix-2.0 \
-g
systemd_ask_password_agent_LDADD = \
$(DBUSGLIB_LIBS) \
$(GTK_LIBS)
pam_systemd_la_SOURCES = \
src/pam-module.c \
src/cgroup-util.c \

View File

@ -226,7 +226,7 @@ AC_SUBST(AUDIT_LIBS)
have_gtk=no
AC_ARG_ENABLE(gtk, AS_HELP_STRING([--disable-gtk], [disable GTK tools]))
if test "x$enable_gtk" != "xno"; then
PKG_CHECK_MODULES(GTK, [ gtk+-2.0 ],
PKG_CHECK_MODULES(GTK, [ gtk+-2.0 gio-unix-2.0 ],
[AC_DEFINE(HAVE_GTK, 1, [Define if GTK is available]) have_gtk=yes], have_gtk=no)
AC_SUBST(GTK_CFLAGS)
AC_SUBST(GTK_LIBS)

1
src/.gitignore vendored
View File

@ -1,2 +1,3 @@
ask-password-agent.c
systemd-interfaces.c
systemadm.c

250
src/ask-password-agent.vala Normal file
View File

@ -0,0 +1,250 @@
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
using Gtk;
using GLib;
using DBus;
using Linux;
using Posix;
[CCode (cheader_filename = "time.h")]
extern int clock_gettime(int id, out timespec ts);
public class PasswordDialog : Dialog {
public Entry entry;
public PasswordDialog(string message, string icon) {
set_title("System Password");
set_has_separator(false);
set_border_width(8);
set_default_response(ResponseType.OK);
set_icon_name(icon);
add_button(STOCK_CANCEL, ResponseType.CANCEL);
add_button(STOCK_OK, ResponseType.OK);
Container content = (Container) get_content_area();
Box hbox = new HBox(false, 16);
hbox.set_border_width(8);
content.add(hbox);
Image image = new Image.from_icon_name(icon, IconSize.DIALOG);
hbox.pack_start(image, false, false);
Box vbox = new VBox(false, 8);
hbox.pack_start(vbox, true, true);
Label label = new Label(message);
vbox.pack_start(label, false, false);
entry = new Entry();
entry.set_visibility(false);
entry.set_activates_default(true);
vbox.pack_start(entry, false, false);
entry.activate.connect(on_entry_activated);
show_all();
}
public void on_entry_activated() {
response(ResponseType.OK);
}
}
public class MyStatusIcon : StatusIcon {
File directory;
File current;
FileMonitor file_monitor;
string message;
string icon;
string socket;
PasswordDialog password_dialog;
public MyStatusIcon() throws GLib.Error {
GLib.Object(icon_name : "dialog-password");
set_title("System Password Agent");
directory = File.new_for_path("/dev/.systemd/ask-password/");
file_monitor = directory.monitor_directory(0);
file_monitor.changed.connect(file_monitor_changed);
current = null;
look_for_password();
activate.connect(status_icon_activate);
}
void file_monitor_changed(GLib.File file, GLib.File? other_file, GLib.FileMonitorEvent event_type) throws GLib.Error {
if (!file.get_basename().has_prefix("ask."))
return;
if (event_type == FileMonitorEvent.CREATED ||
event_type == FileMonitorEvent.DELETED)
look_for_password();
}
void look_for_password() throws GLib.Error {
if (current != null) {
if (!current.query_exists()) {
current = null;
if (password_dialog != null)
password_dialog.response(ResponseType.REJECT);
}
}
if (current == null) {
FileEnumerator enumerator = directory.enumerate_children("standard::name", FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
FileInfo i;
while ((i = enumerator.next_file()) != null) {
if (!i.get_name().has_prefix("ask."))
continue;
current = directory.get_child(i.get_name());
if (load_password())
break;
current = null;
}
}
if (current == null)
set_visible(false);
}
bool load_password() {
KeyFile key_file = new KeyFile();
try {
timespec ts;
key_file.load_from_file(current.get_path(), KeyFileFlags.NONE);
string not_after_as_string = key_file.get_string("Ask", "NotAfter");
clock_gettime(1, out ts);
uint64 now = (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
uint64 not_after;
if (not_after_as_string.scanf("%llu", out not_after) != 1)
return false;
if (not_after < now)
return false;
socket = key_file.get_string("Ask", "Socket");
} catch (GLib.Error e) {
return false;
}
try {
message = key_file.get_string("Ask", "Message").compress();
} catch (GLib.Error e) {
message = "Please Enter System Password!";
}
set_tooltip_text(message);
try {
icon = key_file.get_string("Ask", "Icon");
} catch (GLib.Error e) {
icon = "dialog-password";
}
set_from_icon_name(icon);
set_visible(true);
return true;
}
void status_icon_activate() throws GLib.Error {
if (current == null)
return;
if (password_dialog != null) {
password_dialog.present();
return;
}
password_dialog = new PasswordDialog(message, icon);
int result = password_dialog.run();
string password = password_dialog.entry.get_text();
password_dialog.destroy();
password_dialog = null;
if (result == ResponseType.REJECT ||
result == ResponseType.DELETE_EVENT)
return;
int to_process;
Process.spawn_async_with_pipes(
null,
{ "/usr/bin/pkexec", "/lib/systemd/systemd-reply-password", result == ResponseType.OK ? "1" : "0", socket },
null,
0,
null,
null,
out to_process,
null,
null);
OutputStream stream = new UnixOutputStream(to_process, true);
stream.write(password, password.length, null);
}
}
static const OptionEntry entries[] = {
{ null }
};
void show_error(string e) {
var m = new MessageDialog(null, 0, MessageType.ERROR, ButtonsType.CLOSE, "%s", e);
m.run();
m.destroy();
}
int main(string[] args) {
try {
Gtk.init_with_args(ref args, "[OPTION...]", entries, "systemd-ask-password-agent");
MyStatusIcon i = new MyStatusIcon();
Gtk.main();
} catch (DBus.Error e) {
show_error(e.message);
} catch (GLib.Error e) {
show_error(e.message);
}
return 0;
}

352
src/ask-password.c Normal file
View File

@ -0,0 +1,352 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/signalfd.h>
#include <getopt.h>
#include "log.h"
#include "macro.h"
#include "util.h"
static const char *arg_icon = NULL;
static const char *arg_message = NULL;
static usec_t arg_timeout = 60 * USEC_PER_SEC;
static int create_socket(char **name) {
int fd;
union {
struct sockaddr sa;
struct sockaddr_un un;
} sa;
int one = 1, r;
char *c;
assert(name);
if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
log_error("socket() failed: %m");
return -errno;
}
zero(sa);
sa.un.sun_family = AF_UNIX;
snprintf(sa.un.sun_path+1, sizeof(sa.un.sun_path)-1, "/org/freedesktop/systemd1/ask-password/%llu", random_ull());
if (bind(fd, &sa.sa, sizeof(sa_family_t) + 1 + strlen(sa.un.sun_path+1)) < 0) {
r = -errno;
log_error("bind() failed: %m");
goto fail;
}
if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
r = -errno;
log_error("SO_PASSCRED failed: %m");
goto fail;
}
if (!(c = strdup(sa.un.sun_path+1))) {
r = -ENOMEM;
log_error("Out of memory");
goto fail;
}
*name = c;
return fd;
fail:
close_nointr_nofail(fd);
return r;
}
static int help(void) {
printf("%s [OPTIONS...] MESSAGE\n\n"
"Query the user for a passphrase.\n\n"
" -h --help Show this help\n"
" --icon=NAME Icon name\n"
" --timeout=USEC Timeout in usec\n",
program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_ICON = 0x100,
ARG_TIMEOUT
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "icon", required_argument, NULL, ARG_ICON },
{ "timeout", required_argument, NULL, ARG_TIMEOUT },
{ NULL, 0, NULL, 0 }
};
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
switch (c) {
case 'h':
help();
return 0;
case ARG_ICON:
arg_icon = optarg;
break;
case ARG_TIMEOUT:
if (parse_usec(optarg, &arg_timeout) < 0 || arg_timeout <= 0) {
log_error("Failed to parse --timeout parameter %s", optarg);
return -EINVAL;
}
break;
case '?':
return -EINVAL;
default:
log_error("Unknown option code %c", c);
return -EINVAL;
}
}
if (optind != argc-1) {
help();
return -EINVAL;
}
arg_message = argv[optind];
return 0;
}
int main(int argc, char *argv[]) {
char temp[] = "/dev/.systemd/ask-password/tmp.XXXXXX";
char final[sizeof(temp)] = "";
int fd = -1, r = EXIT_FAILURE, k;
FILE *f = NULL;
char *socket_name = NULL;
int socket_fd, signal_fd;
sigset_t mask;
usec_t not_after;
log_parse_environment();
log_open();
if ((k = parse_argv(argc, argv)) < 0) {
r = k < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
goto finish;
}
if ((fd = mkostemp(temp, O_CLOEXEC|O_CREAT|O_WRONLY)) < 0) {
log_error("Failed to create password file: %m");
goto finish;
}
fchmod(fd, 0644);
if (!(f = fdopen(fd, "w"))) {
log_error("Failed to allocate FILE: %m");
goto finish;
}
fd = -1;
assert_se(sigemptyset(&mask) == 0);
sigset_add_many(&mask, SIGINT, SIGTERM, -1);
assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
log_error("signalfd(): %m");
goto finish;
}
if ((socket_fd = create_socket(&socket_name)) < 0)
goto finish;
not_after = now(CLOCK_MONOTONIC) + arg_timeout;
fprintf(f,
"[Ask]\n"
"Socket=%s\n"
"NotAfter=%llu\n",
socket_name,
(unsigned long long) not_after);
if (arg_message)
fprintf(f, "Message=%s\n", arg_message);
if (arg_icon)
fprintf(f, "Icon=%s\n", arg_icon);
fflush(f);
if (ferror(f)) {
log_error("Failed to write query file: %m");
goto finish;
}
memcpy(final, temp, sizeof(temp));
final[sizeof(final)-11] = 'a';
final[sizeof(final)-10] = 's';
final[sizeof(final)-9] = 'k';
if (rename(temp, final) < 0) {
log_error("Failed to rename query file: %m");
goto finish;
}
for (;;) {
enum {
FD_SOCKET,
FD_SIGNAL,
_FD_MAX
};
char passphrase[LINE_MAX+1];
struct msghdr msghdr;
struct iovec iovec;
struct ucred *ucred;
union {
struct cmsghdr cmsghdr;
uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
} control;
ssize_t n;
struct pollfd pollfd[_FD_MAX];
zero(pollfd);
pollfd[FD_SOCKET].fd = socket_fd;
pollfd[FD_SOCKET].events = POLLIN;
pollfd[FD_SIGNAL].fd = signal_fd;
pollfd[FD_SIGNAL].events = POLLIN;
if ((k = poll(pollfd, 2, arg_timeout/USEC_PER_MSEC)) < 0) {
if (errno == EINTR)
continue;
log_error("poll() failed: %s", strerror(-r));
goto finish;
}
if (k <= 0) {
log_notice("Timed out");
goto finish;
}
if (pollfd[FD_SIGNAL].revents & POLLIN)
break;
if (pollfd[FD_SOCKET].revents != POLLIN) {
log_error("Unexpected poll() event.");
goto finish;
}
zero(iovec);
iovec.iov_base = passphrase;
iovec.iov_len = sizeof(passphrase)-1;
zero(control);
zero(msghdr);
msghdr.msg_iov = &iovec;
msghdr.msg_iovlen = 1;
msghdr.msg_control = &control;
msghdr.msg_controllen = sizeof(control);
if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) {
if (errno == EAGAIN ||
errno == EINTR)
continue;
log_error("recvmsg() failed: %m");
goto finish;
}
if (n <= 0) {
log_error("Message too short");
continue;
}
if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
control.cmsghdr.cmsg_level != SOL_SOCKET ||
control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
log_warning("Received message without credentials. Ignoring.");
continue;
}
ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
if (ucred->uid != 0) {
log_warning("Got request from unprivileged user. Ignoring.");
continue;
}
if (passphrase[0] == '+') {
passphrase[n] = 0;
fputs(passphrase+1, stdout);
} else if (passphrase[0] == '-')
goto finish;
else {
log_error("Invalid packet");
continue;
}
break;
}
r = EXIT_SUCCESS;
finish:
if (fd >= 0)
close_nointr_nofail(fd);
if (socket_fd >= 0)
close_nointr_nofail(socket_fd);
if (f)
fclose(f);
unlink(temp);
if (final[0])
unlink(final);
return r;
}

View File

@ -1006,6 +1006,8 @@ int main(int argc, char *argv[]) {
kmod_setup();
hostname_setup();
loopback_setup();
mkdir_p("/dev/.systemd/ask-password/", 0755);
}
if ((r = manager_new(arg_running_as, &m)) < 0) {

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<!--
This file is part of systemd.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
-->
<policyconfig>
<vendor>The systemd Project</vendor>
<vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
<action id="org.freedesktop.systemd1.ReplyPassword">
<description>Send passphrase back to system</description>
<message>Authentication is required to send the entered passphrase back to the system.</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/lib/systemd/systemd-reply-password</annotate>
</action>
</policyconfig>

108
src/reply-password.c Normal file
View File

@ -0,0 +1,108 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/signalfd.h>
#include <getopt.h>
#include "log.h"
#include "macro.h"
#include "util.h"
static int send_on_socket(int fd, const char *socket_name, const void *packet, size_t size) {
union {
struct sockaddr sa;
struct sockaddr_un un;
} sa;
assert(fd >= 0);
assert(socket_name);
assert(packet);
zero(sa);
sa.un.sun_family = AF_UNIX;
strncpy(sa.un.sun_path+1, socket_name, sizeof(sa.un.sun_path)-1);
if (sendto(fd, packet, size, MSG_NOSIGNAL, &sa.sa, sizeof(sa_family_t) + 1 + strlen(socket_name)) < 0) {
log_error("Failed to send: %m");
return -1;
}
return 0;
}
int main(int argc, char *argv[]) {
int fd = -1, r = EXIT_FAILURE;
char packet[LINE_MAX];
size_t length;
log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
log_parse_environment();
log_open();
if (argc != 3) {
log_error("Wrong number of arguments.");
goto finish;
}
if (streq(argv[1], "1")) {
packet[0] = '+';
if (!fgets(packet+1, sizeof(packet)-1, stdin)) {
log_error("Failed to read password: %m");
goto finish;
}
truncate_nl(packet+1);
length = strlen(packet+1) + 1;
} else if (streq(argv[1], "0")) {
packet[0] = '-';
length = 1;
} else {
log_error("Invalid first argument %s", argv[1]);
goto finish;
}
if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
log_error("socket() failed: %m");
goto finish;
}
if (send_on_socket(fd, argv[2], packet, length) < 0)
goto finish;
r = EXIT_SUCCESS;
finish:
if (fd >= 0)
close_nointr_nofail(fd);
return r;
}

View File

@ -978,7 +978,7 @@ void show_error(string e) {
m.destroy();
}
int main (string[] args) {
int main(string[] args) {
try {
Gtk.init_with_args(ref args, "[OPTION...]", entries, "systemadm");