1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2009-2010 Red Hat, Inc.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
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
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Written by: Matthias Clasen <mclasen@redhat.com>
20 */
21
22 #include "config.h"
23
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/wait.h>
28 #include <fcntl.h>
29 #include <grp.h>
30
31 #include <syslog.h>
32
33 #include <polkit/polkit.h>
34
35 #include "util.h"
36
37 static gchar *
get_cmdline_of_pid(GPid pid)38 get_cmdline_of_pid (GPid pid)
39 {
40 gchar *ret;
41 g_autofree gchar *filename = NULL;
42 g_autofree gchar *contents = NULL;
43 gsize contents_len;
44 g_autoptr(GError) error = NULL;
45 guint n;
46
47 filename = g_strdup_printf ("/proc/%d/cmdline", (int) pid);
48
49 if (!g_file_get_contents (filename,
50 &contents,
51 &contents_len,
52 &error)) {
53 g_warning ("Error opening `%s': %s",
54 filename,
55 error->message);
56 return NULL;
57 }
58 /* The kernel uses '\0' to separate arguments - replace those with a space. */
59 for (n = 0; n < contents_len - 1; n++) {
60 if (contents[n] == '\0')
61 contents[n] = ' ';
62 }
63
64 ret = g_strdup (contents);
65 g_strstrip (ret);
66 return ret;
67 }
68
69 static gboolean
get_caller_pid(GDBusMethodInvocation * context,GPid * pid)70 get_caller_pid (GDBusMethodInvocation *context,
71 GPid *pid)
72 {
73 g_autoptr(GVariant) reply = NULL;
74 g_autoptr(GError) error = NULL;
75 guint32 pid_as_int;
76
77 reply = g_dbus_connection_call_sync (g_dbus_method_invocation_get_connection (context),
78 "org.freedesktop.DBus",
79 "/org/freedesktop/DBus",
80 "org.freedesktop.DBus",
81 "GetConnectionUnixProcessID",
82 g_variant_new ("(s)",
83 g_dbus_method_invocation_get_sender (context)),
84 G_VARIANT_TYPE ("(u)"),
85 G_DBUS_CALL_FLAGS_NONE,
86 -1,
87 NULL,
88 &error);
89
90 if (reply == NULL) {
91 g_warning ("Could not talk to message bus to find uid of sender %s: %s",
92 g_dbus_method_invocation_get_sender (context),
93 error->message);
94 return FALSE;
95 }
96
97 g_variant_get (reply, "(u)", &pid_as_int);
98 *pid = pid_as_int;
99
100 return TRUE;
101 }
102
103 void
sys_log(GDBusMethodInvocation * context,const gchar * format,...)104 sys_log (GDBusMethodInvocation *context,
105 const gchar *format,
106 ...)
107 {
108 va_list args;
109 g_autofree gchar *msg = NULL;
110
111 va_start (args, format);
112 msg = g_strdup_vprintf (format, args);
113 va_end (args);
114
115 if (context) {
116 PolkitSubject *subject;
117 g_autofree gchar *cmdline = NULL;
118 g_autofree gchar *id = NULL;
119 GPid pid = 0;
120 gint uid = -1;
121 g_autofree gchar *tmp = NULL;
122
123 subject = polkit_system_bus_name_new (g_dbus_method_invocation_get_sender (context));
124 id = polkit_subject_to_string (subject);
125
126 if (get_caller_pid (context, &pid)) {
127 cmdline = get_cmdline_of_pid (pid);
128 } else {
129 pid = 0;
130 cmdline = NULL;
131 }
132
133 if (cmdline != NULL) {
134 if (get_caller_uid (context, &uid)) {
135 tmp = g_strdup_printf ("request by %s [%s pid:%d uid:%d]: %s", id, cmdline, (int) pid, uid, msg);
136 } else {
137 tmp = g_strdup_printf ("request by %s [%s pid:%d]: %s", id, cmdline, (int) pid, msg);
138 }
139 } else {
140 if (get_caller_uid (context, &uid) && pid != 0) {
141 tmp = g_strdup_printf ("request by %s [pid:%d uid:%d]: %s", id, (int) pid, uid, msg);
142 } else if (pid != 0) {
143 tmp = g_strdup_printf ("request by %s [pid:%d]: %s", id, (int) pid, msg);
144 } else {
145 tmp = g_strdup_printf ("request by %s: %s", id, msg);
146 }
147 }
148
149 g_free (msg);
150 msg = g_steal_pointer (&tmp);
151
152 g_object_unref (subject);
153 }
154
155 syslog (LOG_NOTICE, "%s", msg);
156 }
157
158 static void
get_caller_loginuid(GDBusMethodInvocation * context,gchar * loginuid,gint size)159 get_caller_loginuid (GDBusMethodInvocation *context, gchar *loginuid, gint size)
160 {
161 GPid pid;
162 gint uid;
163 g_autofree gchar *path = NULL;
164 g_autofree gchar *buf = NULL;
165
166 if (!get_caller_uid (context, &uid)) {
167 uid = getuid ();
168 }
169
170 if (get_caller_pid (context, &pid)) {
171 path = g_strdup_printf ("/proc/%d/loginuid", (int) pid);
172 } else {
173 path = NULL;
174 }
175
176 if (path != NULL && g_file_get_contents (path, &buf, NULL, NULL)) {
177 strncpy (loginuid, buf, size);
178 }
179 else {
180 g_snprintf (loginuid, size, "%d", uid);
181 }
182 }
183
184 static gboolean
compat_check_exit_status(int estatus,GError ** error)185 compat_check_exit_status (int estatus,
186 GError **error)
187 {
188 #if GLIB_CHECK_VERSION(2, 33, 12)
189 return g_spawn_check_exit_status (estatus, error);
190 #else
191 if (!WIFEXITED (estatus)) {
192 g_set_error (error,
193 G_SPAWN_ERROR,
194 G_SPAWN_ERROR_FAILED,
195 "Exited abnormally");
196 return FALSE;
197 }
198 if (WEXITSTATUS (estatus) != 0) {
199 g_set_error (error,
200 G_SPAWN_ERROR,
201 G_SPAWN_ERROR_FAILED,
202 "Exited with code %d",
203 WEXITSTATUS(estatus));
204 return FALSE;
205 }
206 return TRUE;
207 #endif
208 }
209
210 static void
setup_loginuid(gpointer data)211 setup_loginuid (gpointer data)
212 {
213 const char *id = data;
214 int fd;
215
216 fd = open ("/proc/self/loginuid", O_WRONLY);
217 write (fd, id, strlen (id));
218 close (fd);
219 }
220
221 gboolean
spawn_with_login_uid(GDBusMethodInvocation * context,const gchar * argv[],GError ** error)222 spawn_with_login_uid (GDBusMethodInvocation *context,
223 const gchar *argv[],
224 GError **error)
225 {
226 gboolean ret = FALSE;
227 gchar loginuid[20];
228 gint status;
229
230 get_caller_loginuid (context, loginuid, G_N_ELEMENTS (loginuid));
231
232 if (!g_spawn_sync (NULL, (gchar**)argv, NULL, 0, setup_loginuid, loginuid, NULL, NULL, &status, error))
233 goto out;
234 if (!compat_check_exit_status (status, error))
235 goto out;
236
237 ret = TRUE;
238 out:
239 return ret;
240 }
241
242 gint
get_user_groups(const gchar * user,gid_t group,gid_t ** groups)243 get_user_groups (const gchar *user,
244 gid_t group,
245 gid_t **groups)
246 {
247 gint res;
248 gint ngroups;
249
250 ngroups = 0;
251 res = getgrouplist (user, group, NULL, &ngroups);
252
253 g_debug ("user %s has %d groups", user, ngroups);
254 *groups = g_new (gid_t, ngroups);
255 res = getgrouplist (user, group, *groups, &ngroups);
256
257 return res;
258 }
259
260
261 gboolean
get_caller_uid(GDBusMethodInvocation * context,gint * uid)262 get_caller_uid (GDBusMethodInvocation *context,
263 gint *uid)
264 {
265 g_autoptr(GVariant) reply = NULL;
266 g_autoptr(GError) error = NULL;
267
268 reply = g_dbus_connection_call_sync (g_dbus_method_invocation_get_connection (context),
269 "org.freedesktop.DBus",
270 "/org/freedesktop/DBus",
271 "org.freedesktop.DBus",
272 "GetConnectionUnixUser",
273 g_variant_new ("(s)",
274 g_dbus_method_invocation_get_sender (context)),
275 G_VARIANT_TYPE ("(u)"),
276 G_DBUS_CALL_FLAGS_NONE,
277 -1,
278 NULL,
279 &error);
280
281 if (reply == NULL) {
282 g_warning ("Could not talk to message bus to find uid of sender %s: %s",
283 g_dbus_method_invocation_get_sender (context),
284 error->message);
285 return FALSE;
286 }
287
288 g_variant_get (reply, "(u)", uid);
289
290 return TRUE;
291 }
292