f6543e64cdeb066f0a814cfe972d460d6c98ce2d
[systembsd.git] / src / interfaces / hostnamed / hostnamed.c
1 /*
2 * Copyright (c) 2014 Ian Sutton <ian@kremlin.cc>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <unistd.h>
18 #include <limits.h>
19 #include <signal.h>
20 #include <string.h>
21
22 #include <sys/param.h>
23 #include <sys/sysctl.h>
24 #include <sys/sensors.h>
25 #include <sys/ioctl.h>
26 #include <sys/utsname.h>
27
28 #include <machine/apmvar.h>
29
30 #include <glib/gprintf.h>
31 #include <glib-unix.h>
32 #include <polkit/polkit.h>
33
34 #include "hostnamed-gen.h"
35 #include "hostnamed.h"
36
37 #include "../../polkit-auth.h"
38
39 /* format: {
40 * (1) string to be matched against runtime machine's sysctl output.
41 * can be either the exact string or a substring contained
42 * within sysctl strings. no "guesses" here, a match should
43 * reliably indicate the chassis/icon.
44 *
45 * (2) string describing chassis type divulged by (1).
46 * must be one of "desktop", "laptop", "server",
47 * "tablet", "handset", "vm", "container" or NULL
48 * if only icon string can be ascertained. "vm" refers
49 * to operating systems running on baremetal hypervisors
50 * (hardware virtualization, like XEN) while "container"
51 * refers to OSs running on shared hypervisors like
52 * virtualbox or VMware. consider the distinction carefully
53 * as common virtualization software like KVM may share
54 * characteristics of both "vm" and "container" types.
55 *
56 * (3) string specifying icon to use. follows XDG icon spec.
57 * see http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
58 * for allowed strings.
59 *
60 * (4) chassis precedence bit. TRUE if (2) is defined and
61 * we're certain it is the proper string. FALSE in the
62 * circumstance (2) may be the correct string, unless
63 * a match with this bit set to TRUE overrides it.
64 * if (2) is NULL, this bit is inconsequential.
65 *
66 * (5) icon precedence bit. see previous definition.
67 * } */
68 struct SYSCTL_LOOKUP_TABLE {
69 gchar *match_string;
70 gchar *chassis;
71 gchar *icon;
72 gboolean chassis_precedence;
73 gboolean icon_precedence;
74 };
75
76 GPtrArray *hostnamed_freeable;
77 Hostname1 *hostnamed_interf;
78
79 GMainLoop *hostnamed_loop;
80
81 guint bus_descriptor;
82 gboolean dbus_interface_exported; /* reliable because of gdbus operational guarantees */
83
84 gchar *HOSTNAME, *STATIC_HOSTNAME, *PRETTY_HOSTNAME;
85 gchar *CHASSIS, *ICON;
86 gchar *KERN_NAME, *KERN_RELEASE, *KERN_VERS, *OS_CPENAME;
87
88 /* TODO no specific vm or laptop icon in gnome
89 * NOTE paravirtualization on xen is only available for linuxes right now
90 * dmesg on linux systems reveals xen and virtualization method (HVM or PVM)
91 * but we will worry about those later */
92
93 /* add any sysctl strings that suggest virtualization here */
94 const struct SYSCTL_LOOKUP_TABLE chassis_indicator_table[] =
95 {
96 { "QEMU Virtual CPU", "vm", NULL, FALSE, FALSE }, /* could be QEMU running in userspace or as part of KVM */
97 { "KVM", "vm", "drive-multidisk", FALSE, FALSE },
98 { "SmartDC HVM", "vm", "drive-multidisk", TRUE, TRUE }, /* illumos-joyent kvm */
99 { "VirtualBox", "vm", "drive-multidisk", TRUE, TRUE },
100 { "VMware, Inc.", "vm", "drive-multidisk", TRUE, TRUE },
101 { "VMware Virtual Platform", "vm", "drive-multidisk", TRUE, TRUE },
102 { "Parallels", "vm", "drive-multidisk", TRUE, TRUE }, /* need verification */
103 { "Xen", "vm", "drive-multidisk", FALSE, FALSE }
104 }; /* TODO: chroots, etc. are the actual "containers", add them */
105
106 /* archs to check against when determining if machine is server */
107 const gchar *server_archs[] = {
108 "hppa",
109 "sparc",
110 "sparc64"
111 };
112
113 static const gchar *DEFAULT_DOMAIN = ".home.network";
114 static const gchar *OS_HOSTNAME_PATH = "/etc/myname";
115
116 /* --- begin method/property/dbus signal code --- */
117
118 /* TODO free some strings here */
119 static gboolean
120 on_handle_set_hostname(Hostname1 *hn1_passed_interf,
121 GDBusMethodInvocation *invoc,
122 const gchar *greet,
123 gpointer data) {
124 GVariant *params;
125 gchar *proposed_hostname, *valid_hostname_buf;
126 const gchar *bus_name;
127 gboolean policykit_auth, ret, try_to_set;
128 size_t check_length;
129 check_auth_result is_authed;
130
131 proposed_hostname = NULL;
132 ret = try_to_set = FALSE;
133
134 params = g_dbus_method_invocation_get_parameters(invoc);
135 g_variant_get(params, "(sb)", &proposed_hostname, &policykit_auth);
136 bus_name = g_dbus_method_invocation_get_sender(invoc);
137
138 /* verify caller has correct permissions via polkit */
139 is_authed = polkit_try_auth(bus_name, "org.freedesktop.hostname1.set-hostname", policykit_auth);
140
141 switch(is_authed) {
142
143 case AUTHORIZED_NATIVELY:
144 case AUTHORIZED_BY_PROMPT:
145 try_to_set = TRUE;
146 break;
147
148 case UNAUTHORIZED_NATIVELY:
149 case UNAUTHORIZED_FAILED_PROMPT:
150 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EACCES", "Insufficient permissions to set hostname.");
151 break;
152
153 case ERROR_BADBUS:
154 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided bus name is invalid.");
155 break;
156
157 case ERROR_BADACTION:
158 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided action ID is invalid.");
159 break;
160
161 case ERROR_GENERIC:
162 default:
163 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set hostname for unknown reason.");
164 break;
165 }
166
167 /* verify passed hostname's validity */
168 if(try_to_set && proposed_hostname && (valid_hostname_buf = g_hostname_to_ascii(proposed_hostname))) {
169
170 check_length = strnlen(valid_hostname_buf, MAXHOSTNAMELEN + 1);
171
172 if(check_length > MAXHOSTNAMELEN) {
173
174 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ENAMETOOLONG", "Hostname string exceeded maximum length.");
175 g_free(valid_hostname_buf);
176
177 } else if(sethostname(proposed_hostname, check_length)) {
178
179 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set hostname for unknown reason.");
180 g_free(valid_hostname_buf);
181
182 } else {
183
184 HOSTNAME = valid_hostname_buf;
185 hostname1_set_hostname(hn1_passed_interf, HOSTNAME);
186 g_ptr_array_add(hostnamed_freeable, valid_hostname_buf);
187 ret = TRUE;
188 hostname1_complete_set_hostname(hn1_passed_interf, invoc);
189 }
190 }
191
192 return ret;
193 }
194
195 static gboolean
196 on_handle_set_static_hostname(Hostname1 *hn1_passed_interf,
197 GDBusMethodInvocation *invoc,
198 const gchar *greet,
199 gpointer data) {
200
201 GVariant *params;
202 gchar *proposed_static_hostname, *valid_static_hostname_buf;
203 const gchar *bus_name;
204 gboolean policykit_auth, ret, try_to_set;
205 size_t check_length;
206 check_auth_result is_authed;
207
208 proposed_static_hostname = NULL;
209 ret = try_to_set = FALSE;
210
211 params = g_dbus_method_invocation_get_parameters(invoc);
212 g_variant_get(params, "(sb)", &proposed_static_hostname, &policykit_auth);
213 bus_name = g_dbus_method_invocation_get_sender(invoc);
214
215 /* verify caller has correct permissions via polkit */
216 is_authed = polkit_try_auth(bus_name, "org.freedesktop.hostname1.set-static-hostname", policykit_auth);
217
218 switch(is_authed) {
219
220 case AUTHORIZED_NATIVELY:
221 case AUTHORIZED_BY_PROMPT:
222 try_to_set = TRUE;
223 break;
224
225 case UNAUTHORIZED_NATIVELY:
226 case UNAUTHORIZED_FAILED_PROMPT:
227 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EACCES", "Insufficient permissions to set static hostname.");
228 break;
229
230 case ERROR_BADBUS:
231 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided bus name is invalid.");
232 break;
233
234 case ERROR_BADACTION:
235 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided action ID is invalid.");
236 break;
237
238 case ERROR_GENERIC:
239 default:
240 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set static hostname for unknown reason.");
241 break;
242 }
243
244 /* verify passed hostname's validity */
245 if(try_to_set && proposed_static_hostname && (valid_static_hostname_buf = g_hostname_to_ascii(proposed_static_hostname))) {
246
247 check_length = strnlen(valid_static_hostname_buf, MAXHOSTNAMELEN + 1);
248
249 if(check_length > MAXHOSTNAMELEN) {
250
251 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ENAMETOOLONG", "Static hostname string exceeded maximum length.");
252 g_free(valid_static_hostname_buf);
253
254 } else if(!(STATIC_HOSTNAME = valid_static_hostname_buf)) {
255
256 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set static hostname for unknown reason.");
257 g_free(valid_static_hostname_buf);
258
259 } else {
260
261 g_strdelimit(STATIC_HOSTNAME, " ", '-');
262 hostname1_set_static_hostname(hn1_passed_interf, STATIC_HOSTNAME);
263 g_ptr_array_add(hostnamed_freeable, valid_static_hostname_buf);
264 ret = (!sethostname(valid_static_hostname_buf, MAXHOSTNAMELEN)) ? TRUE : FALSE; /* TODO set /etc/myname, guarantee domain or substitue .home.network" */
265 hostname1_complete_set_static_hostname(hn1_passed_interf, invoc);
266 }
267 }
268
269 return ret;
270 }
271
272 static gboolean
273 on_handle_set_pretty_hostname(Hostname1 *hn1_passed_interf,
274 GDBusMethodInvocation *invoc,
275 const gchar *greet,
276 gpointer data) {
277
278 GVariant *params;
279 gchar *proposed_pretty_hostname, *valid_pretty_hostname_buf, *computed_static_hostname;
280 const gchar *bus_name;
281 gboolean policykit_auth, ret, try_to_set;
282 size_t check_length;
283 check_auth_result is_authed;
284 GKeyFile *config;
285
286 config = g_key_file_new();
287 proposed_pretty_hostname = NULL;
288 ret = try_to_set = FALSE;
289
290 params = g_dbus_method_invocation_get_parameters(invoc);
291 g_variant_get(params, "(sb)", &proposed_pretty_hostname, &policykit_auth);
292 bus_name = g_dbus_method_invocation_get_sender(invoc);
293
294 /* verify caller has correct permissions via polkit */
295 is_authed = polkit_try_auth(bus_name, "org.freedesktop.hostname1.set-pretty-hostname", policykit_auth);
296
297 switch(is_authed) {
298
299 case AUTHORIZED_NATIVELY:
300 case AUTHORIZED_BY_PROMPT:
301 try_to_set = TRUE;
302 break;
303
304 case UNAUTHORIZED_NATIVELY:
305 case UNAUTHORIZED_FAILED_PROMPT:
306 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EACCES", "Insufficient permissions to set pretty hostname.");
307 break;
308
309 case ERROR_BADBUS:
310 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided bus name is invalid.");
311 break;
312
313 case ERROR_BADACTION:
314 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided action ID is invalid.");
315 break;
316
317 case ERROR_GENERIC:
318 default:
319 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set pretty hostname for unknown reason.");
320 break;
321 }
322
323 /* verify passed hostname's validity */
324 if(try_to_set && proposed_pretty_hostname && (valid_pretty_hostname_buf = g_locale_to_utf8(proposed_pretty_hostname, -1, 0, 0, NULL))) {
325
326 check_length = strnlen(valid_pretty_hostname_buf, MAXHOSTNAMELEN + 1);
327
328 if(check_length > MAXHOSTNAMELEN) {
329
330 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ENAMETOOLONG", "Static hostname string exceeded maximum length.");
331 g_free(valid_pretty_hostname_buf);
332
333 } else if(!(PRETTY_HOSTNAME = valid_pretty_hostname_buf)) {
334
335 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set pretty hostname for unknown reason.");
336 g_free(valid_pretty_hostname_buf);
337
338 } else {
339
340 hostname1_set_pretty_hostname(hn1_passed_interf, PRETTY_HOSTNAME);
341 g_ptr_array_add(hostnamed_freeable, valid_pretty_hostname_buf);
342 hostname1_complete_set_pretty_hostname(hn1_passed_interf, invoc);
343 ret = TRUE;
344
345 if(g_key_file_load_from_file(config, "/etc/machine-info", G_KEY_FILE_NONE, NULL)) {
346
347 g_key_file_set_string(config, "hostnamed", "PRETTY_HOSTNAME", valid_pretty_hostname_buf);
348
349 /* if((computed_static_hostname = g_hostname_to_ascii(PRETTY_HOSTNAME))) {
350
351 g_strdelimit(computed_static_hostname, " ", '-');
352 hostname1_set_static_hostname(hn1_passed_interf, computed_static_hostname);
353 STATIC_HOSTNAME = computed_static_hostname;
354 g_ptr_array_add(hostnamed_freeable, computed_static_hostname);
355 g_key_file_set_string(config, "hostnamed", "StaticHostname", computed_static_hostname);
356
357 } */
358 }
359 }
360 }
361
362 g_key_file_save_to_file(config, "/etc/machine-info", NULL);
363 g_key_file_unref(config);
364
365 return ret;
366 }
367
368 static gboolean
369 on_handle_set_chassis(Hostname1 *hn1_passed_interf,
370 GDBusMethodInvocation *invoc,
371 const gchar *greet,
372 gpointer data) {
373
374 GVariant *params;
375 gchar *proposed_chassis_name, *valid_chassis_name_buf;
376 const gchar *bus_name;
377 gboolean policykit_auth, ret, try_to_set;
378 check_auth_result is_authed;
379 GKeyFile *config;
380
381 config = g_key_file_new();
382 proposed_chassis_name = NULL;
383 ret = try_to_set = FALSE;
384 valid_chassis_name_buf = (gchar *)g_malloc0(8192);
385
386 params = g_dbus_method_invocation_get_parameters(invoc);
387 g_variant_get(params, "(sb)", &proposed_chassis_name, &policykit_auth);
388 bus_name = g_dbus_method_invocation_get_sender(invoc);
389
390 g_strlcpy(valid_chassis_name_buf, proposed_chassis_name, (gsize)64);
391
392 /* verify caller has correct permissions via polkit */
393 is_authed = polkit_try_auth(bus_name, "org.freedesktop.hostname1.set-chassis", policykit_auth);
394
395 switch(is_authed) {
396
397 case AUTHORIZED_NATIVELY:
398 case AUTHORIZED_BY_PROMPT:
399 try_to_set = TRUE;
400 break;
401
402 case UNAUTHORIZED_NATIVELY:
403 case UNAUTHORIZED_FAILED_PROMPT:
404 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EACCES", "Insufficient permissions to set chassis type.");
405 break;
406
407 case ERROR_BADBUS:
408 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided bus name is invalid.");
409 break;
410
411 case ERROR_BADACTION:
412 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided action ID is invalid.");
413 break;
414
415 case ERROR_GENERIC:
416 default:
417 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set chassis type for unknown reason.");
418 break;
419 }
420
421 /* verify passed chassis type's validity */
422 if(try_to_set && proposed_chassis_name) {
423
424 if(!is_valid_chassis_type(proposed_chassis_name)) {
425
426 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Chassis type must be 'desktop', 'laptop', 'server', 'tablet', 'handset', 'vm', or 'container'.");
427 g_free(valid_chassis_name_buf);
428
429 } else if(!(CHASSIS = valid_chassis_name_buf)) {
430
431 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set chassis type for unknown reason.");
432 g_free(valid_chassis_name_buf);
433
434 } else {
435
436 hostname1_set_chassis(hn1_passed_interf, CHASSIS);
437 g_ptr_array_add(hostnamed_freeable, valid_chassis_name_buf);
438 hostname1_complete_set_chassis(hn1_passed_interf, invoc);
439
440 if(g_key_file_load_from_file(config, "/etc/machine-info", G_KEY_FILE_NONE, NULL)) {
441
442 ret = TRUE;
443 g_key_file_set_string(config, "hostnamed", "ChassisType", valid_chassis_name_buf);
444
445 }
446 }
447 }
448
449 g_key_file_save_to_file(config, "/etc/machine-info", NULL);
450 g_key_file_unref(config);
451
452 return ret;
453 }
454
455 static gboolean
456 on_handle_set_icon_name(Hostname1 *hn1_passed_interf,
457 GDBusMethodInvocation *invoc,
458 const gchar *greet,
459 gpointer data) {
460
461 GVariant *params;
462 gchar *proposed_icon_name, *valid_icon_name_buf;
463 const gchar *bus_name;
464 gboolean policykit_auth, ret, try_to_set;
465 check_auth_result is_authed;
466 GKeyFile *config;
467
468 config = g_key_file_new();
469 proposed_icon_name = NULL;
470 ret = try_to_set = FALSE;
471
472 params = g_dbus_method_invocation_get_parameters(invoc);
473 g_variant_get(params, "(sb)", &proposed_icon_name, &policykit_auth);
474 bus_name = g_dbus_method_invocation_get_sender(invoc);
475
476 /* verify caller has correct permissions via polkit */
477 is_authed = polkit_try_auth(bus_name, "org.freedesktop.hostname1.set-icon-name", policykit_auth);
478
479 switch(is_authed) {
480
481 case AUTHORIZED_NATIVELY:
482 case AUTHORIZED_BY_PROMPT:
483 try_to_set = TRUE;
484 break;
485
486 case UNAUTHORIZED_NATIVELY:
487 case UNAUTHORIZED_FAILED_PROMPT:
488 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EACCES", "Insufficient permissions to set icon name.");
489 break;
490
491 case ERROR_BADBUS:
492 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided bus name is invalid.");
493 break;
494
495 case ERROR_BADACTION:
496 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.EFAULT", "Provided action ID is invalid.");
497 break;
498
499 case ERROR_GENERIC:
500 default:
501 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set icon name for unknown reason.");
502 break;
503 }
504
505 /* verify passed chassis type's validity */
506 if(try_to_set && proposed_icon_name) {
507
508 g_strlcpy(valid_icon_name_buf, proposed_icon_name, (gsize)64);
509
510 if(!(ICON = valid_icon_name_buf)) {
511
512 g_dbus_method_invocation_return_dbus_error(invoc, "org.freedesktop.hostname1.Error.ECANCELED", "Failed to set icon name for unknown reason.");
513 g_free(valid_icon_name_buf);
514
515 } else {
516
517 hostname1_set_icon_name(hn1_passed_interf, ICON);
518 g_ptr_array_add(hostnamed_freeable, valid_icon_name_buf);
519 hostname1_complete_set_icon_name(hn1_passed_interf, invoc);
520
521 if(g_key_file_load_from_file(config, "/etc/machine-info", G_KEY_FILE_NONE, NULL)) {
522
523 ret = TRUE;
524 g_key_file_set_string(config, "hostnamed", "IconName", valid_icon_name_buf);
525
526 }
527 }
528 }
529
530 g_key_file_save_to_file(config, "/etc/machine-info", NULL);
531 g_key_file_unref(config);
532
533 return ret;
534 }
535
536 /* note: all hostnamed/hostname1's properties are read-only,
537 * and do not need set_ functions, gdbus-codegen realized
538 * this from the XML and handled the to-be error of trying
539 * to set a read-only property's value
540 */
541
542 const gchar *
543 our_get_hostname() {
544
545 gchar *hostname_buf;
546 hostname_buf = (gchar *)g_malloc0(MAXHOSTNAMELEN);
547
548 if(gethostname(hostname_buf, MAXHOSTNAMELEN))
549 return "localhost.home.network"; /* TODO bomb out here probably */
550
551 else if(!g_strcmp0(HOSTNAME, hostname_buf)) {
552
553 g_free(hostname_buf);
554 return HOSTNAME;
555 }
556
557 g_ptr_array_add(hostnamed_freeable, hostname_buf);
558 HOSTNAME = hostname_buf;
559 hostname1_set_hostname(hostnamed_interf, HOSTNAME);
560
561 return HOSTNAME;
562 }
563
564 const gchar *
565 our_get_static_hostname() {
566
567 if(STATIC_HOSTNAME && g_strcmp0(STATIC_HOSTNAME, ""))
568 return STATIC_HOSTNAME;
569 else if(HOSTNAME)
570 return HOSTNAME;
571
572 return "localhost.home.network";
573 }
574
575 const gchar *
576 our_get_pretty_hostname() {
577
578 if(PRETTY_HOSTNAME)
579 return PRETTY_HOSTNAME;
580
581 return "";
582 }
583
584 const gchar *
585 our_get_chassis() {
586
587 if(CHASSIS)
588 return CHASSIS;
589
590 return "desktop"; /* this leads to the most generic beheivor in the unlikely case its returned */
591 }
592
593 const gchar *
594 our_get_icon_name() {
595
596 if(ICON)
597 return ICON;
598
599 return "";
600 }
601
602 const gchar *
603 our_get_kernel_name() {
604
605 if(KERN_NAME)
606 return KERN_NAME;
607
608 return "";
609 }
610
611 const gchar *
612 our_get_kernel_version() {
613
614 if(KERN_VERS)
615 return KERN_VERS;
616
617 return "";
618 }
619
620 const gchar *
621 our_get_kernel_release() {
622
623 if(KERN_RELEASE)
624 return KERN_RELEASE;
625
626 return "";
627 }
628
629 const gchar *
630 our_get_os_cpename() {
631
632 /* XXX needs to parse /etc/os-release (fallback to /usr/local/lib/os-release) */
633 return "";
634 }
635
636 const gchar *
637 our_get_os_pretty_name() {
638
639 return "OpenBSD";
640 }
641
642 /* --- end method/property/dbus signal code, begin bus/name handlers --- */
643
644 static void hostnamed_on_bus_acquired(GDBusConnection *conn,
645 const gchar *name,
646 gpointer user_data) {
647
648 g_printf("got bus/name, exporting %s's interface...\n", name);
649
650 hostnamed_interf = hostname1_skeleton_new();
651
652 /* attach function pointers to generated struct's method handlers */
653 g_signal_connect(hostnamed_interf, "handle-set-hostname", G_CALLBACK(on_handle_set_hostname), NULL);
654 g_signal_connect(hostnamed_interf, "handle-set-static-hostname", G_CALLBACK(on_handle_set_static_hostname), NULL);
655 g_signal_connect(hostnamed_interf, "handle-set-pretty-hostname", G_CALLBACK(on_handle_set_pretty_hostname), NULL);
656 g_signal_connect(hostnamed_interf, "handle-set-chassis", G_CALLBACK(on_handle_set_chassis), NULL);
657 g_signal_connect(hostnamed_interf, "handle-set-icon-name", G_CALLBACK(on_handle_set_icon_name), NULL);
658
659 /* set our properties before export */
660 hostname1_set_hostname(hostnamed_interf, our_get_hostname());
661 hostname1_set_static_hostname(hostnamed_interf, our_get_static_hostname());
662 hostname1_set_pretty_hostname(hostnamed_interf, our_get_pretty_hostname());
663 hostname1_set_chassis(hostnamed_interf, our_get_chassis());
664 hostname1_set_icon_name(hostnamed_interf, our_get_icon_name());
665 hostname1_set_kernel_name(hostnamed_interf, our_get_kernel_name());
666 hostname1_set_kernel_version(hostnamed_interf, our_get_kernel_version());
667 hostname1_set_kernel_release(hostnamed_interf, our_get_kernel_release());
668 hostname1_set_operating_system_cpename(hostnamed_interf, our_get_os_cpename());
669 hostname1_set_operating_system_pretty_name(hostnamed_interf, our_get_os_pretty_name());
670
671 if(!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(hostnamed_interf),
672 conn,
673 "/org/freedesktop/hostname1",
674 NULL)) {
675
676 g_printf("failed to export %s's interface!\n", name); /* unusual edge case, TODO check errno */
677 hostnamed_mem_clean();
678
679 } else {
680
681 dbus_interface_exported = TRUE;
682 g_printf("exported %s's interface on the system bus...\n", name);
683 }
684 }
685
686 static void hostnamed_on_name_acquired(GDBusConnection *conn,
687 const gchar *name,
688 gpointer user_data) {
689
690 g_printf("success!\n");
691 }
692
693 static void hostnamed_on_name_lost(GDBusConnection *conn,
694 const gchar *name,
695 gpointer user_data) {
696
697 if(!conn) {
698
699 g_printf("failed to connect to the system bus while trying to acquire name '%s': either dbus-daemon isn't running or we don't have permission to push names and/or their interfaces to it.\n", name);
700 hostnamed_mem_clean();
701 }
702
703 g_printf("lost name %s, exiting...\n", name);
704
705 hostnamed_mem_clean();
706 }
707
708 /* --- end bus/name handlers, begin misc unix functions --- */
709
710 /* safe call to clean and then exit
711 * this stops our GMainLoop safely before letting main() return */
712 void hostnamed_mem_clean() {
713
714 g_printf("exiting...\n");
715
716 if(dbus_interface_exported)
717 g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(hostnamed_interf));
718
719 if(g_main_loop_is_running(hostnamed_loop))
720 g_main_loop_quit(hostnamed_loop);
721
722 }
723
724 /* wrapper for glib's unix signal handling; called only once if terminatating signal is raised against us */
725 gboolean unix_sig_terminate_handler(gpointer data) {
726
727 g_printf("caught SIGINT/HUP/TERM, exiting\n");
728
729 hostnamed_mem_clean();
730 return G_SOURCE_REMOVE;
731 }
732
733 void set_signal_handlers() {
734
735 /* we don't care about its descriptor, we never need to unregister these */
736 g_unix_signal_add(SIGINT, unix_sig_terminate_handler, NULL);
737 g_unix_signal_add(SIGHUP, unix_sig_terminate_handler, NULL);
738 g_unix_signal_add(SIGTERM, unix_sig_terminate_handler, NULL);
739
740 /* TODO: the "only once" guarantee only counts towards specific signals.
741 * make sure calling a SIGINT and SIGHUP doesn't cause term_handler()
742 * to be called twice */
743 }
744
745 int main() {
746
747 hostnamed_freeable = g_ptr_array_new();
748
749 /* TODO: check for valid, writable config at init. if no, complain to `make install` */
750
751 get_bsd_hostname("adsf"); /* TODO KILL ME */
752
753 CHASSIS = ICON = OS_CPENAME = 0;
754 KERN_NAME = KERN_RELEASE = KERN_VERS = 0;
755 HOSTNAME = STATIC_HOSTNAME = PRETTY_HOSTNAME = NULL;
756
757 set_signal_handlers();
758
759 if(!determine_chassis_and_icon() || !set_uname_properties() || !set_names())
760 return 1;
761
762 hostnamed_loop = g_main_loop_new(NULL, TRUE);
763
764 bus_descriptor = g_bus_own_name(G_BUS_TYPE_SYSTEM,
765 "org.freedesktop.hostname1",
766 G_BUS_NAME_OWNER_FLAGS_NONE,
767 hostnamed_on_bus_acquired,
768 hostnamed_on_name_acquired,
769 hostnamed_on_name_lost,
770 NULL,
771 NULL);
772
773 g_main_loop_run(hostnamed_loop);
774 /* runs until single g_main_loop_quit() call is raised inside <interface>_mem_clean() */
775 g_main_loop_unref(hostnamed_loop);
776
777 /* guaranteed unownable */
778 g_bus_unown_name(bus_descriptor);
779
780 /* at this point no operations can occur with our data, it is safe to free it + its container */
781 g_ptr_array_free(hostnamed_freeable, TRUE);
782
783 return 0;
784 }
785
786 gboolean set_names() {
787
788 /* (1) set up */
789 gchar *hostname_buf, *static_hostname_buf, *pretty_hostname_buf;
790 GKeyFile *config;
791 size_t hostname_divider;
792
793 hostname_buf = (gchar*) g_malloc0(MAXHOSTNAMELEN);
794 static_hostname_buf = (gchar*) g_malloc0(4096);
795 pretty_hostname_buf = (gchar*) g_malloc0(4096);
796
797 config = g_key_file_new();
798
799 g_ptr_array_add(hostnamed_freeable, hostname_buf);
800 g_ptr_array_add(hostnamed_freeable, static_hostname_buf);
801 g_ptr_array_add(hostnamed_freeable, pretty_hostname_buf);
802
803 /* (2) set HOSTNAME */
804 if(gethostname(hostname_buf, MAXHOSTNAMELEN) || !g_strcmp0(hostname_buf, ""))
805 HOSTNAME = "localhost";
806
807 HOSTNAME = hostname_buf;
808
809 /* this bit gets you the /etc/myname style hostname
810 hostname_divider = strcspn(hostname_buf, ".");
811 strncpy(ret, hostname_buf, hostname_divider); */
812
813 /* (3) set PRETTY_HOSTNAME */
814 if(g_key_file_load_from_file(config, "/etc/machine-info", G_KEY_FILE_NONE, NULL)
815 && (pretty_hostname_buf = g_key_file_get_value(config, "hostnamed", "PRETTY_HOSTNAME", NULL)))
816 PRETTY_HOSTNAME = pretty_hostname_buf;
817 else
818 PRETTY_HOSTNAME = "";
819
820
821 /* (4) set STATIC_HOSTNAME */
822 if((static_hostname_buf = g_key_file_get_value(config, "hostnamed", "STATIC_HOSTNAME", NULL)))
823 STATIC_HOSTNAME = static_hostname_buf;
824
825 else
826 STATIC_HOSTNAME = "";
827
828 if(config)
829 g_key_file_unref(config);
830
831 return (HOSTNAME && STATIC_HOSTNAME && PRETTY_HOSTNAME) ? TRUE : FALSE;
832 }
833
834 gboolean set_uname_properties() {
835
836 struct utsname un;
837
838 if(-1 == uname(&un))
839 return FALSE;
840
841 KERN_NAME = (gchar*)g_malloc0(sizeof(un.sysname));
842 g_ptr_array_add(hostnamed_freeable, KERN_NAME);
843 g_strlcpy(KERN_NAME, un.sysname, sizeof(un.sysname));
844
845 KERN_RELEASE = (gchar*)g_malloc0(sizeof(un.release));
846 g_ptr_array_add(hostnamed_freeable, KERN_RELEASE);
847 g_strlcpy(KERN_RELEASE, un.release, sizeof(un.release));
848
849 KERN_VERS = (gchar*)g_malloc0(sizeof(un.version));
850 g_ptr_array_add(hostnamed_freeable, KERN_VERS);
851 g_strlcpy(KERN_VERS, un.version, sizeof(un.version));
852
853 return TRUE;
854 }
855
856 gboolean determine_chassis_and_icon() {
857
858 const size_t bufsize = 4096;
859
860 char *hwproduct, *hwmodel, *hwvendor, *hwmachine;
861 size_t hwproduct_size, hwmodel_size, hwvendor_size, hwmachine_size;
862 int hwproduct_name[2], hwmodel_name[2], hwvendor_name[2], hwmachine_name[2];
863 unsigned int i;
864 gboolean UNSURE_CHASSIS_FLAG, UNSURE_ICON_FLAG;
865
866 hwproduct_size = hwmodel_size = hwvendor_size = hwmachine_size = bufsize;
867 UNSURE_CHASSIS_FLAG = UNSURE_ICON_FLAG = FALSE;
868 i = 0;
869
870 hwproduct = (char*)g_malloc0(4096);
871 hwmodel = (char*)g_malloc0(4096);
872 hwvendor = (char*)g_malloc0(4096);
873 hwmachine = (char*)g_malloc0(4096);
874
875 g_ptr_array_add(hostnamed_freeable, hwproduct);
876 g_ptr_array_add(hostnamed_freeable, hwmodel);
877 g_ptr_array_add(hostnamed_freeable, hwvendor);
878 g_ptr_array_add(hostnamed_freeable, hwmachine);
879
880 hwproduct_name[0] = CTL_HW;
881 hwproduct_name[1] = HW_PRODUCT;
882
883 hwmodel_name[0] = CTL_HW;
884 hwmodel_name[1] = HW_MODEL;
885
886 hwvendor_name[0] = CTL_HW;
887 hwvendor_name[1] = HW_VENDOR;
888
889 hwmachine_name[0] = CTL_HW;
890 hwmachine_name[1] = HW_MACHINE;
891
892 /* pass NULL buffer to check size first, then pass hw to be filled according to freshly-set hw_size */
893 if(-1 == sysctl(hwproduct_name, 2, NULL, &hwproduct_size, NULL, 0) || -1 == sysctl(hwproduct_name, 2, hwproduct, &hwproduct_size, NULL, 0))
894 return FALSE;
895
896 if(-1 == sysctl(hwmodel_name, 2, NULL, &hwmodel_size, NULL, 0) || -1 == sysctl(hwmodel_name, 2, hwmodel, &hwmodel_size, NULL, 0))
897 return FALSE;
898
899 if(-1 == sysctl(hwvendor_name, 2, NULL, &hwvendor_size, NULL, 0) || -1 == sysctl(hwvendor_name, 2, hwvendor, &hwvendor_size, NULL, 0))
900 return FALSE;
901
902 if(-1 == sysctl(hwmachine_name, 2, NULL, &hwmachine_size, NULL, 0) || -1 == sysctl(hwmachine_name, 2, hwmachine, &hwmachine_size, NULL, 0))
903 return FALSE;
904
905 /* TODO: test for laptop, if not, dmidecode for desktop vs. server
906 * probably move this code to vm test func and set a global after running it early, once */
907
908 for(; i < G_N_ELEMENTS(chassis_indicator_table); i++) {
909 if(strcasestr(hwproduct, chassis_indicator_table[i].match_string)
910 || strcasestr(hwmodel, chassis_indicator_table[i].match_string)
911 || strcasestr(hwvendor, chassis_indicator_table[i].match_string)) {
912
913 if(!UNSURE_CHASSIS_FLAG && chassis_indicator_table[i].chassis) {
914
915 UNSURE_CHASSIS_FLAG = chassis_indicator_table[i].chassis_precedence;
916 CHASSIS = chassis_indicator_table[i].chassis;
917 }
918
919 if(!UNSURE_ICON_FLAG && chassis_indicator_table[i].icon) {
920
921 UNSURE_ICON_FLAG = chassis_indicator_table[i].icon_precedence;
922 ICON = chassis_indicator_table[i].icon;
923 }
924 }
925 }
926
927 if(up_native_is_laptop()) {
928
929 if(!CHASSIS)
930 CHASSIS = "laptop";
931 if(!ICON)
932 ICON = "input-touchpad"; /* TODO pull an icon package that actually has the icons we're looking for */
933
934 } else if(is_server(hwmachine)) {
935
936 if(!CHASSIS)
937 CHASSIS = "server";
938 if(!ICON)
939 ICON = "uninterruptible-power-supply";
940
941 } else if(!CHASSIS || !ICON) {
942
943 if(!CHASSIS)
944 CHASSIS = "desktop";
945 if(!ICON)
946 ICON = "computer";
947 }
948
949 return (CHASSIS && ICON);
950 }
951
952 gboolean is_server(gchar *arch) {
953
954 unsigned int i;
955
956 for(; i < G_N_ELEMENTS(server_archs); i++)
957 if(strcasestr(arch, server_archs[i]))
958 return TRUE;
959
960 return FALSE;
961 }
962
963 gboolean up_native_is_laptop() {
964
965 struct apm_power_info bstate;
966 struct sensordev acpiac;
967
968 if (up_native_get_sensordev("acpiac0", &acpiac))
969 return TRUE;
970
971 if (-1 == ioctl(up_apm_get_fd(), APM_IOC_GETPOWER, &bstate))
972 g_error("ioctl on apm fd failed : %s", g_strerror(errno));
973
974 return bstate.ac_state != APM_AC_UNKNOWN;
975 }
976
977 int up_apm_get_fd() {
978
979 static int apm_fd = 0;
980
981 if(apm_fd == 0) {
982
983 g_debug("apm_fd is not initialized yet, opening");
984
985 /* open /dev/apm */
986 if((apm_fd = open("/dev/apm", O_RDONLY)) == -1) {
987 if(errno != ENXIO && errno != ENOENT)
988 g_error("cannot open device file");
989 }
990 }
991
992 return apm_fd;
993 }
994
995 gboolean up_native_get_sensordev(const char * id, struct sensordev * snsrdev) {
996
997 int devn;
998 size_t sdlen = sizeof(struct sensordev);
999 int mib[] = {CTL_HW, HW_SENSORS, 0, 0 ,0};
1000
1001 for (devn = 0 ; ; devn++) {
1002 mib[2] = devn;
1003 if(sysctl(mib, 3, snsrdev, &sdlen, NULL, 0) == -1) {
1004 if(errno == ENXIO)
1005 continue;
1006 if(errno == ENOENT)
1007 break;
1008 }
1009
1010 if (!strcmp(snsrdev->xname, id))
1011 return TRUE;
1012 }
1013
1014 return FALSE;
1015 }
1016
1017 static gboolean is_valid_chassis_type(gchar *test) {
1018
1019 if(!g_strcmp0(test, "desktop") ||
1020 !g_strcmp0(test, "laptop") ||
1021 !g_strcmp0(test, "server") ||
1022 !g_strcmp0(test, "tablet") ||
1023 !g_strcmp0(test, "handset") ||
1024 !g_strcmp0(test, "vm") ||
1025 !g_strcmp0(test, "container") ||
1026 !g_strcmp0(test, ""))
1027 return TRUE;
1028
1029 return FALSE;
1030 }
1031
1032 /* returns a proper, bsd-style FQDN hostname safe to write to /etc/myname
1033 * if proposed_hostname does not contain an appended domain, the one in /etc/myname is substituted.
1034 * failing that, DEFAULT_DOMAIN is used. NULL if proposed_hostname is invalid
1035 * returns string that should be g_free()'d, or NULL if passed an invalid hostname */
1036 static gchar *get_bsd_hostname(gchar *proposed_hostname) {
1037
1038 gchar *bsd_hostname, *ascii_translated_hostname, **myname_contents, *passed_domain, *temp_buf;
1039 size_t domain_len, check_len;
1040 gboolean read_result;
1041
1042 g_strdelimit(proposed_hostname, "`~!@#$%^&*()_=+[{]}|:;'\"\\", '-');
1043
1044 ascii_translated_hostname = g_hostname_to_ascii(proposed_hostname);
1045 check_len = strnlen(ascii_translated_hostname, MAXHOSTNAMELEN);
1046
1047 if(!ascii_translated_hostname || !check_len || check_len > MAXHOSTNAMELEN || !g_strcmp0("", ascii_translated_hostname) || !g_strcmp0(".", ascii_translated_hostname)) {
1048
1049 bsd_hostname = NULL;
1050 passed_domain = NULL;
1051 myname_contents = NULL;
1052
1053 } else if((passed_domain = has_domain(ascii_translated_hostname))) {
1054
1055 bsd_hostname = (gchar *) g_malloc0(MAXHOSTNAMELEN);
1056 g_strlcpy(bsd_hostname, ascii_translated_hostname, MAXHOSTNAMELEN);
1057
1058 passed_domain = NULL;
1059 myname_contents = NULL;
1060
1061 } else {
1062
1063 myname_contents = (gchar **) g_malloc0(MAXHOSTNAMELEN * 2);
1064 read_result = g_file_get_contents(OS_HOSTNAME_PATH, myname_contents, NULL, NULL);
1065
1066 if(read_result && (passed_domain = has_domain(myname_contents[0]))) {
1067
1068 domain_len = strnlen(passed_domain, MAXHOSTNAMELEN);
1069
1070 if((domain_len + check_len) > MAXHOSTNAMELEN)
1071 bsd_hostname = NULL;
1072 else
1073 bsd_hostname = g_strconcat(ascii_translated_hostname, passed_domain, NULL);
1074
1075 } else if(myname_contents[0]) {
1076
1077 g_printf("%s does not contain a proper FQDN! this is a significant error on BSD machines, otherwise OK.\nfalling back to default domain, '%s'\n", OS_HOSTNAME_PATH, DEFAULT_DOMAIN);
1078
1079 domain_len = strnlen(DEFAULT_DOMAIN, MAXHOSTNAMELEN);
1080
1081 if((domain_len + check_len) > MAXHOSTNAMELEN)
1082 bsd_hostname = NULL;
1083 else
1084 bsd_hostname = g_strconcat(ascii_translated_hostname, DEFAULT_DOMAIN, NULL);
1085
1086 } else {
1087
1088 g_printf("could not read hostname at %s, this is a major error\n", OS_HOSTNAME_PATH);
1089 bsd_hostname = NULL;
1090 passed_domain = (gchar *) g_malloc0(MAXHOSTNAMELEN);
1091 }
1092 }
1093
1094 if(passed_domain)
1095 g_free(passed_domain);
1096 if(myname_contents)
1097 g_free(myname_contents);
1098
1099 if(bsd_hostname && !strchr(bsd_hostname, '\n')) {
1100
1101 temp_buf = bsd_hostname;
1102 bsd_hostname = g_strconcat(bsd_hostname, "\n", NULL);
1103 g_free(temp_buf);
1104 }
1105
1106 return bsd_hostname;
1107 }
1108
1109 /* returns NULL if no domain, otherwise append-appropriate domain string you must g_free()
1110 * i.e. has_domain("foo.bar.com") returns ".bar.com"
1111 * only pass g_hostname_to_ascii'd strings */
1112 static gchar *has_domain(const gchar *test) {
1113
1114 size_t hostname_len, full_len;
1115 gchar *ret;
1116
1117 hostname_len = strcspn(test, ".");
1118 full_len = strnlen(test, MAXHOSTNAMELEN);
1119
1120 if(full_len == hostname_len)
1121 return NULL;
1122
1123 ret = (gchar *) g_malloc0(MAXHOSTNAMELEN);
1124 g_strlcpy(ret, &test[hostname_len], MAXHOSTNAMELEN);
1125
1126 return ret;
1127 }