GCC Code Coverage Report


Directory: ./
File: panels/system/cc-password-utils.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 97 0.0%
Functions: 0 7 0.0%
Branches: 0 74 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (C) 2024 Red Hat, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include "cc-password-utils.h"
20
21 #include <gio/gio.h>
22 #include <glib.h>
23 #include <glob.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #define DICEWARE_CORPUS_GLOB "/usr/share/dict/*"
28 #define SPECIAL_CHARACTERS "-!\"#$%&()*,./:;?@[]^_`{|}~+<=>"
29 #define WORD_SEPARATORS " -_&+,;:."
30
31 static char *
32 get_word_at_line (const char *file_path,
33 guint line_number)
34 {
35 g_autoptr (GError) error = NULL;
36 g_autoptr (GFile) file = NULL;
37 g_autoptr (GFileInputStream) input_stream = NULL;
38 g_autoptr (GDataInputStream) data = NULL;
39 g_autofree char *line = NULL;
40 gsize length;
41 guint line_count = 0;
42
43 file = g_file_new_for_path (file_path);
44
45 input_stream = g_file_read (file, NULL, &error);
46
47 if (error != NULL)
48 {
49 g_warning ("Failed to open file: %s", error->message);
50 return NULL;
51 }
52
53 data = g_data_input_stream_new (G_INPUT_STREAM (input_stream));
54
55 while ((line = g_data_input_stream_read_line_utf8 (data, &length, NULL, &error)) != NULL)
56 {
57 if (line_count == line_number)
58 return g_str_to_ascii (line, NULL);
59
60 g_clear_pointer (&line, g_free);
61 line_count++;
62 }
63
64 if (error != NULL)
65 {
66 g_warning ("Failed to read file: %s", error->message);
67 return NULL;
68 }
69
70 if (line_count == 0)
71 {
72 g_warning ("File is empty");
73 return NULL;
74 }
75
76 if (line_number >= line_count)
77 return get_word_at_line (file_path, line_number % line_count);
78
79 return NULL;
80 }
81
82 static gboolean
83 is_word_separator (char character)
84 {
85 return strchr (WORD_SEPARATORS, character) != NULL;
86 }
87
88 static char
89 generate_word_separator (void)
90 {
91 return WORD_SEPARATORS[g_random_int_range (0, strlen (WORD_SEPARATORS))];
92 }
93
94 static char
95 generate_special_character (void)
96 {
97 return SPECIAL_CHARACTERS[g_random_int_range (0, strlen (SPECIAL_CHARACTERS))];
98 }
99
100 static char
101 generate_digit (void)
102 {
103 return g_random_int_range (0, 10) + '0';
104 }
105
106 static char *
107 get_random_file_from_glob (const char *pattern)
108 {
109 glob_t glob_result;
110 int ret = 0;
111 int index;
112 g_autofree char *file = NULL;
113
114 ret = glob (pattern, 0, NULL, &glob_result);
115 if (ret != 0)
116 goto out;
117
118 if (glob_result.gl_pathc == 0)
119 goto out;
120
121 index = g_random_int_range (0, glob_result.gl_pathc);
122 file = g_strdup (glob_result.gl_pathv[index]);
123
124 out:
125 globfree (&glob_result);
126 return g_steal_pointer (&file);
127 }
128
129 char *
130 cc_generate_password (void)
131 {
132 g_autoptr(GString) password_string = NULL;
133 g_autofree char *password = NULL;
134 static const size_t min_number_of_words = 2;
135 size_t i;
136 char *p = NULL;
137 gboolean needs_digit = TRUE;
138 gboolean needs_special_character = TRUE;
139 gboolean needs_uppercase = TRUE;
140 gboolean last_character_trimmable = TRUE;
141
142 password_string = g_string_new (NULL);
143
144 i = 0;
145 while (password_string->len < 16 || i < min_number_of_words)
146 {
147 g_autofree char *file_path = NULL;
148 int word_offset = g_random_int ();
149 g_autofree char *word = NULL;
150
151 file_path = get_random_file_from_glob (DICEWARE_CORPUS_GLOB);
152
153 if (!file_path)
154 return NULL;
155
156 word = get_word_at_line (file_path, word_offset);
157
158 if (word == NULL)
159 return NULL;
160
161 if (strlen (word) > 10)
162 continue;
163
164 g_string_append (password_string, word);
165 g_string_append_c (password_string, ' ');
166 i++;
167 }
168
169 password = g_string_free_and_steal (g_steal_pointer (&password_string));
170
171 while (needs_uppercase || needs_digit || needs_special_character || strstr (password, " ") != NULL)
172 {
173 for (p = password; *p != '\0'; p++)
174 {
175 if (p == password || is_word_separator (p[-1]))
176 {
177 if (g_random_int_range (0, 2) == 0)
178 {
179 *p = g_ascii_toupper (*p);
180 needs_uppercase = FALSE;
181 }
182 }
183
184 if (!is_word_separator (*p))
185 continue;
186
187 if (needs_digit && g_random_int_range (0, strlen (password)) == 0)
188 {
189 *p = generate_digit ();
190 needs_digit = FALSE;
191
192 if (p[1] == '\0')
193 last_character_trimmable = FALSE;
194 }
195 else if (needs_special_character && g_random_int_range (0, strlen (password)) == 0)
196 {
197 *p = generate_special_character ();
198 needs_special_character = FALSE;
199
200 if (p[1] == '\0')
201 last_character_trimmable = FALSE;
202 }
203 else if (!needs_digit && !needs_special_character)
204 {
205 *p = generate_word_separator ();
206 }
207 }
208 }
209
210 if (last_character_trimmable)
211 password[strlen (password) - 1] = '\0';
212
213 return g_steal_pointer (&password);
214 }
215