1 /* profile.c
2  * Dialog box for profiles editing
3  * Stig Bjorlykke <stig@bjorlykke.org>, 2008
4  *
5  * Wireshark - Network traffic analyzer
6  * By Gerald Combs <gerald@wireshark.org>
7  * Copyright 1998 Gerald Combs
8  *
9  * SPDX-License-Identifier: GPL-2.0-or-later
10  */
11 
12 #include "config.h"
13 
14 #include <string.h>
15 #include <errno.h>
16 
17 #include <glib.h>
18 
19 #include <wsutil/filesystem.h>
20 
21 #include "profile.h"
22 
23 #include "ui/simple_dialog.h"
24 #include "ui/recent.h"
25 
26 #include <wsutil/file_util.h>
27 #include <wsutil/ws_assert.h>
28 
29 static GList *current_profiles = NULL;
30 static GList *edited_profiles = NULL;
31 
32 #define PROF_OPERATION_NEW  1
33 #define PROF_OPERATION_EDIT 2
34 
35 GList * current_profile_list(void) {
36     return g_list_first(current_profiles);
37 }
38 
39 GList * edited_profile_list(void) {
40     return g_list_first(edited_profiles);
41 }
42 
43 static GList *
44 add_profile_entry(GList *fl, const char *profilename, const char *reference, int status,
45         gboolean is_global, gboolean from_global, gboolean is_import)
46 {
47     profile_def *profile;
48 
49     profile = g_new0(profile_def, 1);
50     profile->name = g_strdup(profilename);
51     profile->reference = g_strdup(reference);
52     profile->status = status;
53     profile->is_global = is_global;
54     profile->from_global = from_global;
55     profile->is_import = is_import;
56     return g_list_append(fl, profile);
57 }
58 
59 static GList *
60 remove_profile_entry(GList *fl, GList *fl_entry)
61 {
62     profile_def *profile;
63 
64     profile = (profile_def *) fl_entry->data;
65     g_free(profile->name);
66     g_free(profile->reference);
67     g_free(profile);
68     return g_list_remove_link(fl, fl_entry);
69 }
70 
71 const gchar *
72 get_profile_parent (const gchar *profilename)
73 {
74     GList *fl_entry = g_list_first(edited_profiles);
75     guint no_edited = g_list_length(edited_profiles);
76     profile_def *profile;
77     guint i;
78 
79     if (fl_entry) {
80         /* We have edited profiles, find parent */
81         for (i = 0; i < no_edited; i++) {
82             while (fl_entry) {
83                 profile = (profile_def *) fl_entry->data;
84                 if (strcmp (profile->name, profilename) == 0) {
85                     if ((profile->status == PROF_STAT_NEW) ||
86                             (profile->reference == NULL)) {
87                         /* Copy from a new profile */
88                         return NULL;
89                     } else {
90                         /* Found a parent, use this */
91                         profilename = profile->reference;
92                     }
93                 }
94                 fl_entry = g_list_next(fl_entry);
95             }
96             fl_entry = g_list_first(edited_profiles);
97         }
98     }
99 
100     return profilename;
101 }
102 
103 gchar *apply_profile_changes(void)
104 {
105     char        *pf_dir_path, *pf_dir_path2, *pf_filename;
106     GList       *fl1, *fl2;
107     profile_def *profile1, *profile2;
108     gboolean     found;
109     gchar       *err_msg;
110 
111     /* First validate all profile names */
112     fl1 = edited_profile_list();
113     while (fl1) {
114         profile1 = (profile_def *) fl1->data;
115         g_strstrip(profile1->name);
116         if ((err_msg = profile_name_is_valid(profile1->name)) != NULL) {
117             gchar *message = g_strdup_printf("%s\nProfiles unchanged.", err_msg);
118             g_free(err_msg);
119             return message;
120         }
121         fl1 = g_list_next(fl1);
122     }
123 
124     /* Write recent file for current profile before copying or renaming */
125     write_profile_recent();
126 
127     /* Then do all copy profiles */
128     fl1 = edited_profile_list();
129     while (fl1) {
130         profile1 = (profile_def *) fl1->data;
131         g_strstrip(profile1->name);
132         if (profile1->status == PROF_STAT_COPY) {
133             if (create_persconffile_profile(profile1->name, &pf_dir_path) == -1) {
134                 err_msg = g_strdup_printf("Can't create directory\n\"%s\":\n%s.",
135                         pf_dir_path, g_strerror(errno));
136 
137                 g_free(pf_dir_path);
138                 return err_msg;
139             }
140             profile1->status = PROF_STAT_EXISTS;
141 
142             if (profile1->reference) {
143                 if (copy_persconffile_profile(profile1->name, profile1->reference, profile1->from_global,
144                             &pf_filename, &pf_dir_path, &pf_dir_path2) == -1) {
145                     simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
146                             "Can't copy file \"%s\" in directory\n\"%s\" to\n\"%s\":\n%s.",
147                             pf_filename, pf_dir_path2, pf_dir_path, g_strerror(errno));
148 
149                     g_free(pf_filename);
150                     g_free(pf_dir_path);
151                     g_free(pf_dir_path2);
152                 }
153             }
154 
155             g_free (profile1->reference);
156             profile1->reference = g_strdup(profile1->name);
157         }
158         fl1 = g_list_next(fl1);
159     }
160 
161 
162     /* Then create new and rename changed */
163     fl1 = edited_profile_list();
164     while (fl1) {
165         profile1 = (profile_def *) fl1->data;
166         g_strstrip(profile1->name);
167         if (profile1->status == PROF_STAT_NEW) {
168             /* We do not create a directory for the default profile */
169             if (strcmp(profile1->name, DEFAULT_PROFILE)!=0  && ! profile1->is_import) {
170                 if (create_persconffile_profile(profile1->name, &pf_dir_path) == -1) {
171                     simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
172                             "Can't create directory\n\"%s\":\n%s.",
173                             pf_dir_path, g_strerror(errno));
174 
175                     g_free(pf_dir_path);
176                 }
177                 profile1->status = PROF_STAT_EXISTS;
178 
179                 g_free (profile1->reference);
180                 profile1->reference = g_strdup(profile1->name);
181             /* correctly apply imports as existing profiles */
182             } else if (profile1->is_import) {
183                 profile1->status = PROF_STAT_EXISTS;
184                 g_free (profile1->reference);
185                 profile1->reference = g_strdup(profile1->name);
186                 profile1->is_import = FALSE;
187             }
188         } else if (profile1->status == PROF_STAT_CHANGED) {
189             if (strcmp(profile1->reference, profile1->name)!=0) {
190                 /* Rename old profile directory to new */
191                 if (rename_persconffile_profile(profile1->reference, profile1->name,
192                             &pf_dir_path, &pf_dir_path2) == -1) {
193                     simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
194                             "Can't rename directory\n\"%s\" to\n\"%s\":\n%s.",
195                             pf_dir_path, pf_dir_path2, g_strerror(errno));
196 
197                     g_free(pf_dir_path);
198                     g_free(pf_dir_path2);
199                 }
200                 profile1->status = PROF_STAT_EXISTS;
201             }
202         }
203         fl1 = g_list_next(fl1);
204     }
205 
206     /* Last remove deleted */
207     fl1 = current_profile_list();
208     while (fl1) {
209         found = FALSE;
210         profile1 = (profile_def *) fl1->data;
211         fl2 = edited_profile_list();
212         while (fl2) {
213             profile2 = (profile_def *) fl2->data;
214             if (!profile2->is_global) {
215                 if (strcmp(profile1->name, profile2->name)==0) {
216                     /* Profile exists in both lists */
217                     found = TRUE;
218                 } else if (strcmp(profile1->name, profile2->reference)==0) {
219                     /* Profile has been renamed, update reference to the new name */
220                     g_free (profile2->reference);
221                     profile2->reference = g_strdup(profile2->name);
222                     found = TRUE;
223                 }
224             }
225             fl2 = g_list_next(fl2);
226         }
227         if (!found) {
228             /* Exists in existing list and not in edited, this is a deleted profile */
229             if (delete_persconffile_profile(profile1->name, &pf_dir_path) == -1) {
230                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
231                         "Can't delete profile directory\n\"%s\":\n%s.",
232                         pf_dir_path, g_strerror(errno));
233 
234                 g_free(pf_dir_path);
235             }
236         }
237         fl1 = g_list_next(fl1);
238     }
239 
240     copy_profile_list();
241     return NULL;
242 }
243 
244 GList *
245 add_to_profile_list(const char *name, const char *expression, int status,
246         gboolean is_global, gboolean from_global, gboolean is_imported)
247 {
248     edited_profiles = add_profile_entry(edited_profiles, name, expression, status,
249             is_global, from_global, is_imported);
250 
251     return g_list_last(edited_profiles);
252 }
253 
254 void
255 remove_from_profile_list(GList *fl_entry)
256 {
257     edited_profiles = remove_profile_entry(edited_profiles, fl_entry);
258 }
259 
260 void
261 empty_profile_list(gboolean edit_list)
262 {
263     GList **flpp;
264 
265     if (edit_list) {
266         flpp = &edited_profiles;
267 
268         while(*flpp) {
269             *flpp = remove_profile_entry(*flpp, g_list_first(*flpp));
270         }
271 
272         ws_assert(g_list_length(*flpp) == 0);
273         if ( ! edited_profiles )
274             edited_profiles = NULL;
275     }
276 
277     flpp = &current_profiles;
278 
279     while(*flpp) {
280         *flpp = remove_profile_entry(*flpp, g_list_first(*flpp));
281     }
282 
283     ws_assert(g_list_length(*flpp) == 0);
284     if ( ! current_profiles )
285         current_profiles = NULL;
286 }
287 
288 void
289 copy_profile_list(void)
290 {
291     GList      *flp_src;
292     profile_def *profile;
293 
294     flp_src = edited_profiles;
295 
296     /* throw away the "old" destination list - a NULL list is ok here */
297     empty_profile_list(FALSE);
298 
299     /* copy the list entries */
300     while(flp_src) {
301         profile = (profile_def *)(flp_src)->data;
302 
303         current_profiles = add_profile_entry(current_profiles, profile->name,
304                 profile->reference, profile->status,
305                 profile->is_global, profile->from_global, FALSE);
306         flp_src = g_list_next(flp_src);
307     }
308 }
309 
310 void
311 init_profile_list(void)
312 {
313     WS_DIR        *dir;             /* scanned directory */
314     WS_DIRENT     *file;            /* current file */
315     const gchar   *name;
316     GList         *local_profiles = NULL;
317     GList         *global_profiles = NULL;
318     GList         *iter;
319     gchar         *profiles_dir, *filename;
320 
321     empty_profile_list(TRUE);
322 
323     /* Default entry */
324     add_to_profile_list(DEFAULT_PROFILE, DEFAULT_PROFILE, PROF_STAT_DEFAULT, FALSE, FALSE, FALSE);
325 
326     /* Local (user) profiles */
327     profiles_dir = get_profiles_dir();
328     if ((dir = ws_dir_open(profiles_dir, 0, NULL)) != NULL) {
329         while ((file = ws_dir_read_name(dir)) != NULL) {
330             name = ws_dir_get_name(file);
331             filename = g_strdup_printf ("%s%s%s", profiles_dir, G_DIR_SEPARATOR_S, name);
332 
333             if (test_for_directory(filename) == EISDIR) {
334                 local_profiles = g_list_prepend(local_profiles, g_strdup(name));
335             }
336             g_free (filename);
337         }
338         ws_dir_close (dir);
339     }
340     g_free(profiles_dir);
341 
342     local_profiles = g_list_sort(local_profiles, (GCompareFunc)g_ascii_strcasecmp);
343     for (iter = g_list_first(local_profiles); iter; iter = g_list_next(iter)) {
344         name = (gchar *)iter->data;
345         add_to_profile_list(name, name, PROF_STAT_EXISTS, FALSE, FALSE, FALSE);
346     }
347     g_list_free_full(local_profiles, g_free);
348 
349     /* Global profiles */
350     profiles_dir = get_global_profiles_dir();
351     if ((dir = ws_dir_open(profiles_dir, 0, NULL)) != NULL) {
352         while ((file = ws_dir_read_name(dir)) != NULL) {
353             name = ws_dir_get_name(file);
354             filename = g_strdup_printf ("%s%s%s", profiles_dir, G_DIR_SEPARATOR_S, name);
355 
356             if (test_for_directory(filename) == EISDIR) {
357                 global_profiles = g_list_prepend(global_profiles, g_strdup(name));
358             }
359             g_free (filename);
360         }
361         ws_dir_close (dir);
362     }
363     g_free(profiles_dir);
364 
365     global_profiles = g_list_sort(global_profiles, (GCompareFunc)g_ascii_strcasecmp);
366     for (iter = g_list_first(global_profiles); iter; iter = g_list_next(iter)) {
367         name = (gchar *)iter->data;
368         add_to_profile_list(name, name, PROF_STAT_EXISTS, TRUE, TRUE, FALSE);
369     }
370     g_list_free_full(global_profiles, g_free);
371 
372     /* Make the current list and the edited list equal */
373     copy_profile_list ();
374 }
375 
376 gchar *
377 profile_name_is_valid(const gchar *name)
378 {
379     gchar *reason = NULL;
380     gchar *message;
381 
382 #ifdef _WIN32
383     char *invalid_dir_char = "\\/:*?\"<>|";
384     gboolean invalid = FALSE;
385     int i;
386 
387     for (i = 0; i < 9; i++) {
388         if (strchr(name, invalid_dir_char[i])) {
389             /* Invalid character in directory */
390             invalid = TRUE;
391         }
392     }
393     if (name[0] == '.' || name[strlen(name)-1] == '.') {
394         /* Profile name cannot start or end with period */
395         invalid = TRUE;
396     }
397     if (invalid) {
398         reason = g_strdup_printf("start or end with period (.), or contain any of the following characters:\n"
399                 "   \\ / : * ? \" &lt; &gt; |");
400     }
401 #else
402     if (strchr(name, '/')) {
403         /* Invalid character in directory */
404         reason = g_strdup_printf("contain the '/' character.");
405     }
406 #endif
407 
408     if (reason) {
409         message = g_strdup_printf("A profile name cannot %s", reason);
410         g_free(reason);
411         return message;
412     }
413 
414     return NULL;
415 }
416 
417 gboolean delete_current_profile(void) {
418     const gchar *name = get_profile_name();
419     char        *pf_dir_path;
420 
421     if (profile_exists(name, FALSE) && strcmp (name, DEFAULT_PROFILE) != 0) {
422         if (delete_persconffile_profile(name, &pf_dir_path) == -1) {
423             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
424                     "Can't delete profile directory\n\"%s\":\n%s.",
425                     pf_dir_path, g_strerror(errno));
426 
427             g_free(pf_dir_path);
428         } else {
429             return TRUE;
430         }
431     }
432     return FALSE;
433 }
434