chiark / gitweb /
timedated: extra overflow safety check when doing relative time changes
[elogind.git] / src / timedate / timedated.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 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 <dbus/dbus.h>
23
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "systemd/sd-id128.h"
29 #include "systemd/sd-messages.h"
30 #include "util.h"
31 #include "strv.h"
32 #include "dbus-common.h"
33 #include "polkit.h"
34 #include "def.h"
35 #include "hwclock.h"
36 #include "conf-files.h"
37 #include "path-util.h"
38 #include "fileio-label.h"
39 #include "label.h"
40
41 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
42 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
43
44 #define INTERFACE                                                       \
45         " <interface name=\"org.freedesktop.timedate1\">\n"             \
46         "  <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n"  \
47         "  <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n"  \
48         "  <property name=\"CanNTP\" type=\"b\" access=\"read\"/>\n"    \
49         "  <property name=\"NTP\" type=\"b\" access=\"read\"/>\n"       \
50         "  <method name=\"SetTime\">\n"                                 \
51         "   <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n"     \
52         "   <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n"     \
53         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
54         "  </method>\n"                                                 \
55         "  <method name=\"SetTimezone\">\n"                             \
56         "   <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n"     \
57         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
58         "  </method>\n"                                                 \
59         "  <method name=\"SetLocalRTC\">\n"                             \
60         "   <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n"    \
61         "   <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n"   \
62         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
63         "  </method>\n"                                                 \
64         "  <method name=\"SetNTP\">\n"                                  \
65         "   <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n"      \
66         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
67         "  </method>\n"                                                 \
68         " </interface>\n"
69
70 #define INTROSPECTION                                                   \
71         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
72         "<node>\n"                                                      \
73         INTERFACE                                                       \
74         BUS_PROPERTIES_INTERFACE                                        \
75         BUS_INTROSPECTABLE_INTERFACE                                    \
76         BUS_PEER_INTERFACE                                              \
77         "</node>\n"
78
79 #define INTERFACES_LIST                         \
80         BUS_GENERIC_INTERFACES_LIST             \
81         "org.freedesktop.timedate1\0"
82
83 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
84
85 typedef struct TZ {
86         char *zone;
87         bool local_rtc;
88         int can_ntp;
89         int use_ntp;
90 } TZ;
91
92 static TZ tz = {
93         .can_ntp = -1,
94         .use_ntp = -1,
95 };
96
97 static usec_t remain_until;
98
99 static void free_data(void) {
100         free(tz.zone);
101         tz.zone = NULL;
102
103         tz.local_rtc = false;
104 }
105
106 static bool valid_timezone(const char *name) {
107         const char *p;
108         char *t;
109         bool slash = false;
110         int r;
111         struct stat st;
112
113         assert(name);
114
115         if (*name == '/' || *name == 0)
116                 return false;
117
118         for (p = name; *p; p++) {
119                 if (!(*p >= '0' && *p <= '9') &&
120                     !(*p >= 'a' && *p <= 'z') &&
121                     !(*p >= 'A' && *p <= 'Z') &&
122                     !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
123                         return false;
124
125                 if (*p == '/') {
126
127                         if (slash)
128                                 return false;
129
130                         slash = true;
131                 } else
132                         slash = false;
133         }
134
135         if (slash)
136                 return false;
137
138         t = strappend("/usr/share/zoneinfo/", name);
139         if (!t)
140                 return false;
141
142         r = stat(t, &st);
143         free(t);
144
145         if (r < 0)
146                 return false;
147
148         if (!S_ISREG(st.st_mode))
149                 return false;
150
151         return true;
152 }
153
154 static int read_data(void) {
155         int r;
156         _cleanup_free_ char *t = NULL;
157
158         free_data();
159
160         r = readlink_malloc("/etc/localtime", &t);
161         if (r < 0) {
162                 if (r == -EINVAL)
163                         log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
164                 else
165                         log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
166         } else {
167                 const char *e;
168
169                 e = path_startswith(t, "/usr/share/zoneinfo/");
170                 if (!e)
171                         e = path_startswith(t, "../usr/share/zoneinfo/");
172
173                 if (!e)
174                         log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
175                 else {
176                         tz.zone = strdup(e);
177                         if (!tz.zone)
178                                 return log_oom();
179
180                         goto have_timezone;
181                 }
182         }
183
184 have_timezone:
185         if (isempty(tz.zone)) {
186                 free(tz.zone);
187                 tz.zone = NULL;
188         }
189
190         tz.local_rtc = hwclock_is_localtime() > 0;
191
192         return 0;
193 }
194
195 static int write_data_timezone(void) {
196         int r = 0;
197         _cleanup_free_ char *p = NULL;
198
199         if (!tz.zone) {
200                 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
201                         r = -errno;
202
203                 return r;
204         }
205
206         p = strappend("../usr/share/zoneinfo/", tz.zone);
207         if (!p)
208                 return log_oom();
209
210         r = symlink_atomic(p, "/etc/localtime");
211         if (r < 0)
212                 return r;
213
214         return 0;
215 }
216
217 static int write_data_local_rtc(void) {
218         int r;
219         char *s, *w;
220
221         r = read_full_file("/etc/adjtime", &s, NULL);
222         if (r < 0) {
223                 if (r != -ENOENT)
224                         return r;
225
226                 if (!tz.local_rtc)
227                         return 0;
228
229                 w = strdup(NULL_ADJTIME_LOCAL);
230                 if (!w)
231                         return -ENOMEM;
232         } else {
233                 char *p, *e;
234                 size_t a, b;
235
236                 p = strchr(s, '\n');
237                 if (!p) {
238                         free(s);
239                         return -EIO;
240                 }
241
242                 p = strchr(p+1, '\n');
243                 if (!p) {
244                         free(s);
245                         return -EIO;
246                 }
247
248                 p++;
249                 e = strchr(p, '\n');
250                 if (!e) {
251                         free(s);
252                         return -EIO;
253                 }
254
255                 a = p - s;
256                 b = strlen(e);
257
258                 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
259                 if (!w) {
260                         free(s);
261                         return -ENOMEM;
262                 }
263
264                 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
265
266                 if (streq(w, NULL_ADJTIME_UTC)) {
267                         free(w);
268
269                         if (unlink("/etc/adjtime") < 0) {
270                                 if (errno != ENOENT)
271                                         return -errno;
272                         }
273
274                         return 0;
275                 }
276         }
277         label_init("/etc");
278         r = write_one_line_file_atomic_label("/etc/adjtime", w);
279         free(w);
280
281         return r;
282 }
283
284 static char** get_ntp_services(void) {
285         char **r = NULL, **files, **i;
286         int k;
287
288         k = conf_files_list(&files, ".list", NULL,
289                             "/etc/systemd/ntp-units.d",
290                             "/run/systemd/ntp-units.d",
291                             "/usr/local/lib/systemd/ntp-units.d",
292                             "/usr/lib/systemd/ntp-units.d",
293                             NULL);
294         if (k < 0)
295                 return NULL;
296
297         STRV_FOREACH(i, files) {
298                 FILE *f;
299
300                 f = fopen(*i, "re");
301                 if (!f)
302                         continue;
303
304                 for (;;) {
305                         char line[PATH_MAX], *l, **q;
306
307                         if (!fgets(line, sizeof(line), f)) {
308
309                                 if (ferror(f))
310                                         log_error("Failed to read NTP units file: %m");
311
312                                 break;
313                         }
314
315                         l = strstrip(line);
316                         if (l[0] == 0 || l[0] == '#')
317                                 continue;
318
319                         q = strv_append(r, l);
320                         if (!q) {
321                                 log_oom();
322                                 break;
323                         }
324
325                         strv_free(r);
326                         r = q;
327                 }
328
329                 fclose(f);
330         }
331
332         strv_free(files);
333
334         return strv_uniq(r);
335 }
336
337 static int read_ntp(DBusConnection *bus) {
338         DBusMessage *m = NULL, *reply = NULL;
339         DBusError error;
340         int r;
341         char **i, **l;
342
343         assert(bus);
344
345         dbus_error_init(&error);
346
347         l = get_ntp_services();
348         STRV_FOREACH(i, l) {
349                 const char *s;
350
351                 if (m)
352                         dbus_message_unref(m);
353                 m = dbus_message_new_method_call(
354                                 "org.freedesktop.systemd1",
355                                 "/org/freedesktop/systemd1",
356                                 "org.freedesktop.systemd1.Manager",
357                                 "GetUnitFileState");
358                 if (!m) {
359                         r = log_oom();
360                         goto finish;
361                 }
362
363                 if (!dbus_message_append_args(m,
364                                               DBUS_TYPE_STRING, i,
365                                               DBUS_TYPE_INVALID)) {
366                         r = log_oom();
367                         goto finish;
368                 }
369
370                 if (reply)
371                         dbus_message_unref(reply);
372                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
373                 if (!reply) {
374                         if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
375                                 /* This implementation does not exist, try next one */
376                                 dbus_error_free(&error);
377                                 continue;
378                         }
379
380                         log_error("Failed to issue method call: %s", bus_error_message(&error));
381                         r = -EIO;
382                         goto finish;
383                 }
384
385                 if (!dbus_message_get_args(reply, &error,
386                                            DBUS_TYPE_STRING, &s,
387                                            DBUS_TYPE_INVALID)) {
388                         log_error("Failed to parse reply: %s", bus_error_message(&error));
389                         r = -EIO;
390                         goto finish;
391                 }
392
393                 tz.can_ntp = 1;
394                 tz.use_ntp =
395                         streq(s, "enabled") ||
396                         streq(s, "enabled-runtime");
397                 r = 0;
398                 goto finish;
399         }
400
401         /* NTP is not installed. */
402         tz.can_ntp = 0;
403         tz.use_ntp = 0;
404         r = 0;
405
406 finish:
407         if (m)
408                 dbus_message_unref(m);
409
410         if (reply)
411                 dbus_message_unref(reply);
412
413         strv_free(l);
414
415         dbus_error_free(&error);
416
417         return r;
418 }
419
420 static int start_ntp(DBusConnection *bus, DBusError *error) {
421         DBusMessage *m = NULL, *reply = NULL;
422         const char *mode = "replace";
423         char **i, **l;
424         int r;
425
426         assert(bus);
427         assert(error);
428
429         l = get_ntp_services();
430         STRV_FOREACH(i, l) {
431                 if (m)
432                         dbus_message_unref(m);
433                 m = dbus_message_new_method_call(
434                                 "org.freedesktop.systemd1",
435                                 "/org/freedesktop/systemd1",
436                                 "org.freedesktop.systemd1.Manager",
437                                 tz.use_ntp ? "StartUnit" : "StopUnit");
438                 if (!m) {
439                         log_error("Could not allocate message.");
440                         r = -ENOMEM;
441                         goto finish;
442                 }
443
444                 if (!dbus_message_append_args(m,
445                                               DBUS_TYPE_STRING, i,
446                                               DBUS_TYPE_STRING, &mode,
447                                               DBUS_TYPE_INVALID)) {
448                         log_error("Could not append arguments to message.");
449                         r = -ENOMEM;
450                         goto finish;
451                 }
452
453                 if (reply)
454                         dbus_message_unref(reply);
455                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
456                 if (!reply) {
457                         if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
458                             streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
459                             streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
460                                 /* This implementation does not exist, try next one */
461                                 dbus_error_free(error);
462                                 continue;
463                         }
464
465                         log_error("Failed to issue method call: %s", bus_error_message(error));
466                         r = -EIO;
467                         goto finish;
468                 }
469
470                 r = 0;
471                 goto finish;
472         }
473
474         /* No implementaiton available... */
475         r = -ENOENT;
476
477 finish:
478         if (m)
479                 dbus_message_unref(m);
480
481         if (reply)
482                 dbus_message_unref(reply);
483
484         strv_free(l);
485
486         return r;
487 }
488
489 static int enable_ntp(DBusConnection *bus, DBusError *error) {
490         DBusMessage *m = NULL, *reply = NULL;
491         int r;
492         DBusMessageIter iter;
493         dbus_bool_t f = FALSE, t = TRUE;
494         char **i, **l;
495
496         assert(bus);
497         assert(error);
498
499         l = get_ntp_services();
500         STRV_FOREACH(i, l) {
501                 char* k[2];
502
503                 if (m)
504                         dbus_message_unref(m);
505                 m = dbus_message_new_method_call(
506                                 "org.freedesktop.systemd1",
507                                 "/org/freedesktop/systemd1",
508                                 "org.freedesktop.systemd1.Manager",
509                                 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
510                 if (!m) {
511                         log_error("Could not allocate message.");
512                         r = -ENOMEM;
513                         goto finish;
514                 }
515
516                 dbus_message_iter_init_append(m, &iter);
517
518                 k[0] = *i;
519                 k[1] = NULL;
520
521                 r = bus_append_strv_iter(&iter, k);
522                 if (r < 0) {
523                         log_error("Failed to append unit files.");
524                         goto finish;
525                 }
526
527                 /* send runtime bool */
528                 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
529                         log_error("Failed to append runtime boolean.");
530                         r = -ENOMEM;
531                         goto finish;
532                 }
533
534                 if (tz.use_ntp) {
535                         /* send force bool */
536                         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
537                                 log_error("Failed to append force boolean.");
538                                 r = -ENOMEM;
539                                 goto finish;
540                         }
541                 }
542
543                 if (reply)
544                         dbus_message_unref(reply);
545                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
546                 if (!reply) {
547                         if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
548                                 /* This implementation does not exist, try next one */
549                                 dbus_error_free(error);
550                                 continue;
551                         }
552
553                         log_error("Failed to issue method call: %s", bus_error_message(error));
554                         r = -EIO;
555                         goto finish;
556                 }
557
558                 dbus_message_unref(m);
559                 m = dbus_message_new_method_call(
560                                 "org.freedesktop.systemd1",
561                                 "/org/freedesktop/systemd1",
562                                 "org.freedesktop.systemd1.Manager",
563                                 "Reload");
564                 if (!m) {
565                         log_error("Could not allocate message.");
566                         r = -ENOMEM;
567                         goto finish;
568                 }
569
570                 dbus_message_unref(reply);
571                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
572                 if (!reply) {
573                         log_error("Failed to issue method call: %s", bus_error_message(error));
574                         r = -EIO;
575                         goto finish;
576                 }
577
578                 r = 0;
579                 goto finish;
580         }
581
582         r = -ENOENT;
583
584 finish:
585         if (m)
586                 dbus_message_unref(m);
587
588         if (reply)
589                 dbus_message_unref(reply);
590
591         strv_free(l);
592
593         return r;
594 }
595
596 static int property_append_can_ntp(DBusMessageIter *i, const char *property, void *data) {
597         dbus_bool_t db;
598
599         assert(i);
600         assert(property);
601
602         db = tz.can_ntp > 0;
603
604         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
605                 return -ENOMEM;
606
607         return 0;
608 }
609
610 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
611         dbus_bool_t db;
612
613         assert(i);
614         assert(property);
615
616         db = tz.use_ntp > 0;
617
618         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
619                 return -ENOMEM;
620
621         return 0;
622 }
623
624 static const BusProperty bus_timedate_properties[] = {
625         { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone),     true },
626         { "LocalRTC", bus_property_append_bool,   "b", offsetof(TZ, local_rtc) },
627         { "CanNTP",   property_append_can_ntp,    "b", offsetof(TZ, can_ntp)   },
628         { "NTP",      property_append_ntp,        "b", offsetof(TZ, use_ntp)   },
629         { NULL, }
630 };
631
632 static const BusBoundProperties bps[] = {
633         { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
634         { NULL, }
635 };
636
637 static DBusHandlerResult timedate_message_handler(
638                 DBusConnection *connection,
639                 DBusMessage *message,
640                 void *userdata) {
641
642         DBusMessage *reply = NULL, *changed = NULL;
643         DBusError error;
644         int r;
645
646         assert(connection);
647         assert(message);
648
649         dbus_error_init(&error);
650
651         if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
652                 const char *z;
653                 dbus_bool_t interactive;
654
655                 if (!dbus_message_get_args(
656                                     message,
657                                     &error,
658                                     DBUS_TYPE_STRING, &z,
659                                     DBUS_TYPE_BOOLEAN, &interactive,
660                                     DBUS_TYPE_INVALID))
661                         return bus_send_error_reply(connection, message, &error, -EINVAL);
662
663                 if (!valid_timezone(z))
664                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
665
666                 if (!streq_ptr(z, tz.zone)) {
667                         char *t;
668
669                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
670                         if (r < 0)
671                                 return bus_send_error_reply(connection, message, &error, r);
672
673                         t = strdup(z);
674                         if (!t)
675                                 goto oom;
676
677                         free(tz.zone);
678                         tz.zone = t;
679
680                         /* 1. Write new configuration file */
681                         r = write_data_timezone();
682                         if (r < 0) {
683                                 log_error("Failed to set timezone: %s", strerror(-r));
684                                 return bus_send_error_reply(connection, message, NULL, r);
685                         }
686
687                         /* 2. Tell the kernel our time zone */
688                         hwclock_set_timezone(NULL);
689
690                         if (tz.local_rtc) {
691                                 struct timespec ts;
692                                 struct tm *tm;
693
694                                 /* 3. Sync RTC from system clock, with the new delta */
695                                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
696                                 assert_se(tm = localtime(&ts.tv_sec));
697                                 hwclock_set_time(tm);
698                         }
699
700                         log_struct(LOG_INFO,
701                                    MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
702                                    "TIMEZONE=%s", tz.zone,
703                                    "MESSAGE=Changed timezone to '%s'.", tz.zone,
704                                    NULL);
705
706                         changed = bus_properties_changed_new(
707                                         "/org/freedesktop/timedate1",
708                                         "org.freedesktop.timedate1",
709                                         "Timezone\0");
710                         if (!changed)
711                                 goto oom;
712                 }
713
714         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
715                 dbus_bool_t lrtc;
716                 dbus_bool_t fix_system;
717                 dbus_bool_t interactive;
718
719                 if (!dbus_message_get_args(
720                                     message,
721                                     &error,
722                                     DBUS_TYPE_BOOLEAN, &lrtc,
723                                     DBUS_TYPE_BOOLEAN, &fix_system,
724                                     DBUS_TYPE_BOOLEAN, &interactive,
725                                     DBUS_TYPE_INVALID))
726                         return bus_send_error_reply(connection, message, &error, -EINVAL);
727
728                 if (lrtc != tz.local_rtc) {
729                         struct timespec ts;
730
731                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
732                         if (r < 0)
733                                 return bus_send_error_reply(connection, message, &error, r);
734
735                         tz.local_rtc = lrtc;
736
737                         /* 1. Write new configuration file */
738                         r = write_data_local_rtc();
739                         if (r < 0) {
740                                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
741                                 return bus_send_error_reply(connection, message, NULL, r);
742                         }
743
744                         /* 2. Tell the kernel our time zone */
745                         hwclock_set_timezone(NULL);
746
747                         /* 3. Synchronize clocks */
748                         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
749
750                         if (fix_system) {
751                                 struct tm tm;
752
753                                 /* Sync system clock from RTC; first,
754                                  * initialize the timezone fields of
755                                  * struct tm. */
756                                 if (tz.local_rtc)
757                                         tm = *localtime(&ts.tv_sec);
758                                 else
759                                         tm = *gmtime(&ts.tv_sec);
760
761                                 /* Override the main fields of
762                                  * struct tm, but not the timezone
763                                  * fields */
764                                 if (hwclock_get_time(&tm) >= 0) {
765
766                                         /* And set the system clock
767                                          * with this */
768                                         if (tz.local_rtc)
769                                                 ts.tv_sec = mktime(&tm);
770                                         else
771                                                 ts.tv_sec = timegm(&tm);
772
773                                         clock_settime(CLOCK_REALTIME, &ts);
774                                 }
775
776                         } else {
777                                 struct tm *tm;
778
779                                 /* Sync RTC from system clock */
780                                 if (tz.local_rtc)
781                                         tm = localtime(&ts.tv_sec);
782                                 else
783                                         tm = gmtime(&ts.tv_sec);
784
785                                 hwclock_set_time(tm);
786                         }
787
788                         log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
789
790                         changed = bus_properties_changed_new(
791                                         "/org/freedesktop/timedate1",
792                                         "org.freedesktop.timedate1",
793                                         "LocalRTC\0");
794                         if (!changed)
795                                 goto oom;
796                 }
797
798         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
799                 int64_t utc;
800                 dbus_bool_t relative;
801                 dbus_bool_t interactive;
802
803                 if (!dbus_message_get_args(
804                                     message,
805                                     &error,
806                                     DBUS_TYPE_INT64, &utc,
807                                     DBUS_TYPE_BOOLEAN, &relative,
808                                     DBUS_TYPE_BOOLEAN, &interactive,
809                                     DBUS_TYPE_INVALID))
810                         return bus_send_error_reply(connection, message, &error, -EINVAL);
811
812                 if (!relative && utc <= 0)
813                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
814
815                 if (!relative || utc != 0) {
816                         struct timespec ts;
817                         struct tm* tm;
818
819                         if (relative) {
820                                 usec_t n, x;
821
822                                 n = now(CLOCK_REALTIME);
823                                 x = n + utc;
824
825                                 if ((utc > 0 && x < n) ||
826                                     (utc < 0 && x > n))
827                                         return bus_send_error_reply(connection, message, NULL, -EOVERFLOW);
828
829                                 timespec_store(&ts, x);
830                         } else
831                                 timespec_store(&ts, (usec_t) utc);
832
833                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
834                         if (r < 0)
835                                 return bus_send_error_reply(connection, message, &error, r);
836
837                         /* Set system clock */
838                         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
839                                 log_error("Failed to set local time: %m");
840                                 return bus_send_error_reply(connection, message, NULL, -errno);
841                         }
842
843                         /* Sync down to RTC */
844                         if (tz.local_rtc)
845                                 tm = localtime(&ts.tv_sec);
846                         else
847                                 tm = gmtime(&ts.tv_sec);
848
849                         hwclock_set_time(tm);
850
851                         log_struct(LOG_INFO,
852                                    MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
853                                    "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
854                                    "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
855                                    NULL);
856                 }
857         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
858                 dbus_bool_t ntp;
859                 dbus_bool_t interactive;
860
861                 if (!dbus_message_get_args(
862                                     message,
863                                     &error,
864                                     DBUS_TYPE_BOOLEAN, &ntp,
865                                     DBUS_TYPE_BOOLEAN, &interactive,
866                                     DBUS_TYPE_INVALID))
867                         return bus_send_error_reply(connection, message, &error, -EINVAL);
868
869                 if (ntp != !!tz.use_ntp) {
870
871                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
872                         if (r < 0)
873                                 return bus_send_error_reply(connection, message, &error, r);
874
875                         tz.use_ntp = !!ntp;
876
877                         r = enable_ntp(connection, &error);
878                         if (r < 0)
879                                 return bus_send_error_reply(connection, message, &error, r);
880
881                         r = start_ntp(connection, &error);
882                         if (r < 0)
883                                 return bus_send_error_reply(connection, message, &error, r);
884
885                         log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
886
887                         changed = bus_properties_changed_new(
888                                         "/org/freedesktop/timedate1",
889                                         "org.freedesktop.timedate1",
890                                         "NTP\0");
891                         if (!changed)
892                                 goto oom;
893                 }
894
895         } else
896                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
897
898         if (!(reply = dbus_message_new_method_return(message)))
899                 goto oom;
900
901         if (!bus_maybe_send_reply(connection, message, reply))
902                 goto oom;
903
904         dbus_message_unref(reply);
905         reply = NULL;
906
907         if (changed) {
908
909                 if (!dbus_connection_send(connection, changed, NULL))
910                         goto oom;
911
912                 dbus_message_unref(changed);
913         }
914
915         return DBUS_HANDLER_RESULT_HANDLED;
916
917 oom:
918         if (reply)
919                 dbus_message_unref(reply);
920
921         if (changed)
922                 dbus_message_unref(changed);
923
924         dbus_error_free(&error);
925
926         return DBUS_HANDLER_RESULT_NEED_MEMORY;
927 }
928
929 static int connect_bus(DBusConnection **_bus) {
930         static const DBusObjectPathVTable timedate_vtable = {
931                 .message_function = timedate_message_handler
932         };
933         DBusError error;
934         DBusConnection *bus = NULL;
935         int r;
936
937         assert(_bus);
938
939         dbus_error_init(&error);
940
941         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
942         if (!bus) {
943                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
944                 r = -ECONNREFUSED;
945                 goto fail2;
946         }
947
948         dbus_connection_set_exit_on_disconnect(bus, FALSE);
949
950         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
951             !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
952                 r = log_oom();
953                 goto fail;
954         }
955
956         r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
957         if (dbus_error_is_set(&error)) {
958                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
959                 r = -EEXIST;
960                 goto fail;
961         }
962
963         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
964                 log_error("Failed to acquire name.");
965                 r = -EEXIST;
966                 goto fail;
967         }
968
969         if (_bus)
970                 *_bus = bus;
971
972         return 0;
973
974 fail:
975         dbus_connection_close(bus);
976         dbus_connection_unref(bus);
977 fail2:
978         dbus_error_free(&error);
979
980         return r;
981 }
982
983 int main(int argc, char *argv[]) {
984         int r;
985         DBusConnection *bus = NULL;
986         bool exiting = false;
987
988         log_set_target(LOG_TARGET_AUTO);
989         log_parse_environment();
990         log_open();
991
992         umask(0022);
993
994         if (argc == 2 && streq(argv[1], "--introspect")) {
995                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
996                       "<node>\n", stdout);
997                 fputs(timedate_interface, stdout);
998                 fputs("</node>\n", stdout);
999                 return 0;
1000         }
1001
1002         if (argc != 1) {
1003                 log_error("This program takes no arguments.");
1004                 r = -EINVAL;
1005                 goto finish;
1006         }
1007
1008         r = read_data();
1009         if (r < 0) {
1010                 log_error("Failed to read timezone data: %s", strerror(-r));
1011                 goto finish;
1012         }
1013
1014         r = connect_bus(&bus);
1015         if (r < 0)
1016                 goto finish;
1017
1018         r = read_ntp(bus);
1019         if (r < 0) {
1020                 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1021                 goto finish;
1022         }
1023
1024         remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1025         for (;;) {
1026
1027                 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1028                         break;
1029
1030                 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1031                         exiting = true;
1032                         bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1033                 }
1034         }
1035
1036         r = 0;
1037
1038 finish:
1039         free_data();
1040
1041         if (bus) {
1042                 dbus_connection_flush(bus);
1043                 dbus_connection_close(bus);
1044                 dbus_connection_unref(bus);
1045         }
1046
1047         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1048 }