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