Merge lp:~jamesodhunt/upstart/upstart-local-bridge into lp:upstart

Proposed by James Hunt on 2013-07-25
Status: Merged
Merged at revision: 1515
Proposed branch: lp:~jamesodhunt/upstart/upstart-local-bridge
Merge into: lp:upstart
Diff against target: 1012 lines (+964/-1)
4 files modified
ChangeLog (+22/-0)
extra/Makefile.am (+14/-1)
extra/man/upstart-local-bridge.8 (+123/-0)
extra/upstart-local-bridge.c (+805/-0)
To merge this branch: bzr merge lp:~jamesodhunt/upstart/upstart-local-bridge
Reviewer Review Type Date Requested Status
Dimitri John Ledkov 2013-07-25 Approve on 2013-07-29
Review via email: mp+177027@code.launchpad.net

Description of the change

This branch introduces a new bridge (the upstart-local-bridge) that accepts connections on a local socket and emits a named event (decided upon by the bridge, not the client), appending a name=value pair read from the socket to the event.

This bridge is extremely simple and as a result suffers from some limitations...

= Limitations =

- Only Local sockets are supported.
- The data is read in line-oriented fashion from the socket and only a single name=value pair is read / line
  (meaning that the event can only have a single variable added by the client).
- Only a single client connection is serviced at any one time.

= Security =

By default, the bridge will only accept connections from clients running under the same uid as the bridge, the expectation being that the bridge will run as root and only accept connections from root clients).

The bridge does nothing to prevent a possible DoS should a client send an arbitrarily large amount of data in the single name=value pair.

To post a comment you must log in.
1514. By James Hunt on 2013-07-25

* extra/upstart-local-bridge.c: Cruft removal.

Dimitri John Ledkov (xnox) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ChangeLog'
2--- ChangeLog 2013-07-24 09:40:15 +0000
3+++ ChangeLog 2013-07-25 19:58:24 +0000
4@@ -1,8 +1,30 @@
5+2013-07-25 James Hunt <james.hunt@ubuntu.com>
6+
7+ * extra/Makefile.am: Renamed to upstart-local-bridge.
8+ * extra/man/upstart-local-bridge.8: Removed inet type details.
9+ * extra/upstart-local-bridge.c:
10+ - Removed inet socket handling.
11+ - General clean-up.
12+
13+2013-07-24 James Hunt <james.hunt@ubuntu.com>
14+
15+ * extra/man/upstart-text-bridge.8: Added extra variables.
16+ * extra/upstart-text-bridge.c: socket_reader():
17+ - Fixed assertion failure caused by passing invalid address of fd.
18+ - Added new standard environment variables to event.
19+
20 2013-07-24 James Hunt <james.hunt@ubuntu.com>
21
22 * extra/upstart-dbus-bridge.c: signal_filter(): Use inttype
23 macros to ensure portability.
24
25+2013-07-23 James Hunt <james.hunt@ubuntu.com>
26+
27+ * extra/upstart-text-bridge.c: New bridge.
28+ * extra/Makefile.am: Updated for new bridge.
29+ * extra/man/upstart-text-bridge.8: New man page.
30+ * extra/Makefile.am: Added man page for text-bridge.
31+
32 2013-07-19 Dmitrijs Ledkovs <xnox@ubuntu.com>
33
34 * init/session.c: fix a bug in session_from_index to handle more
35
36=== modified file 'extra/Makefile.am'
37--- extra/Makefile.am 2013-07-24 04:41:18 +0000
38+++ extra/Makefile.am 2013-07-25 19:58:24 +0000
39@@ -28,7 +28,8 @@
40 upstart-socket-bridge \
41 upstart-event-bridge \
42 upstart-file-bridge \
43- upstart-dbus-bridge
44+ upstart-dbus-bridge \
45+ upstart-local-bridge
46
47 dist_init_DATA = \
48 conf/upstart-socket-bridge.conf \
49@@ -41,6 +42,7 @@
50 man/upstart-event-bridge.8 \
51 man/upstart-file-bridge.8 \
52 man/upstart-dbus-bridge.8 \
53+ man/upstart-local-bridge.8 \
54 man/socket-event.7 \
55 man/file-event.7 \
56 man/dbus-event.7
57@@ -89,6 +91,17 @@
58 $(NIH_DBUS_LIBS) \
59 $(DBUS_LIBS)
60
61+upstart_local_bridge_SOURCES = \
62+ upstart-local-bridge.c
63+nodist_upstart_local_bridge_SOURCES = \
64+ $(com_ubuntu_Upstart_OUTPUTS) \
65+ $(com_ubuntu_Upstart_Job_OUTPUTS)
66+upstart_local_bridge_LDADD = \
67+ $(LTLIBINTL) \
68+ $(NIH_LIBS) \
69+ $(NIH_DBUS_LIBS) \
70+ $(DBUS_LIBS)
71+
72 if HAVE_UDEV
73 dist_init_DATA += \
74 conf/upstart-udev-bridge.conf
75
76=== added file 'extra/man/upstart-local-bridge.8'
77--- extra/man/upstart-local-bridge.8 1970-01-01 00:00:00 +0000
78+++ extra/man/upstart-local-bridge.8 2013-07-25 19:58:24 +0000
79@@ -0,0 +1,123 @@
80+.TH upstart\-local\-bridge 8 2013-07-23 upstart
81+.\"
82+.SH NAME
83+upstart\-local\-bridge \- Bridge between Upstart and a local client socket
84+connection.
85+.\"
86+.SH SYNOPSIS
87+.B upstart\-local\-bridge
88+.RI [ OPTIONS ]...
89+.\"
90+.SH DESCRIPTION
91+.B upstart\-local\-bridge
92+listens on a local domain socket for name=value pairs and creates
93+.BR init (8)
94+events for them.
95+
96+The local unix domain socket can be either named or abstract.
97+.\"
98+.SH OPTIONS
99+.\"
100+.TP
101+.B \-\-any\-user
102+By default the bridge will only accept connections from clients running
103+under the same user ID as the bridge itself. This option allows
104+connections from any user.
105+.\"
106+.TP
107+.B \-\-daemon
108+Detach and run in the background.
109+.\"
110+.TP
111+.B \-\-debug
112+Enable debugging output.
113+.\"
114+.TP
115+.B \-\-event \fIevent\fP
116+Specify name of event to emit on receipt of a name=value pair.
117+.\"
118+.TP
119+.B \-\-help
120+Show brief usage summary.
121+.\"
122+.TP
123+.B \-\-path \fIpath\fP
124+Specify path for local/abstract socket to listen on. If the first byte of
125+.I path
126+is an \(aq\fI@\fP\(aq, the socket will be created as an abstract socket.
127+.\"
128+.TP
129+.B \-\-verbose
130+Enable verbose output.
131+.\"
132+.SH EVENT DETAILS
133+
134+The following environment variables are added automatically to the event
135+to be emitted, with the name=value pair being added as the last variable.
136+.P
137+.IP \(bu 4
138+SOCKET_TYPE=unix
139+.IP \(bu 4
140+SOCKET_VARIANT=[\fInamed\fP|\fIabstract\fP]
141+Sub-type of socket.
142+.IP \(bu 4
143+CLIENT_UID=\fIUID\fP
144+User ID of connected client.
145+.IP \(bu 4
146+CLIENT_GID=\fIGID\fP
147+Group ID of connected client.
148+.IP \(bu 4
149+CLIENT_PID=\fIPID\fP
150+Process ID of connected client.
151+.IP \(bu 4
152+PATH=\fIPATH\fP
153+.P
154+.\"
155+.SH EXAMPLES
156+.IP "upstart\-local\-bridge \-\-event=foo \-\-path=/var/foo/bar" 0.4i
157+Listen on local socket
158+.I /var/foo/bar
159+and when a name=value pair is read, emit an event of the form:
160+
161+.RS
162+.nf
163+foo SOCKET_TYPE=unix SOCKET_VARIANT=named PATH=/var/foo/bar name=value
164+.fi
165+.RE
166+.IP "upstart\-local\-bridge \-\-event=bar \-\-path=@/var/foo/bar" 0.4i
167+Listen on abstract socket
168+.I @/var/foo/bar
169+and when a name=value pair is read, emit an event of the form:
170+
171+.RS
172+.nf
173+bar SOCKET_TYPE=unix SOCKET_VARIANT=abstract PATH=@/var/foo/bar name=value
174+.fi
175+.RE
176+.\"
177+.SH NOTES
178+.IP \(bu 4
179+If a named local socket is specified, all path elements except
180+for the last must already exist before the bridge starts.
181+.\"
182+.SH LIMITATIONS
183+
184+.IP \(bu 4
185+Only a single client connection is serviced at any one time.
186+.\"
187+.SH AUTHOR
188+Written by James Hunt
189+.RB < james.hunt@canonical.com >
190+.\"
191+.SH BUGS
192+Report bugs at
193+.RB < https://launchpad.net/ubuntu/+source/upstart/+bugs >
194+.\"
195+.SH COPYRIGHT
196+Copyright \(co 2013 Canonical Ltd.
197+.PP
198+This is free software; see the source for copying conditions. There is NO
199+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
200+.SH SEE ALSO
201+.BR init (5)
202+.BR init (8)
203
204=== added file 'extra/upstart-local-bridge.c'
205--- extra/upstart-local-bridge.c 1970-01-01 00:00:00 +0000
206+++ extra/upstart-local-bridge.c 2013-07-25 19:58:24 +0000
207@@ -0,0 +1,805 @@
208+/* upstart
209+ *
210+ * Copyright © 2013 Canonical Ltd.
211+ * Author: James Hunt <james.hunt@canonical.com>
212+ *
213+ * This program is free software; you can redistribute it and/or modify
214+ * it under the terms of the GNU General Public License version 2, as
215+ * published by the Free Software Foundation.
216+ *
217+ * This program is distributed in the hope that it will be useful,
218+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
219+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
220+ * GNU General Public License for more details.
221+ *
222+ * You should have received a copy of the GNU General Public License along
223+ * with this program; if not, write to the Free Software Foundation, Inc.,
224+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
225+ */
226+
227+#ifdef HAVE_CONFIG_H
228+# include <config.h>
229+#endif /* HAVE_CONFIG_H */
230+
231+#include <sys/types.h>
232+#include <sys/socket.h>
233+#include <sys/un.h>
234+
235+#include <errno.h>
236+#include <stdlib.h>
237+#include <string.h>
238+#include <syslog.h>
239+#include <unistd.h>
240+
241+#include <nih/macros.h>
242+#include <nih/alloc.h>
243+#include <nih/list.h>
244+#include <nih/hash.h>
245+#include <nih/string.h>
246+#include <nih/io.h>
247+#include <nih/option.h>
248+#include <nih/main.h>
249+#include <nih/logging.h>
250+#include <nih/error.h>
251+
252+#include <nih-dbus/dbus_connection.h>
253+#include <nih-dbus/dbus_proxy.h>
254+
255+#include "dbus/upstart.h"
256+#include "com.ubuntu.Upstart.h"
257+#include "com.ubuntu.Upstart.Job.h"
258+
259+/**
260+ * Job:
261+ *
262+ * @entry: list header,
263+ * @path: D-Bus path for a job.
264+ *
265+ * Representation of an Upstart Job.
266+ *
267+ **/
268+typedef struct job {
269+ NihList entry;
270+ char *path;
271+} Job;
272+
273+/**
274+ * Socket:
275+ *
276+ * @addr/sun_addr: socket address,
277+ * @addrlen: length of sun_addr,
278+ * @sock: file descriptor of socket,
279+ * @watch: IO Watch used to detect client activity.
280+ *
281+ * Representation of a socket(2).
282+ **/
283+typedef struct socket {
284+ union {
285+ struct sockaddr addr; /* Generic type */
286+ struct sockaddr_un sun_addr; /* local/domain/unix/abstract socket */
287+ };
288+ socklen_t addrlen;
289+
290+ int sock;
291+ NihIoWatch *watch;
292+} Socket;
293+
294+/**
295+ * ClientConnection:
296+ *
297+ * @sock: socket client connected via,
298+ * @fd: file descriptor client connected on,
299+ * @ucred: client credentials.
300+ *
301+ * Representation of a connected client.
302+ **/
303+typedef struct client_connection {
304+ Socket *sock;
305+ int fd;
306+ struct ucred ucred;
307+} ClientConnection;
308+
309+static void upstart_job_added (void *data, NihDBusMessage *message,
310+ const char *job);
311+static void upstart_job_removed (void *data, NihDBusMessage *message,
312+ const char *job);
313+static void upstart_connect (void);
314+static void upstart_disconnected (DBusConnection *connection);
315+
316+static Socket *create_socket (void *parent);
317+
318+static void socket_watcher (Socket *sock, NihIoWatch *watch,
319+ NihIoEvents events);
320+
321+static void socket_reader (ClientConnection *client, NihIo *io,
322+ const char *buf, size_t len);
323+
324+static void close_handler (ClientConnection *client, NihIo *io);
325+
326+static void emit_event_error (void *data, NihDBusMessage *message);
327+
328+static void signal_handler (void *data, NihSignal *signal);
329+
330+static void cleanup (void);
331+
332+/**
333+ * daemonise:
334+ *
335+ * Set to TRUE if we should become a daemon, rather than just running
336+ * in the foreground.
337+ **/
338+static int daemonise = FALSE;
339+
340+/**
341+ * jobs:
342+ *
343+ * Jobs that we're monitoring.
344+ **/
345+static NihHash *jobs = NULL;
346+
347+/**
348+ * upstart:
349+ *
350+ * Proxy to Upstart daemon.
351+ **/
352+static NihDBusProxy *upstart = NULL;
353+
354+/**
355+ * event_name:
356+ *
357+ * Name of event this bridge emits.
358+ **/
359+static char *event_name = NULL;
360+
361+/**
362+ * Unix (local) domain socket path.
363+ *
364+ * Abstract sockets will have '@' as first character.
365+ **/
366+static char *socket_path = NULL;
367+
368+/**
369+ * socket_type:
370+ *
371+ * Type of socket supported by this bridge.
372+ **/
373+static char *socket_type = "unix";
374+
375+/**
376+ * socket_name:
377+ *
378+ * Human-readable socket name in form:
379+ *
380+ * unix:[@]/some/path
381+ **/
382+static char *socket_name = NULL;
383+
384+/**
385+ * sock:
386+ *
387+ * Socket this bridge listens on.
388+ **/
389+static Socket *sock = NULL;
390+
391+/**
392+ * any_user:
393+ *
394+ * If FALSE, only accept connections from the same uid as
395+ * user the bridge runs as.
396+ * If TRUE, accept connections from any user.
397+ **/
398+static int any_user = FALSE;
399+
400+/**
401+ * options:
402+ *
403+ * Command-line options accepted by this program.
404+ **/
405+static NihOption options[] = {
406+ { 0, "daemon", N_("Detach and run in the background"),
407+ NULL, NULL, &daemonise, NULL },
408+
409+ { 0, "event", N_("specify name of event to emit on receipt of name=value pair"),
410+ NULL, "EVENT", &event_name, NULL },
411+
412+ { 0, "any-user", N_("allow any user to connect"),
413+ NULL, NULL, &any_user, NULL },
414+
415+ { 0, "path", N_("specify path for local/abstract socket to use"),
416+ NULL, "PATH", &socket_path, NULL },
417+
418+ NIH_OPTION_LAST
419+};
420+
421+
422+/**
423+ * signal_handler:
424+ * @data: unused,
425+ * @signal: signal caught.
426+ *
427+ * Called when we receive the TERM/INT signal.
428+ **/
429+static void
430+signal_handler (void *data,
431+ NihSignal *signal)
432+{
433+ nih_assert (signal != NULL);
434+
435+ cleanup ();
436+
437+ nih_main_loop_exit (0);
438+}
439+
440+/**
441+ * cleanup:
442+ *
443+ * Perform final operations before exit.
444+ **/
445+static void
446+cleanup (void)
447+{
448+ nih_assert (sock);
449+
450+ close (sock->sock);
451+
452+ if (sock->sun_addr.sun_path[0] != '@')
453+ unlink (sock->sun_addr.sun_path);
454+}
455+
456+int
457+main (int argc,
458+ char *argv[])
459+{
460+ char **args;
461+ int ret;
462+
463+ nih_main_init (argv[0]);
464+
465+ nih_option_set_synopsis (_("Local socket Upstart Bridge"));
466+ nih_option_set_help (
467+ _("By default, this bridge does not detach from the "
468+ "console and remains in the foreground. Use the --daemon "
469+ "option to have it detach."));
470+
471+ args = nih_option_parser (NULL, argc, argv, options, FALSE);
472+ if (! args)
473+ exit (1);
474+
475+ if (! event_name) {
476+ nih_fatal ("%s", _("Must specify event name"));
477+ exit (1);
478+ }
479+
480+ /* Allocate jobs hash table */
481+ jobs = NIH_MUST (nih_hash_string_new (NULL, 0));
482+
483+ sock = create_socket (NULL);
484+ if (! sock) {
485+ nih_fatal ("%s %s",
486+ _("Failed to create socket"),
487+ socket_name);
488+ exit (1);
489+ }
490+
491+ nih_debug ("Connected to socket '%s' on fd %d", socket_name, sock->sock);
492+
493+ upstart_connect ();
494+
495+ /* Become daemon */
496+ if (daemonise) {
497+ if (nih_main_daemonise () < 0) {
498+ NihError *err;
499+
500+ err = nih_error_get ();
501+ nih_fatal ("%s: %s", _("Unable to become daemon"),
502+ err->message);
503+ nih_free (err);
504+
505+ exit (1);
506+ }
507+
508+ /* Send all logging output to syslog */
509+ openlog (program_name, LOG_PID, LOG_DAEMON);
510+ nih_log_set_logger (nih_logger_syslog);
511+ }
512+
513+ nih_signal_set_handler (SIGTERM, nih_signal_handler);
514+ NIH_MUST (nih_signal_add_handler (NULL, SIGTERM, signal_handler, NULL));
515+
516+ nih_signal_set_handler (SIGINT, nih_signal_handler);
517+ NIH_MUST (nih_signal_add_handler (NULL, SIGINT, signal_handler, NULL));
518+
519+ /* Handle TERM and INT signals gracefully */
520+ nih_signal_set_handler (SIGTERM, nih_signal_handler);
521+ NIH_MUST (nih_signal_add_handler (NULL, SIGTERM, nih_main_term_signal, NULL));
522+
523+ if (! daemonise) {
524+ nih_signal_set_handler (SIGINT, nih_signal_handler);
525+ NIH_MUST (nih_signal_add_handler (NULL, SIGINT, nih_main_term_signal, NULL));
526+ }
527+
528+ ret = nih_main_loop ();
529+
530+ return ret;
531+}
532+
533+static void
534+upstart_job_added (void *data,
535+ NihDBusMessage *message,
536+ const char *job_class_path)
537+{
538+ nih_local NihDBusProxy *job_class = NULL;
539+ nih_local char ***start_on = NULL;
540+ nih_local char ***stop_on = NULL;
541+ Job *job;
542+
543+ nih_assert (job_class_path != NULL);
544+
545+ /* Obtain a proxy to the job */
546+ job_class = nih_dbus_proxy_new (NULL, upstart->connection,
547+ upstart->name, job_class_path,
548+ NULL, NULL);
549+ if (! job_class) {
550+ NihError *err;
551+
552+ err = nih_error_get ();
553+ nih_error ("Could not create proxy for job %s: %s",
554+ job_class_path, err->message);
555+ nih_free (err);
556+
557+ return;
558+ }
559+
560+ job_class->auto_start = FALSE;
561+
562+ /* Obtain the start_on and stop_on properties of the job */
563+ if (job_class_get_start_on_sync (NULL, job_class, &start_on) < 0) {
564+ NihError *err;
565+
566+ err = nih_error_get ();
567+ nih_error ("Could not obtain job start condition %s: %s",
568+ job_class_path, err->message);
569+ nih_free (err);
570+
571+ return;
572+ }
573+
574+ if (job_class_get_stop_on_sync (NULL, job_class, &stop_on) < 0) {
575+ NihError *err;
576+
577+ err = nih_error_get ();
578+ nih_error ("Could not obtain job stop condition %s: %s",
579+ job_class_path, err->message);
580+ nih_free (err);
581+
582+ return;
583+ }
584+
585+ /* Free any existing record for the job (should never happen,
586+ * but worth being safe).
587+ */
588+ job = (Job *)nih_hash_lookup (jobs, job_class_path);
589+ if (job)
590+ nih_free (job);
591+
592+ /* Create new record for the job */
593+ job = NIH_MUST (nih_new (NULL, Job));
594+ job->path = NIH_MUST (nih_strdup (job, job_class_path));
595+
596+ nih_list_init (&job->entry);
597+
598+ nih_debug ("Job got added %s", job_class_path);
599+
600+ nih_alloc_set_destructor (job, nih_list_destroy);
601+
602+ /* Add all jobs */
603+ nih_hash_add (jobs, &job->entry);
604+}
605+
606+static void
607+upstart_job_removed (void *data,
608+ NihDBusMessage *message,
609+ const char *job_path)
610+{
611+ Job *job;
612+
613+ nih_assert (job_path != NULL);
614+
615+ job = (Job *)nih_hash_lookup (jobs, job_path);
616+ if (job) {
617+ nih_debug ("Job went away %s", job_path);
618+ nih_free (job);
619+ }
620+}
621+
622+static void
623+upstart_connect (void)
624+{
625+ DBusConnection *connection;
626+ char **job_class_paths;
627+
628+ /* Initialise the connection to Upstart */
629+ connection = NIH_SHOULD (nih_dbus_connect (DBUS_ADDRESS_UPSTART, upstart_disconnected));
630+ if (! connection) {
631+ NihError *err;
632+
633+ err = nih_error_get ();
634+ nih_fatal ("%s: %s", _("Could not connect to Upstart"),
635+ err->message);
636+ nih_free (err);
637+
638+ exit (1);
639+ }
640+
641+ upstart = NIH_SHOULD (nih_dbus_proxy_new (NULL, connection,
642+ NULL, DBUS_PATH_UPSTART,
643+ NULL, NULL));
644+ if (! upstart) {
645+ NihError *err;
646+
647+ err = nih_error_get ();
648+ nih_fatal ("%s: %s", _("Could not create Upstart proxy"),
649+ err->message);
650+ nih_free (err);
651+
652+ exit (1);
653+ }
654+
655+ nih_debug ("Connected to Upstart");
656+
657+ /* Connect signals to be notified when jobs come and go */
658+ if (! nih_dbus_proxy_connect (upstart, &upstart_com_ubuntu_Upstart0_6, "JobAdded",
659+ (NihDBusSignalHandler)upstart_job_added, NULL)) {
660+ NihError *err;
661+
662+ err = nih_error_get ();
663+ nih_fatal ("%s: %s", _("Could not create JobAdded signal connection"),
664+ err->message);
665+ nih_free (err);
666+
667+ exit (1);
668+ }
669+
670+ if (! nih_dbus_proxy_connect (upstart, &upstart_com_ubuntu_Upstart0_6, "JobRemoved",
671+ (NihDBusSignalHandler)upstart_job_removed, NULL)) {
672+ NihError *err;
673+
674+ err = nih_error_get ();
675+ nih_fatal ("%s: %s", _("Could not create JobRemoved signal connection"),
676+ err->message);
677+ nih_free (err);
678+
679+ exit (1);
680+ }
681+
682+ /* Request a list of all current jobs */
683+ if (upstart_get_all_jobs_sync (NULL, upstart, &job_class_paths) < 0) {
684+ NihError *err;
685+
686+ err = nih_error_get ();
687+ nih_fatal ("%s: %s", _("Could not obtain job list"),
688+ err->message);
689+ nih_free (err);
690+
691+ exit (1);
692+ }
693+
694+ for (char **job_class_path = job_class_paths;
695+ job_class_path && *job_class_path; job_class_path++)
696+ upstart_job_added (NULL, NULL, *job_class_path);
697+
698+ nih_free (job_class_paths);
699+}
700+
701+static void
702+upstart_disconnected (DBusConnection *connection)
703+{
704+ nih_fatal (_("Disconnected from Upstart"));
705+ nih_main_loop_exit (1);
706+}
707+
708+/**
709+ * socket_watcher:
710+ *
711+ * @sock: Socket,
712+ * @watch: IO watch,
713+ * @event: events that occurred.
714+ *
715+ * Called when activity is received for socket fd.
716+ **/
717+static void
718+socket_watcher (Socket *sock,
719+ NihIoWatch *watch,
720+ NihIoEvents events)
721+{
722+ struct sockaddr client_addr;
723+ socklen_t client_len;
724+ nih_local char *buffer = NULL;
725+ ClientConnection *client;
726+ size_t len;
727+
728+ nih_assert (sock);
729+ nih_assert (watch);
730+
731+ client = NIH_MUST (nih_new (NULL, ClientConnection));
732+ memset (client, 0, sizeof (ClientConnection));
733+ client->sock = sock;
734+
735+ client_len = sizeof (struct sockaddr);
736+
737+ client->fd = accept (sock->sock, (struct sockaddr *)&client_addr, &client_len);
738+
739+ if (client->fd < 0) {
740+ nih_fatal ("%s %s %s", _("Failed to accept socket"),
741+ socket_name, strerror (errno));
742+ return;
743+ }
744+
745+ len = sizeof (client->ucred);
746+
747+ /* Establish who is connected to the other end */
748+ if (getsockopt (client->fd, SOL_SOCKET, SO_PEERCRED, &client->ucred, &len) < 0)
749+ goto error;
750+
751+ if (! any_user && client->ucred.uid != geteuid ()) {
752+ nih_warn ("Ignoring request from uid %u (gid %u, pid %u)",
753+ (unsigned int)client->ucred.uid,
754+ (unsigned int)client->ucred.gid,
755+ (unsigned int)client->ucred.pid);
756+ close (client->fd);
757+ return;
758+ }
759+
760+ nih_debug ("Client connected via local socket to %s: "
761+ "pid %d (uid %d, gid %d)",
762+ socket_name,
763+ client->ucred.pid,
764+ client->ucred.uid,
765+ client->ucred.gid);
766+
767+ /* Wait for remote end to send data */
768+ NIH_MUST (nih_io_reopen (sock, client->fd,
769+ NIH_IO_STREAM,
770+ (NihIoReader)socket_reader,
771+ (NihIoCloseHandler)close_handler,
772+ NULL,
773+ client));
774+ return;
775+
776+error:
777+ nih_warn ("%s %s: %s",
778+ _("Cannot establish peer credentials for socket"),
779+ socket_name, strerror (errno));
780+}
781+
782+/**
783+ * socket_reader:
784+ *
785+ * @client: client connection,
786+ * @io: NihIo,
787+ * @buf: data read from client,
788+ * @len: length of @buf.
789+ *
790+ * NihIoReader function called when data has been read from the
791+ * connected client.
792+ **/
793+static void
794+socket_reader (ClientConnection *client,
795+ NihIo *io,
796+ const char *buf,
797+ size_t len)
798+{
799+ DBusPendingCall *pending_call;
800+ nih_local char **env = NULL;
801+ nih_local char *var = NULL;
802+ size_t used_len = 0;
803+ int i;
804+
805+ nih_assert (sock);
806+ nih_assert (client);
807+ nih_assert (io);
808+ nih_assert (buf);
809+
810+ /* Ignore messages that are too short */
811+ if (len < 2)
812+ goto error;
813+
814+ /* Ensure the data is a name=value pair */
815+ if (! strchr (buf, '=') || buf[0] == '=')
816+ goto error;
817+
818+ /* Remove line endings */
819+ for (i = 0, used_len = len; i < 2; i++) {
820+ if (buf[used_len-1] == '\n' || buf[used_len-1] == '\r')
821+ used_len--;
822+ else
823+ break;
824+ }
825+
826+ /* Second check to ensure overly short messages are ignored */
827+ if (used_len < 2)
828+ goto error;
829+
830+ /* Construct the event environment.
831+ *
832+ * Note that although the client could conceivably specify one
833+ * of the variables below _itself_, if the intent is malicious
834+ * it will be thwarted since although the following example
835+ * event is valid...
836+ *
837+ * foo BAR=BAZ BAR=MALICIOUS
838+ *
839+ * ... environment variable matching only happens for the first
840+ * occurence of a variable. In summary, a malicious client
841+ * cannot spoof the standard variables we set.
842+ */
843+ env = NIH_MUST (nih_str_array_new (NULL));
844+
845+ /* Specify type to allow for other types to be added in the future */
846+ var = NIH_MUST (nih_sprintf (NULL, "SOCKET_TYPE=%s", socket_type));
847+ NIH_MUST (nih_str_array_addp (&env, NULL, NULL, var));
848+
849+ var = NIH_MUST (nih_sprintf (NULL, "SOCKET_VARIANT=%s",
850+ sock->sun_addr.sun_path[0] ? "named" : "abstract"));
851+ NIH_MUST (nih_str_array_addp (&env, NULL, NULL, var));
852+
853+ var = NIH_MUST (nih_sprintf (NULL, "CLIENT_UID=%u", (unsigned int)client->ucred.uid));
854+ NIH_MUST (nih_str_array_addp (&env, NULL, NULL, var));
855+
856+ var = NIH_MUST (nih_sprintf (NULL, "CLIENT_GID=%u", (unsigned int)client->ucred.gid));
857+ NIH_MUST (nih_str_array_addp (&env, NULL, NULL, var));
858+
859+ var = NIH_MUST (nih_sprintf (NULL, "CLIENT_PID=%u", (unsigned int)client->ucred.pid));
860+ NIH_MUST (nih_str_array_addp (&env, NULL, NULL, var));
861+
862+ var = NIH_MUST (nih_sprintf (NULL, "PATH=%s", socket_path));
863+ NIH_MUST (nih_str_array_addp (&env, NULL, NULL, var));
864+
865+ /* Add the name=value pair */
866+ NIH_MUST (nih_str_array_addn (&env, NULL, NULL, buf, used_len));
867+
868+ pending_call = upstart_emit_event (upstart,
869+ event_name, env, FALSE,
870+ NULL, emit_event_error, NULL,
871+ NIH_DBUS_TIMEOUT_NEVER);
872+
873+ if (! pending_call) {
874+ NihError *err;
875+ err = nih_error_get ();
876+ nih_warn ("%s", err->message);
877+ nih_free (err);
878+ }
879+
880+ /* Consume the entire length */
881+ nih_io_buffer_shrink (io->recv_buf, len);
882+
883+ dbus_pending_call_unref (pending_call);
884+
885+ return;
886+
887+error:
888+ nih_debug ("ignoring invalid input of length %lu",
889+ (unsigned long int)len);
890+
891+ /* Consume the entire length */
892+ nih_io_buffer_shrink (io->recv_buf, len);
893+}
894+
895+static void
896+close_handler (ClientConnection *client, NihIo *io)
897+{
898+ nih_assert (client);
899+ nih_assert (io);
900+
901+ nih_debug ("Remote end closed connection");
902+
903+ close (client->fd);
904+ nih_free (client);
905+ nih_free (io);
906+}
907+
908+/**
909+ * create_socket:
910+ * @parent: Parent pointer.
911+ *
912+ * Create a Socket object, listen on it and arrange for NIH to monitor
913+ * it.
914+ *
915+ * Returns: Newly-allocated Socket on success, or NULL on error.
916+ **/
917+static Socket *
918+create_socket (void *parent)
919+{
920+ Socket *sock = NULL;
921+ int opt = 1;
922+ size_t len;
923+
924+ if (! socket_path) {
925+ nih_fatal ("%s", _("Must specify socket path"));
926+ exit (1);
927+ }
928+
929+ NIH_MUST (nih_strcat_sprintf (&socket_name, NULL, "%s:%s",
930+ socket_type, socket_path));
931+
932+ sock = NIH_MUST (nih_new (NULL, Socket));
933+ memset (sock, 0, sizeof (Socket));
934+ sock->sock = -1;
935+
936+ sock->sun_addr.sun_family = AF_UNIX;
937+
938+ if (! *socket_path || (socket_path[0] != '/' && socket_path[0] != '@')) {
939+ nih_fatal ("%s %s", _("Invalid path"), socket_path);
940+ goto error;
941+ }
942+
943+ len = strlen (socket_path);
944+
945+ if (len > sizeof (sock->sun_addr.sun_path)) {
946+ nih_fatal ("%s %s", _("Path too long"), socket_path);
947+ goto error;
948+ }
949+
950+ strncpy (sock->sun_addr.sun_path, socket_path,
951+ sizeof (sock->sun_addr.sun_path));
952+
953+ sock->addrlen = sizeof (sock->sun_addr.sun_family) + len;
954+
955+ /* Handle abstract names */
956+ if (sock->sun_addr.sun_path[0] == '@')
957+ sock->sun_addr.sun_path[0] = '\0';
958+
959+ sock->sock = socket (sock->addr.sa_family, SOCK_STREAM, 0);
960+ if (sock->sock < 0) {
961+ nih_fatal ("%s %s %s", _("Failed to create socket"),
962+ socket_name, strerror (errno));
963+ goto error;
964+ }
965+
966+ if (setsockopt (sock->sock, SOL_SOCKET, SO_REUSEADDR,
967+ &opt, sizeof (opt)) < 0) {
968+ nih_fatal ("%s %s %s", _("Failed to set socket reuse"),
969+ socket_name, strerror (errno));
970+ goto error;
971+ }
972+
973+ if (setsockopt (sock->sock, SOL_SOCKET, SO_PASSCRED,
974+ &opt, sizeof (opt)) < 0) {
975+ nih_fatal ("%s %s %s", _("Failed to set socket credential-passing"),
976+ socket_name, strerror (errno));
977+ goto error;
978+ }
979+
980+ if (bind (sock->sock, &sock->addr, sock->addrlen) < 0) {
981+ nih_fatal ("%s %s %s", _("Failed to bind socket"),
982+ socket_name, strerror (errno));
983+ goto error;
984+ }
985+
986+ if (listen (sock->sock, SOMAXCONN) < 0) {
987+ nih_fatal ("%s %s %s", _("Failed to listen on socket"),
988+ socket_name, strerror (errno));
989+ goto error;
990+ }
991+
992+ sock->watch = NIH_MUST (nih_io_add_watch (sock, sock->sock,
993+ NIH_IO_READ,
994+ (NihIoWatcher)socket_watcher, sock));
995+
996+ return sock;
997+
998+error:
999+ nih_free (sock);
1000+ return NULL;
1001+}
1002+
1003+static void
1004+emit_event_error (void *data,
1005+ NihDBusMessage *message)
1006+{
1007+ NihError *err;
1008+
1009+ err = nih_error_get ();
1010+ nih_warn ("%s", err->message);
1011+ nih_free (err);
1012+}

Subscribers

People subscribed via source and target branches