GCC Code Coverage Report


Directory: src/
File: src/polkit-auth-handler.service.c
Date: 2025-03-14 00:32:36
Exec Total Coverage
Lines: 197 224 87.9%
Functions: 20 22 90.9%
Branches: 59 82 72.0%

Line Branch Exec Source
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 // Copyright (C) 2024 Omar Castro
3 #include "glib-object.h"
4 #include "glib.h"
5 #include "glibconfig.h"
6 #include <stdbool.h>
7 #define _GNU_SOURCE
8 #include <grp.h>
9 #include <pwd.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12
13 #include <unistd.h>
14 #include <string.h>
15 #include <gmodule.h>
16 #include <time.h>
17 #include <fcntl.h>
18 #include "logger.h"
19 #include "app.h"
20 #include "json-glib.extension.h"
21 #include "accepted-actions.enum.h"
22 #include "error-message.dialog.h"
23 #include "polkit-auth-handler.service.h"
24 #include "request-messages.h"
25
26
6/7
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 8 times.
40 G_DEFINE_TYPE(CmdPkAgentPolkitListener, cmd_pk_agent_polkit_listener, POLKIT_AGENT_TYPE_LISTENER)
27
28 typedef enum {
29 IN_QUEUE,
30 AUTHENTICATING,
31 CANCELED,
32 AUTHORIZED
33 } AuthDlgDataStatus;
34
35 typedef struct _AuthDlgData AuthDlgData;
36 struct _AuthDlgData {
37 PolkitAgentSession *session;
38 PolkitActionDescription* action_description;
39 gchar *action_id;
40 gchar *cookie;
41 gchar *message;
42 GTask* task;
43 GList *identities;
44 GError *error;
45
46 AuthDlgDataStatus status;
47
48 GPid cmd_pid;
49 int write_channel_fd;
50 int read_channel_fd;
51 guint read_channel_watcher;
52
53 GIOChannel * write_channel;
54 GIOChannel * read_channel;
55 GString * buffer;
56 GString * active_line;
57
58 JsonParser *parser;
59 JsonObject *root;
60
61 };
62
63 /**
64 * Authentication queue to be used in serial mode
65 * The an authentication goes to the queur if there is one authentication currently being handled
66 */
67 GAsyncQueue * serial_mode_queue = NULL;
68 AuthDlgData * serial_mode_current_authentication = NULL;
69
70 3 bool serie_mode_is_queue_empty(){
71
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
3 return serial_mode_queue == NULL || g_async_queue_length(serial_mode_queue) <= 0;
72 }
73
74 2 void serie_mode_push_auth_to_queue(AuthDlgData *d){
75
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if(serial_mode_queue == NULL){
76 1 serial_mode_queue = g_async_queue_new();
77 }
78 2 g_async_queue_push(serial_mode_queue, d);
79 2 }
80
81 2 AuthDlgData* serie_mode_pop_auth_from_queue(){
82
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if(serial_mode_queue == NULL){
83 serial_mode_queue = g_async_queue_new();
84 }
85 2 return (AuthDlgData *) g_async_queue_pop(serial_mode_queue);
86 }
87
88 static void build_session(AuthDlgData *d);
89 static void spawn_command_for_authentication(AuthDlgData *d);
90
91 23 void auth_dialog_data_write_to_channel ( AuthDlgData *data, const char * message){
92 23 GIOChannel * write_channel = data->write_channel;
93
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 if(data->write_channel == NULL){
94 //gets here when the script exits or there was an error loading it
95 return;
96 }
97 23 log__verbose__writing_to_command_stdin(message);
98 gsize bytes_witten;
99 23 g_io_channel_write_chars(write_channel, message, -1, &bytes_witten, &data->error);
100 23 g_io_channel_write_unichar(write_channel, '\n', &data->error);
101 23 g_io_channel_flush(write_channel, &data->error);
102 }
103
104 18 static void auth_dlg_data_run_and_free_task(AuthDlgData *d){
105 18 GTask *task = d->task;
106
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 9 times.
18 if(task != NULL){
107 9 g_task_return_boolean(task, true);
108 9 g_object_unref(task);
109 9 d->task = NULL;
110 }
111 18 }
112
113 9 static void auth_dlg_data_free(AuthDlgData *d)
114 {
115 9 GError* error = NULL;
116
117 9 auth_dlg_data_run_and_free_task(d);
118 9 g_object_unref(d->session);
119 9 g_free(d->action_id);
120 9 g_free(d->cookie);
121 9 g_free(d->message);
122 9 g_list_free(d->identities);
123 9 g_source_remove (d->read_channel_watcher);
124 9 g_io_channel_shutdown(d->write_channel, TRUE, &error);
125
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if(error){
126 fprintf(stderr, "error closing write channel of pid %d: %s\n", d->cmd_pid, error->message);
127 g_error_free ( error );
128 }
129 9 g_io_channel_unref(d->write_channel);
130 9 g_io_channel_shutdown(d->read_channel, FALSE, &error);
131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if(error){
132 fprintf(stderr, "error closing read channel of pid %d: %s\n", d->cmd_pid, error->message);
133 g_error_free ( error );
134 }
135
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if(d->action_description != NULL){
136 9 g_object_unref(d->action_description);
137 }
138 9 g_io_channel_unref(d->read_channel);
139 9 g_string_free(d->active_line, true);
140 9 g_string_free(d->buffer, true);
141 9 g_object_unref(d->parser);
142 9 g_slice_free(AuthDlgData, d);
143 9 }
144
145 12 static gboolean on_new_input ( GIOChannel *source, [[maybe_unused]] GIOCondition condition, gpointer context )
146 {
147 12 log__verbose__reading_command_stdout();
148 12 AuthDlgData *data = (AuthDlgData *) context;
149 12 GString * buffer = data->buffer;
150 12 GString * active_line = data->active_line;
151
152 12 gboolean newline = FALSE;
153
154 12 GError * error = NULL;
155 gunichar unichar;
156 GIOStatus status;
157
158 12 status = g_io_channel_read_unichar(source, &unichar, &error);
159
160 //when there is nothing to read, status is G_IO_STATUS_AGAIN
161
2/2
✓ Branch 0 taken 539 times.
✓ Branch 1 taken 12 times.
551 while(status == G_IO_STATUS_NORMAL) {
162 539 g_string_append_unichar(buffer, unichar);
163
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 527 times.
539 if( unichar == '\n' ){
164
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if(buffer->len > 1){ //input is not an empty line
165 12 g_debug("received new line: %s", buffer->str);
166 12 g_string_assign(active_line, buffer->str);
167 12 newline=TRUE;
168 }
169 12 log__verbose__received_from_command_stdout(buffer->str);
170 12 g_string_set_size(buffer, 0);
171 }
172 539 status = g_io_channel_read_unichar(source, &unichar, &error);
173 }
174
175
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if(newline){
176 12 fprintf(stderr, "parsing line\n");
177
178
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 if(! json_parser_load_from_data(data->parser,data->active_line->str,data->active_line->len,&error)){
179 fprintf(stderr, "Unable to parse line: %s\n", error->message);
180 g_error_free ( error );
181 } else {
182
183 12 data->root = json_node_get_object(json_parser_get_root(data->parser));
184 12 const char * action = json_object_get_string_member_or_else(data->root, "action", NULL);
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if(action == NULL){
186 fprintf(stderr, "no action defined, ignored");
187
2/3
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
12 } else switch (accepted_action_value_of_str(action)) {
188 1 case AcceptedAction_CANCEL: {
189 1 fprintf(stderr, "action cancel");
190 1 data->status = CANCELED;
191 1 polkit_agent_session_cancel(data->session);
192 }
193 1 break;
194 11 case AcceptedAction_AUTHENTICATE: {
195 11 fprintf(stderr, "action authenticate");
196 11 const char * password = json_object_get_string_member_or_else(data->root, "password", NULL);
197
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if(password != NULL){
198 11 polkit_agent_session_response(data->session, password);
199 }
200 }
201 11 break;
202 default:
203 fprintf(stderr, "unknown action %s \n", action);
204 }
205 }
206 }
207
208 12 return G_SOURCE_CONTINUE;
209 }
210
211 12 static void on_session_completed([[maybe_unused]] PolkitAgentSession* session, gboolean authorized, AuthDlgData* d)
212 {
213 12 bool canceled = d->status == CANCELED;
214 12 log__verbose__polkit_session_completed(authorized, canceled);
215
216
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 if(authorized){
217 8 d->status = AUTHORIZED;
218 16 g_autofree const char* message = request_message_authorization_authorized();
219 8 auth_dialog_data_write_to_channel(d, message);
220 }
221
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
12 if (authorized || canceled) {
222 9 auth_dlg_data_run_and_free_task(d);
223 9 auth_dlg_data_free(d);
224
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
9 if(app__get_auth_handling_mode() == AuthHandlingMode_SERIE){
225
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
3 if(serie_mode_is_queue_empty()){
226 1 serial_mode_current_authentication = NULL;
227 } else {
228 2 AuthDlgData* data = serie_mode_pop_auth_from_queue();
229 2 data->status = AUTHENTICATING;
230 2 serial_mode_current_authentication = data;
231 2 spawn_command_for_authentication(data);
232 2 polkit_agent_session_initiate(data->session);
233 }
234 }
235
236 9 return;
237 }
238 3 g_object_unref(d->session);
239 3 d->session = NULL;
240 6 g_autofree const char* message = request_message_authorization_not_authorized();
241 3 auth_dialog_data_write_to_channel(d, message);
242 3 build_session(d);
243 3 polkit_agent_session_initiate(d->session);
244
245 }
246
247 12 static void on_session_request([[maybe_unused]] PolkitAgentSession* session, gchar *req, gboolean visibility, AuthDlgData *d)
248 {
249 12 log__verbose__polkit_session_request(req, visibility);
250 24 g_autofree const char *write_message = request_message_request_password(req, d->message, d->action_description);
251 12 auth_dialog_data_write_to_channel(d, write_message);
252 12 }
253
254 static void on_session_show_error([[maybe_unused]] PolkitAgentSession* session, gchar *text, [[maybe_unused]] AuthDlgData* d)
255 {
256
257 log__verbose__polkit_session_show_error(text);
258 }
259
260 static void on_session_show_info([[maybe_unused]] PolkitAgentSession *session, gchar *text, [[maybe_unused]] AuthDlgData* d)
261 {
262 log__verbose__polkit_session_show_info(text);
263 }
264
265 12 static void build_session(AuthDlgData *d){
266
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (G_UNLIKELY(d->session)) {
267 g_signal_handlers_disconnect_matched(d->session, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, d);
268 polkit_agent_session_cancel(d->session);
269 g_object_unref(d->session);
270 }
271
272 12 PolkitIdentity *id = (PolkitIdentity *)d->identities->data;
273 12 d->session = polkit_agent_session_new(id, d->cookie);
274 12 g_signal_connect(d->session, "completed", G_CALLBACK(on_session_completed), d);
275 12 g_signal_connect(d->session, "request", G_CALLBACK(on_session_request), d);
276 12 g_signal_connect(d->session, "show-error", G_CALLBACK(on_session_show_error), d);
277 12 g_signal_connect(d->session, "show-info", G_CALLBACK(on_session_show_info), d);
278
279 12 }
280
281 9 static void spawn_command_for_authentication(AuthDlgData *d){
282 9 GError *error = NULL;
283 int cmd_input_fd;
284 int cmd_output_fd;
285
286 9 char ** const cmd_argv = app__get_cmd_line_argv();
287
288
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
9 if ( ! g_spawn_async_with_pipes ( NULL, cmd_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &(d->cmd_pid), &(cmd_input_fd), &(cmd_output_fd), NULL, &error)) {
289 show_error_message_format("%s", error->message);
290 polkit_agent_session_cancel(d->session);
291 return;
292 }
293 9 d->read_channel_fd = cmd_output_fd;
294 9 d->write_channel_fd = cmd_input_fd;
295
296 9 int retval = fcntl( d->read_channel_fd, F_SETFL, fcntl(d->read_channel_fd, F_GETFL) | O_NONBLOCK);
297
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (retval != 0){
298 fprintf(stderr,"Error setting non block on output pipe\n");
299 kill(d->cmd_pid, SIGTERM);
300 polkit_agent_session_cancel(d->session);
301 return;
302 }
303
304 9 d->read_channel = g_io_channel_unix_new(d->read_channel_fd);
305 9 d->write_channel = g_io_channel_unix_new(d->write_channel_fd);
306 9 d->read_channel_watcher = g_io_add_watch(d->read_channel, G_IO_IN, on_new_input, d);
307 }
308
309
310 /**
311 * Authentication request handler of PolkitAgentListener.
312 *
313 */
314 9 static void initiate_authentication(PolkitAgentListener *listener,
315 const gchar *action_id,
316 const gchar *message,
317 const gchar *icon_name,
318 PolkitDetails *details,
319 const gchar *cookie,
320 GList *identities,
321 GCancellable *cancellable,
322 GAsyncReadyCallback callback,
323 gpointer user_data)
324 {
325
326 9 log__verbose__init_polkit_authentication(action_id, message, icon_name, cookie);
327 9 log__verbose__polkit_auth_identities(identities);
328 9 log__verbose__polkit_auth_details(details);
329
330 9 AuthDlgData *d = g_slice_new0(AuthDlgData);
331
332 9 GError *error = NULL;
333 9 PolkitAuthority* authority = polkit_authority_get_sync(NULL, &error);
334
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if(error == NULL){
335 9 GList* actions = polkit_authority_enumerate_actions_sync (authority,NULL,&error);
336
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if(error == NULL){
337
2/2
✓ Branch 0 taken 1773 times.
✓ Branch 1 taken 9 times.
1782 for(GList *elem = actions; elem; elem = elem->next) {
338 1773 PolkitActionDescription* action_description = elem->data;
339
2/2
✓ Branch 0 taken 738 times.
✓ Branch 1 taken 1035 times.
1773 if(d->action_description != NULL){
340 // continue to g_object_unref the remaining elements on the list, as they are required
341 // before freeing the `actions` GList
342 738 g_object_unref(action_description);
343 738 continue;
344 }
345
346 1035 const gchar * action_description_action_id = polkit_action_description_get_action_id(action_description);
347
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1026 times.
1035 if(strcmp(action_description_action_id, action_id) == 0){
348 9 log__verbose__polkit_action_description(action_description);
349 9 g_object_ref(action_description);
350 9 d->action_description = action_description;
351 }
352
353 1035 g_object_unref(action_description);
354 }
355 9 g_list_free(actions);
356 }
357 9 g_object_unref(authority);
358 }
359
360 9 d->task = g_task_new(listener, cancellable, callback, user_data);
361 9 d->action_id = g_strdup(action_id);
362 9 d->message = g_strdup(message);
363 9 d->cookie = g_strdup(cookie);
364 9 d->identities = g_list_copy(identities);
365 9 d->buffer = g_string_sized_new (1024);
366 9 d->active_line = g_string_sized_new (1024);
367 9 d->parser = json_parser_new ();
368 9 build_session(d);
369
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 3 times.
9 if(app__get_auth_handling_mode() == AuthHandlingMode_PARALLEL){
370 6 spawn_command_for_authentication(d);
371 6 polkit_agent_session_initiate(d->session);
372
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 } else if(serial_mode_current_authentication != NULL){
373 2 d->status = IN_QUEUE;
374 2 serie_mode_push_auth_to_queue(d);
375 } else {
376 1 d->status = AUTHENTICATING;
377 1 serial_mode_current_authentication = d;
378 1 spawn_command_for_authentication(d);
379 1 polkit_agent_session_initiate(d->session);
380 }
381 9 }
382
383 9 static gboolean initiate_authentication_finish(
384 [[maybe_unused]] PolkitAgentListener *listener,
385 GAsyncResult *res,
386 GError **error)
387 {
388 9 log__verbose__finish_polkit_authentication();
389 9 return g_task_propagate_boolean(G_TASK(res), error);
390 }
391
392 6 static void cmd_pk_agent_polkit_listener_finalize(GObject *object)
393 {
394 6 log__verbose__finalize_polkit_listener();
395
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 g_return_if_fail(object != NULL);
396
4/8
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
6 g_return_if_fail(CMD_PK_AGENT_IS_POLKIT_LISTENER(object));
397 6 G_OBJECT_CLASS(cmd_pk_agent_polkit_listener_parent_class)->finalize(object);
398 }
399
400 4 static void cmd_pk_agent_polkit_listener_class_init(CmdPkAgentPolkitListenerClass *klass)
401 {
402 4 log__verbose__init_polkit_listener();
403 GObjectClass *g_object_class;
404 PolkitAgentListenerClass* pkal_class;
405 4 g_object_class = G_OBJECT_CLASS(klass);
406 4 g_object_class->finalize = cmd_pk_agent_polkit_listener_finalize;
407
408 4 pkal_class = POLKIT_AGENT_LISTENER_CLASS(klass);
409 4 pkal_class->initiate_authentication = initiate_authentication;
410 4 pkal_class->initiate_authentication_finish = initiate_authentication_finish;
411 4 }
412
413 6 static void cmd_pk_agent_polkit_listener_init([[maybe_unused]] CmdPkAgentPolkitListener *self)
414 {
415 6 }
416
417 6 PolkitAgentListener* cmd_pk_agent_polkit_listener_new(void)
418 {
419 6 return g_object_new(CMD_PK_AGENT_TYPE_POLKIT_LISTENER, NULL);
420 }
421
422
423