chiark / gitweb /
ask-password: add minimal framework to allow services query SSL/harddisk passphrases...
[elogind.git] / src / ask-password-agent.vala
1 /***
2   This file is part of systemd.
3
4   Copyright 2010 Lennart Poettering
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 using Gtk;
21 using GLib;
22 using DBus;
23 using Linux;
24 using Posix;
25
26 [CCode (cheader_filename = "time.h")]
27 extern int clock_gettime(int id, out timespec ts);
28
29 public class PasswordDialog : Dialog {
30
31         public Entry entry;
32
33         public PasswordDialog(string message, string icon) {
34                 set_title("System Password");
35                 set_has_separator(false);
36                 set_border_width(8);
37                 set_default_response(ResponseType.OK);
38                 set_icon_name(icon);
39
40                 add_button(STOCK_CANCEL, ResponseType.CANCEL);
41                 add_button(STOCK_OK, ResponseType.OK);
42
43                 Container content = (Container) get_content_area();
44
45                 Box hbox = new HBox(false, 16);
46                 hbox.set_border_width(8);
47                 content.add(hbox);
48
49                 Image image = new Image.from_icon_name(icon, IconSize.DIALOG);
50                 hbox.pack_start(image, false, false);
51
52                 Box vbox = new VBox(false, 8);
53                 hbox.pack_start(vbox, true, true);
54
55                 Label label = new Label(message);
56                 vbox.pack_start(label, false, false);
57
58                 entry = new Entry();
59                 entry.set_visibility(false);
60                 entry.set_activates_default(true);
61                 vbox.pack_start(entry, false, false);
62
63                 entry.activate.connect(on_entry_activated);
64
65                 show_all();
66         }
67
68         public void on_entry_activated() {
69                 response(ResponseType.OK);
70         }
71 }
72
73 public class MyStatusIcon : StatusIcon {
74
75         File directory;
76         File current;
77         FileMonitor file_monitor;
78
79         string message;
80         string icon;
81         string socket;
82
83         PasswordDialog password_dialog;
84
85         public MyStatusIcon() throws GLib.Error {
86                 GLib.Object(icon_name : "dialog-password");
87                 set_title("System Password Agent");
88
89                 directory = File.new_for_path("/dev/.systemd/ask-password/");
90                 file_monitor = directory.monitor_directory(0);
91                 file_monitor.changed.connect(file_monitor_changed);
92
93                 current = null;
94                 look_for_password();
95
96                 activate.connect(status_icon_activate);
97         }
98
99         void file_monitor_changed(GLib.File file, GLib.File? other_file, GLib.FileMonitorEvent event_type) throws GLib.Error {
100
101                 if (!file.get_basename().has_prefix("ask."))
102                         return;
103
104                 if (event_type == FileMonitorEvent.CREATED ||
105                     event_type == FileMonitorEvent.DELETED)
106                         look_for_password();
107         }
108
109         void look_for_password() throws GLib.Error {
110
111                 if (current != null) {
112                         if (!current.query_exists()) {
113                                 current = null;
114                                 if (password_dialog != null)
115                                         password_dialog.response(ResponseType.REJECT);
116                         }
117                 }
118
119                 if (current == null) {
120                         FileEnumerator enumerator = directory.enumerate_children("standard::name", FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
121
122                         FileInfo i;
123                         while ((i = enumerator.next_file()) != null) {
124                                 if (!i.get_name().has_prefix("ask."))
125                                         continue;
126
127                                 current = directory.get_child(i.get_name());
128
129                                 if (load_password())
130                                         break;
131
132                                 current = null;
133                         }
134                 }
135
136                 if (current == null)
137                         set_visible(false);
138
139         }
140
141         bool load_password()  {
142
143                 KeyFile key_file = new KeyFile();
144
145                 try {
146                         timespec ts;
147
148                         key_file.load_from_file(current.get_path(), KeyFileFlags.NONE);
149
150                         string not_after_as_string = key_file.get_string("Ask", "NotAfter");
151
152                         clock_gettime(1, out ts);
153                         uint64 now = (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
154
155                         uint64 not_after;
156                         if (not_after_as_string.scanf("%llu", out not_after) != 1)
157                                 return false;
158
159                         if (not_after < now)
160                                 return false;
161
162                         socket = key_file.get_string("Ask", "Socket");
163                 } catch (GLib.Error e) {
164                         return false;
165                 }
166
167                 try {
168                         message = key_file.get_string("Ask", "Message").compress();
169                 } catch (GLib.Error e) {
170                         message = "Please Enter System Password!";
171                 }
172                 set_tooltip_text(message);
173
174                 try {
175                         icon = key_file.get_string("Ask", "Icon");
176                 } catch (GLib.Error e) {
177                         icon = "dialog-password";
178                 }
179                 set_from_icon_name(icon);
180
181                 set_visible(true);
182                 return true;
183         }
184
185         void status_icon_activate() throws GLib.Error {
186
187                 if (current == null)
188                         return;
189
190                 if (password_dialog != null) {
191                         password_dialog.present();
192                         return;
193                 }
194
195                 password_dialog = new PasswordDialog(message, icon);
196
197                 int result = password_dialog.run();
198                 string password = password_dialog.entry.get_text();
199
200                 password_dialog.destroy();
201                 password_dialog = null;
202
203                 if (result == ResponseType.REJECT ||
204                     result == ResponseType.DELETE_EVENT)
205                         return;
206
207                 int to_process;
208
209                 Process.spawn_async_with_pipes(
210                                 null,
211                                 { "/usr/bin/pkexec", "/lib/systemd/systemd-reply-password", result == ResponseType.OK ? "1" : "0", socket },
212                                 null,
213                                 0,
214                                 null,
215                                 null,
216                                 out to_process,
217                                 null,
218                                 null);
219
220                 OutputStream stream = new UnixOutputStream(to_process, true);
221
222                 stream.write(password, password.length, null);
223         }
224 }
225
226 static const OptionEntry entries[] = {
227         { null }
228 };
229
230 void show_error(string e) {
231         var m = new MessageDialog(null, 0, MessageType.ERROR, ButtonsType.CLOSE, "%s", e);
232         m.run();
233         m.destroy();
234 }
235
236 int main(string[] args) {
237         try {
238                 Gtk.init_with_args(ref args, "[OPTION...]", entries, "systemd-ask-password-agent");
239
240                 MyStatusIcon i = new MyStatusIcon();
241                 Gtk.main();
242
243         } catch (DBus.Error e) {
244                 show_error(e.message);
245         } catch (GLib.Error e) {
246                 show_error(e.message);
247         }
248
249         return 0;
250 }