GCC Code Coverage Report


Directory: src/
File: src/polkit-auth-handler.service.c
Date: 2025-07-02 22:56:24
Exec Total Coverage
Lines: 197 228 86.4%
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, AuthDlgData* d)
255 {
256 log__verbose__polkit_session_show_error(text);
257 g_autofree const char* message = request_message_show_error(text);
258 auth_dialog_data_write_to_channel(d, message);
259 }
260
261 static void on_session_show_info([[maybe_unused]] PolkitAgentSession *session, gchar *text, AuthDlgData* d)
262 {
263 log__verbose__polkit_session_show_info(text);
264 g_autofree const char* message = request_message_show_info(text);
265 auth_dialog_data_write_to_channel(d, message);
266 }
267
268 12 static void build_session(AuthDlgData *d){
269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (G_UNLIKELY(d->session)) {
270 g_signal_handlers_disconnect_matched(d->session, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, d);
271 polkit_agent_session_cancel(d->session);
272 g_object_unref(d->session);
273 }
274
275 12 PolkitIdentity *id = (PolkitIdentity *)d->identities->data;
276 12 d->session = polkit_agent_session_new(id, d->cookie);
277 12 g_signal_connect(d->session, "completed", G_CALLBACK(on_session_completed), d);
278 12 g_signal_connect(d->session, "request", G_CALLBACK(on_session_request), d);
279 12 g_signal_connect(d->session, "show-error", G_CALLBACK(on_session_show_error), d);
280 12 g_signal_connect(d->session, "show-info", G_CALLBACK(on_session_show_info), d);
281
282 12 }
283
284 9 static void spawn_command_for_authentication(AuthDlgData *d){
285 9 GError *error = NULL;
286 int cmd_input_fd;
287 int cmd_output_fd;
288
289 9 char ** const cmd_argv = app__get_cmd_line_argv();
290
291
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)) {
292 show_error_message_format("%s", error->message);
293 polkit_agent_session_cancel(d->session);
294 return;
295 }
296 9 d->read_channel_fd = cmd_output_fd;
297 9 d->write_channel_fd = cmd_input_fd;
298
299 9 int retval = fcntl( d->read_channel_fd, F_SETFL, fcntl(d->read_channel_fd, F_GETFL) | O_NONBLOCK);
300
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (retval != 0){
301 fprintf(stderr,"Error setting non block on output pipe\n");
302 kill(d->cmd_pid, SIGTERM);
303 polkit_agent_session_cancel(d->session);
304 return;
305 }
306
307 9 d->read_channel = g_io_channel_unix_new(d->read_channel_fd);
308 9 d->write_channel = g_io_channel_unix_new(d->write_channel_fd);
309 9 d->read_channel_watcher = g_io_add_watch(d->read_channel, G_IO_IN, on_new_input, d);
310 }
311
312
313 /**
314 * Authentication request handler of PolkitAgentListener.
315 *
316 */
317 9 static void initiate_authentication(PolkitAgentListener *listener,
318 const gchar *action_id,
319 const gchar *message,
320 const gchar *icon_name,
321 PolkitDetails *details,
322 const gchar *cookie,
323 GList *identities,
324 GCancellable *cancellable,
325 GAsyncReadyCallback callback,
326 gpointer user_data)
327 {
328
329 9 log__verbose__init_polkit_authentication(action_id, message, icon_name, cookie);
330 9 log__verbose__polkit_auth_identities(identities);
331 9 log__verbose__polkit_auth_details(details);
332
333 9 AuthDlgData *d = g_slice_new0(AuthDlgData);
334
335 9 GError *error = NULL;
336 9 PolkitAuthority* authority = polkit_authority_get_sync(NULL, &error);
337
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if(error == NULL){
338 9 GList* actions = polkit_authority_enumerate_actions_sync (authority,NULL,&error);
339
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if(error == NULL){
340
2/2
✓ Branch 0 taken 1773 times.
✓ Branch 1 taken 9 times.
1782 for(GList *elem = actions; elem; elem = elem->next) {
341 1773 PolkitActionDescription* action_description = elem->data;
342
2/2
✓ Branch 0 taken 738 times.
✓ Branch 1 taken 1035 times.
1773 if(d->action_description != NULL){
343 // continue to g_object_unref the remaining elements on the list, as they are required
344 // before freeing the `actions` GList
345 738 g_object_unref(action_description);
346 738 continue;
347 }
348
349 1035 const gchar * action_description_action_id = polkit_action_description_get_action_id(action_description);
350
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1026 times.
1035 if(strcmp(action_description_action_id, action_id) == 0){
351 9 log__verbose__polkit_action_description(action_description);
352 9 g_object_ref(action_description);
353 9 d->action_description = action_description;
354 }
355
356 1035 g_object_unref(action_description);
357 }
358 9 g_list_free(actions);
359 }
360 9 g_object_unref(authority);
361 }
362
363 9 d->task = g_task_new(listener, cancellable, callback, user_data);
364 9 d->action_id = g_strdup(action_id);
365 9 d->message = g_strdup(message);
366 9 d->cookie = g_strdup(cookie);
367 9 d->identities = g_list_copy(identities);
368 9 d->buffer = g_string_sized_new (1024);
369 9 d->active_line = g_string_sized_new (1024);
370 9 d->parser = json_parser_new ();
371 9 build_session(d);
372
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 3 times.
9 if(app__get_auth_handling_mode() == AuthHandlingMode_PARALLEL){
373 6 spawn_command_for_authentication(d);
374 6 polkit_agent_session_initiate(d->session);
375
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 } else if(serial_mode_current_authentication != NULL){
376 2 d->status = IN_QUEUE;
377 2 serie_mode_push_auth_to_queue(d);
378 } else {
379 1 d->status = AUTHENTICATING;
380 1 serial_mode_current_authentication = d;
381 1 spawn_command_for_authentication(d);
382 1 polkit_agent_session_initiate(d->session);
383 }
384 9 }
385
386 9 static gboolean initiate_authentication_finish(
387 [[maybe_unused]] PolkitAgentListener *listener,
388 GAsyncResult *res,
389 GError **error)
390 {
391 9 log__verbose__finish_polkit_authentication();
392 9 return g_task_propagate_boolean(G_TASK(res), error);
393 }
394
395 6 static void cmd_pk_agent_polkit_listener_finalize(GObject *object)
396 {
397 6 log__verbose__finalize_polkit_listener();
398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 g_return_if_fail(object != NULL);
399
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));
400 6 G_OBJECT_CLASS(cmd_pk_agent_polkit_listener_parent_class)->finalize(object);
401 }
402
403 4 static void cmd_pk_agent_polkit_listener_class_init(CmdPkAgentPolkitListenerClass *klass)
404 {
405 4 log__verbose__init_polkit_listener();
406 GObjectClass *g_object_class;
407 PolkitAgentListenerClass* pkal_class;
408 4 g_object_class = G_OBJECT_CLASS(klass);
409 4 g_object_class->finalize = cmd_pk_agent_polkit_listener_finalize;
410
411 4 pkal_class = POLKIT_AGENT_LISTENER_CLASS(klass);
412 4 pkal_class->initiate_authentication = initiate_authentication;
413 4 pkal_class->initiate_authentication_finish = initiate_authentication_finish;
414 4 }
415
416 6 static void cmd_pk_agent_polkit_listener_init([[maybe_unused]] CmdPkAgentPolkitListener *self)
417 {
418 6 }
419
420 6 PolkitAgentListener* cmd_pk_agent_polkit_listener_new(void)
421 {
422 6 return g_object_new(CMD_PK_AGENT_TYPE_POLKIT_LISTENER, NULL);
423 }
424
425
426