chiark / gitweb /
audit: ignore if we get EPERM
[elogind.git] / src / update-utmp / update-utmp.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <assert.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27
28 #include <dbus/dbus.h>
29
30 #ifdef HAVE_AUDIT
31 #include <libaudit.h>
32 #endif
33
34 #include "log.h"
35 #include "macro.h"
36 #include "util.h"
37 #include "special.h"
38 #include "utmp-wtmp.h"
39 #include "dbus-common.h"
40
41 typedef struct Context {
42         DBusConnection *bus;
43 #ifdef HAVE_AUDIT
44         int audit_fd;
45 #endif
46 } Context;
47
48 static usec_t get_startup_time(Context *c) {
49         const char
50                 *interface = "org.freedesktop.systemd1.Manager",
51                 *property = "StartupTimestamp";
52
53         DBusError error;
54         usec_t t = 0;
55         DBusMessage *m = NULL, *reply = NULL;
56         DBusMessageIter iter, sub;
57
58         dbus_error_init(&error);
59
60         assert(c);
61
62         if (!(m = dbus_message_new_method_call(
63                               "org.freedesktop.systemd1",
64                               "/org/freedesktop/systemd1",
65                               "org.freedesktop.DBus.Properties",
66                               "Get"))) {
67                 log_error("Could not allocate message.");
68                 goto finish;
69         }
70
71         if (!dbus_message_append_args(m,
72                                       DBUS_TYPE_STRING, &interface,
73                                       DBUS_TYPE_STRING, &property,
74                                       DBUS_TYPE_INVALID)) {
75                 log_error("Could not append arguments to message.");
76                 goto finish;
77         }
78
79         if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
80                 log_error("Failed to send command: %s", bus_error_message(&error));
81                 goto finish;
82         }
83
84         if (!dbus_message_iter_init(reply, &iter) ||
85             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
86                 log_error("Failed to parse reply.");
87                 goto finish;
88         }
89
90         dbus_message_iter_recurse(&iter, &sub);
91
92         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64)  {
93                 log_error("Failed to parse reply.");
94                 goto finish;
95         }
96
97         dbus_message_iter_get_basic(&sub, &t);
98
99 finish:
100         if (m)
101                 dbus_message_unref(m);
102
103         if (reply)
104                 dbus_message_unref(reply);
105
106         dbus_error_free(&error);
107
108         return t;
109 }
110
111 static int get_current_runlevel(Context *c) {
112         static const struct {
113                 const int runlevel;
114                 const char *special;
115         } table[] = {
116                 /* The first target of this list that is active or has
117                  * a job scheduled wins. We prefer runlevels 5 and 3
118                  * here over the others, since these are the main
119                  * runlevels used on Fedora. It might make sense to
120                  * change the order on some distributions. */
121                 { '5', SPECIAL_RUNLEVEL5_TARGET },
122                 { '3', SPECIAL_RUNLEVEL3_TARGET },
123                 { '4', SPECIAL_RUNLEVEL4_TARGET },
124                 { '2', SPECIAL_RUNLEVEL2_TARGET },
125                 { 'S', SPECIAL_RESCUE_TARGET },
126         };
127         const char
128                 *interface = "org.freedesktop.systemd1.Unit",
129                 *property = "ActiveState";
130
131         DBusMessage *m = NULL, *reply = NULL;
132         int r = 0;
133         unsigned i;
134         DBusError error;
135
136         assert(c);
137
138         dbus_error_init(&error);
139
140         for (i = 0; i < ELEMENTSOF(table); i++) {
141                 const char *path = NULL, *state;
142                 DBusMessageIter iter, sub;
143
144                 if (!(m = dbus_message_new_method_call(
145                                       "org.freedesktop.systemd1",
146                                       "/org/freedesktop/systemd1",
147                                       "org.freedesktop.systemd1.Manager",
148                                       "GetUnit"))) {
149                         log_error("Could not allocate message.");
150                         r = -ENOMEM;
151                         goto finish;
152                 }
153
154                 if (!dbus_message_append_args(m,
155                                               DBUS_TYPE_STRING, &table[i].special,
156                                               DBUS_TYPE_INVALID)) {
157                         log_error("Could not append arguments to message.");
158                         r = -ENOMEM;
159                         goto finish;
160                 }
161
162                 if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
163                         dbus_error_free(&error);
164                         continue;
165                 }
166
167                 if (!dbus_message_get_args(reply, &error,
168                                            DBUS_TYPE_OBJECT_PATH, &path,
169                                            DBUS_TYPE_INVALID)) {
170                         log_error("Failed to parse reply: %s", bus_error_message(&error));
171                         r = -EIO;
172                         goto finish;
173                 }
174
175                 dbus_message_unref(m);
176                 if (!(m = dbus_message_new_method_call(
177                                       "org.freedesktop.systemd1",
178                                       path,
179                                       "org.freedesktop.DBus.Properties",
180                                       "Get"))) {
181                         log_error("Could not allocate message.");
182                         r = -ENOMEM;
183                         goto finish;
184                 }
185
186                 if (!dbus_message_append_args(m,
187                                               DBUS_TYPE_STRING, &interface,
188                                               DBUS_TYPE_STRING, &property,
189                                               DBUS_TYPE_INVALID)) {
190                         log_error("Could not append arguments to message.");
191                         r = -ENOMEM;
192                         goto finish;
193                 }
194
195                 dbus_message_unref(reply);
196                 if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
197                         log_error("Failed to send command: %s", bus_error_message(&error));
198                         r = -EIO;
199                         goto finish;
200                 }
201
202                 if (!dbus_message_iter_init(reply, &iter) ||
203                     dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
204                         log_error("Failed to parse reply.");
205                         r = -EIO;
206                         goto finish;
207                 }
208
209                 dbus_message_iter_recurse(&iter, &sub);
210
211                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
212                         log_error("Failed to parse reply.");
213                         r = -EIO;
214                         goto finish;
215                 }
216
217                 dbus_message_iter_get_basic(&sub, &state);
218
219                 if (streq(state, "active") || streq(state, "reloading"))
220                         r = table[i].runlevel;
221
222                 dbus_message_unref(m);
223                 dbus_message_unref(reply);
224                 m = reply = NULL;
225
226                 if (r)
227                         break;
228         }
229
230 finish:
231         if (m)
232                 dbus_message_unref(m);
233
234         if (reply)
235                 dbus_message_unref(reply);
236
237         dbus_error_free(&error);
238
239         return r;
240 }
241
242 static int on_reboot(Context *c) {
243         int r = 0, q;
244         usec_t t;
245
246         assert(c);
247
248         /* We finished start-up, so let's write the utmp
249          * record and send the audit msg */
250
251 #ifdef HAVE_AUDIT
252         if (c->audit_fd >= 0)
253                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "init", NULL, NULL, NULL, 1) < 0 &&
254                     errno != EPERM) {
255                         log_error("Failed to send audit message: %m");
256                         r = -errno;
257                 }
258 #endif
259
260         /* If this call fails it will return 0, which
261          * utmp_put_reboot() will then fix to the current time */
262         t = get_startup_time(c);
263
264         if ((q = utmp_put_reboot(t)) < 0) {
265                 log_error("Failed to write utmp record: %s", strerror(-q));
266                 r = q;
267         }
268
269         return r;
270 }
271
272 static int on_shutdown(Context *c) {
273         int r = 0, q;
274
275         assert(c);
276
277         /* We started shut-down, so let's write the utmp
278          * record and send the audit msg */
279
280 #ifdef HAVE_AUDIT
281         if (c->audit_fd >= 0)
282                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "init", NULL, NULL, NULL, 1) < 0 &&
283                     errno != EPERM) {
284                         log_error("Failed to send audit message: %m");
285                         r = -errno;
286                 }
287 #endif
288
289         if ((q = utmp_put_shutdown()) < 0) {
290                 log_error("Failed to write utmp record: %s", strerror(-q));
291                 r = q;
292         }
293
294         return r;
295 }
296
297 static int on_runlevel(Context *c) {
298         int r = 0, q, previous, runlevel;
299
300         assert(c);
301
302         /* We finished changing runlevel, so let's write the
303          * utmp record and send the audit msg */
304
305         /* First, get last runlevel */
306         if ((q = utmp_get_runlevel(&previous, NULL)) < 0) {
307
308                 if (q != -ESRCH && q != -ENOENT) {
309                         log_error("Failed to get current runlevel: %s", strerror(-q));
310                         return q;
311                 }
312
313                 /* Hmm, we didn't find any runlevel, that means we
314                  * have been rebooted */
315                 r = on_reboot(c);
316                 previous = 0;
317         }
318
319         /* Secondly, get new runlevel */
320         if ((runlevel = get_current_runlevel(c)) < 0)
321                 return runlevel;
322
323         if (previous == runlevel)
324                 return 0;
325
326 #ifdef HAVE_AUDIT
327         if (c->audit_fd >= 0) {
328                 char *s = NULL;
329
330                 if (asprintf(&s, "old-level=%c new-level=%c",
331                              previous > 0 ? previous : 'N',
332                              runlevel > 0 ? runlevel : 'N') < 0)
333                         return -ENOMEM;
334
335                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, NULL, NULL, NULL, 1) < 0 &&
336                     errno != EPERM) {
337                         log_error("Failed to send audit message: %m");
338                         r = -errno;
339                 }
340
341                 free(s);
342         }
343 #endif
344
345         if ((q = utmp_put_runlevel(runlevel, previous)) < 0) {
346                 log_error("Failed to write utmp record: %s", strerror(-q));
347                 r = q;
348         }
349
350         return r;
351 }
352
353 int main(int argc, char *argv[]) {
354         int r;
355         DBusError error;
356         Context c;
357
358         dbus_error_init(&error);
359
360         zero(c);
361 #ifdef HAVE_AUDIT
362         c.audit_fd = -1;
363 #endif
364
365         if (getppid() != 1) {
366                 log_error("This program should be invoked by init only.");
367                 return EXIT_FAILURE;
368         }
369
370         if (argc != 2) {
371                 log_error("This program requires one argument.");
372                 return EXIT_FAILURE;
373         }
374
375         log_set_target(LOG_TARGET_AUTO);
376         log_parse_environment();
377         log_open();
378
379         umask(0022);
380
381 #ifdef HAVE_AUDIT
382         if ((c.audit_fd = audit_open()) < 0 &&
383             /* If the kernel lacks netlink or audit support,
384              * don't worry about it. */
385             errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT)
386                 log_error("Failed to connect to audit log: %m");
387 #endif
388
389         if (bus_connect(DBUS_BUS_SYSTEM, &c.bus, NULL, &error) < 0) {
390                 log_error("Failed to get D-Bus connection: %s", bus_error_message(&error));
391                 r = -EIO;
392                 goto finish;
393         }
394
395         log_debug("systemd-update-utmp running as pid %lu", (unsigned long) getpid());
396
397         if (streq(argv[1], "reboot"))
398                 r = on_reboot(&c);
399         else if (streq(argv[1], "shutdown"))
400                 r = on_shutdown(&c);
401         else if (streq(argv[1], "runlevel"))
402                 r = on_runlevel(&c);
403         else {
404                 log_error("Unknown command %s", argv[1]);
405                 r = -EINVAL;
406         }
407
408         log_debug("systemd-update-utmp stopped as pid %lu", (unsigned long) getpid());
409
410 finish:
411 #ifdef HAVE_AUDIT
412         if (c.audit_fd >= 0)
413                 audit_close(c.audit_fd);
414 #endif
415
416         if (c.bus) {
417                 dbus_connection_flush(c.bus);
418                 dbus_connection_close(c.bus);
419                 dbus_connection_unref(c.bus);
420         }
421
422         dbus_error_free(&error);
423         dbus_shutdown();
424
425         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
426 }