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

Proposed by James Hunt
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 Approve
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

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

Revision history for this message
Dimitri John Ledkov (xnox) :
review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches