diff options
Diffstat (limited to 'src/firecfg/desktop_files.c')
-rw-r--r-- | src/firecfg/desktop_files.c | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/src/firecfg/desktop_files.c b/src/firecfg/desktop_files.c new file mode 100644 index 000000000..eaedc4b64 --- /dev/null +++ b/src/firecfg/desktop_files.c | |||
@@ -0,0 +1,265 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2017 Firejail Authors | ||
3 | * | ||
4 | * This file is part of firejail project | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | |||
21 | #include "firecfg.h" | ||
22 | |||
23 | // look for a profile file in /etc/firejail diectory and in homedir/.config/firejail directory | ||
24 | static int have_profile(const char *filename, const char *homedir) { | ||
25 | assert(filename); | ||
26 | assert(homedir); | ||
27 | |||
28 | if (arg_debug) | ||
29 | printf("checking profile for %s\n", filename); | ||
30 | |||
31 | // remove .desktop extension; if file name starts with org.gnome... remove it | ||
32 | char *f1; | ||
33 | if (strncmp(filename, "org.gnome.", 10) == 0) | ||
34 | f1 = strdup(filename + 10); | ||
35 | else | ||
36 | f1 = strdup(filename); | ||
37 | if (!f1) | ||
38 | errExit("strdup"); | ||
39 | f1[strlen(f1) - 8] = '\0'; | ||
40 | if (arg_debug) | ||
41 | printf("looking for a profile for %s - %s\n", filename, f1); | ||
42 | |||
43 | // build profile name | ||
44 | char *profname1; | ||
45 | char *profname2; | ||
46 | if (asprintf(&profname1, "%s/%s.profile", SYSCONFDIR, f1) == -1) | ||
47 | errExit("asprintf"); | ||
48 | if (asprintf(&profname2, "%s/.config/firejail/%s.profile", homedir, f1) == -1) | ||
49 | errExit("asprintf"); | ||
50 | |||
51 | int rv = 0; | ||
52 | if (access(profname1, R_OK) == 0) { | ||
53 | if (arg_debug) | ||
54 | printf("found %s\n", profname1); | ||
55 | rv = 1; | ||
56 | } | ||
57 | else if (access(profname2, R_OK) == 0) { | ||
58 | if (arg_debug) | ||
59 | printf("found %s\n", profname2); | ||
60 | rv = 1; | ||
61 | } | ||
62 | |||
63 | if (arg_debug) | ||
64 | printf("Profile for %s %s\n", f1, (rv)? "found": "not found"); | ||
65 | free(f1); | ||
66 | free(profname1); | ||
67 | free(profname2); | ||
68 | return rv; | ||
69 | } | ||
70 | |||
71 | void fix_desktop_files(char *homedir) { | ||
72 | assert(homedir); | ||
73 | struct stat sb; | ||
74 | |||
75 | // check user | ||
76 | if (getuid() == 0) { | ||
77 | fprintf(stderr, "Error: this option is not supported for root user; please run as a regular user.\n"); | ||
78 | exit(1); | ||
79 | } | ||
80 | |||
81 | // destination | ||
82 | // create ~/.local/share/applications directory if necessary | ||
83 | char *user_apps_dir; | ||
84 | if (asprintf(&user_apps_dir, "%s/.local/share/applications", homedir) == -1) | ||
85 | errExit("asprintf"); | ||
86 | if (stat(user_apps_dir, &sb) == -1) { | ||
87 | int rv = mkdir(user_apps_dir, 0700); | ||
88 | if (rv) { | ||
89 | fprintf(stderr, "Error: cannot create ~/.local/application directory\n"); | ||
90 | perror("mkdir"); | ||
91 | exit(1); | ||
92 | } | ||
93 | rv = chmod(user_apps_dir, 0700); | ||
94 | (void) rv; | ||
95 | } | ||
96 | |||
97 | // source | ||
98 | DIR *dir = opendir("/usr/share/applications"); | ||
99 | if (!dir) { | ||
100 | perror("Error: cannot open /usr/share/applications directory"); | ||
101 | exit(1); | ||
102 | } | ||
103 | if (chdir("/usr/share/applications")) { | ||
104 | perror("Error: cannot chdir to /usr/share/applications"); | ||
105 | exit(1); | ||
106 | } | ||
107 | |||
108 | printf("\nFixing desktop files in %s\n", user_apps_dir); | ||
109 | // copy | ||
110 | struct dirent *entry; | ||
111 | while ((entry = readdir(dir)) != NULL) { | ||
112 | if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) | ||
113 | continue; | ||
114 | |||
115 | // skip if not regular file or link | ||
116 | // d_type is not available on some file systems | ||
117 | if (entry->d_type != DT_REG && entry->d_type != DT_LNK && entry->d_type != DT_UNKNOWN) | ||
118 | continue; | ||
119 | |||
120 | // skip if not .desktop file | ||
121 | if (strstr(entry->d_name,".desktop") != (entry->d_name+strlen(entry->d_name)-8)) | ||
122 | continue; | ||
123 | |||
124 | char *filename = entry->d_name; | ||
125 | |||
126 | // skip links | ||
127 | if (is_link(filename)) | ||
128 | continue; | ||
129 | if (stat(filename, &sb) == -1) | ||
130 | errExit("stat"); | ||
131 | |||
132 | // no profile in /etc/firejail, no desktop file fixing | ||
133 | if (!have_profile(filename, homedir)) | ||
134 | continue; | ||
135 | |||
136 | //**************************************************** | ||
137 | // load the file in memory and do some basic checking | ||
138 | //**************************************************** | ||
139 | /* coverity[toctou] */ | ||
140 | int fd = open(filename, O_RDONLY); | ||
141 | if (fd == -1) { | ||
142 | fprintf(stderr, "Error: cannot open /usr/share/applications/%s\n", filename); | ||
143 | continue; | ||
144 | } | ||
145 | |||
146 | char *buf = mmap(NULL, sb.st_size + 1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); | ||
147 | if (buf == MAP_FAILED) | ||
148 | errExit("mmap"); | ||
149 | close(fd); | ||
150 | |||
151 | // check format | ||
152 | if (strstr(buf, "[Desktop Entry]\n") == NULL) { | ||
153 | if (arg_debug) | ||
154 | printf(" %s - skipped: wrong format?\n", filename); | ||
155 | munmap(buf, sb.st_size + 1); | ||
156 | continue; | ||
157 | } | ||
158 | |||
159 | // get executable name | ||
160 | char *ptr = strstr(buf,"\nExec="); | ||
161 | if (!ptr || strlen(ptr) < 7) { | ||
162 | if (arg_debug) | ||
163 | printf(" %s - skipped: wrong format?\n", filename); | ||
164 | munmap(buf, sb.st_size + 1); | ||
165 | continue; | ||
166 | } | ||
167 | |||
168 | char *execname = ptr + 6; | ||
169 | // executable name can be quoted, this is rare and currently unsupported, TODO | ||
170 | if (execname[0] == '"') { | ||
171 | if (arg_debug) | ||
172 | printf(" %s - skipped: path quoting unsupported\n", filename); | ||
173 | munmap(buf, sb.st_size + 1); | ||
174 | continue; | ||
175 | } | ||
176 | |||
177 | |||
178 | // try to decide if we need to covert this file | ||
179 | char *change_exec = NULL; | ||
180 | int change_dbus = 0; | ||
181 | |||
182 | // https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s06.html | ||
183 | // The executable program can either be specified with its full path | ||
184 | // or with the name of the executable only | ||
185 | if (execname[0] == '/') { | ||
186 | char *end_name = strchr(execname, ' '); | ||
187 | if (end_name) { | ||
188 | *end_name = '\0'; | ||
189 | char *start_name = strrchr(execname, '/'); | ||
190 | if (start_name) { | ||
191 | start_name++; | ||
192 | // check if we have the executable on the regular path | ||
193 | if (which(start_name)) { | ||
194 | change_exec = strdup(start_name); | ||
195 | if (!change_exec) | ||
196 | errExit("strdup"); | ||
197 | } | ||
198 | } | ||
199 | } | ||
200 | } | ||
201 | |||
202 | if (strstr(buf, "\nDBusActivatable=true")) | ||
203 | change_dbus = 1; | ||
204 | |||
205 | if (change_exec == NULL && change_dbus == 0) { | ||
206 | munmap(buf, sb.st_size + 1); | ||
207 | continue; | ||
208 | } | ||
209 | |||
210 | munmap(buf, sb.st_size + 1); | ||
211 | |||
212 | //**************************************************** | ||
213 | // generate output file | ||
214 | //**************************************************** | ||
215 | char *outname; | ||
216 | if (asprintf(&outname ,"%s/%s", user_apps_dir, filename) == -1) | ||
217 | errExit("asprintf"); | ||
218 | |||
219 | if (stat(outname, &sb) == 0) { | ||
220 | printf(" %s skipped: file exists\n", filename); | ||
221 | continue; | ||
222 | } | ||
223 | |||
224 | FILE *fpin = fopen(filename, "r"); | ||
225 | if (!fpin) { | ||
226 | fprintf(stderr, "Error: cannot open /usr/share/applications/%s\n", filename); | ||
227 | continue; | ||
228 | } | ||
229 | |||
230 | FILE *fpout = fopen(outname, "w"); | ||
231 | if (!fpout) { | ||
232 | fprintf(stderr, "Error: cannot open ~/.local/share/applications/%s\n", outname); | ||
233 | fclose(fpin); | ||
234 | continue; | ||
235 | } | ||
236 | fprintf(fpout, "# converted by firecfg\n"); | ||
237 | free(outname); | ||
238 | |||
239 | char fbuf[MAX_BUF]; | ||
240 | while (fgets(fbuf, MAX_BUF, fpin)) { | ||
241 | if (change_dbus && strcmp(fbuf, "DBusActivatable=true\n") == 0) | ||
242 | fprintf(fpout, "DBusActivatable=false\n"); | ||
243 | else if (change_exec && strncmp(fbuf, "Exec=", 5) == 0) { | ||
244 | char *start_params = strchr(fbuf + 5, ' '); | ||
245 | assert(start_params); | ||
246 | start_params++; | ||
247 | fprintf(fpout, "Exec=%s %s", change_exec, start_params); | ||
248 | } | ||
249 | else | ||
250 | fprintf(fpout, "%s", fbuf); | ||
251 | } | ||
252 | |||
253 | if (change_exec) | ||
254 | free(change_exec); | ||
255 | fclose(fpin); | ||
256 | fclose(fpout); | ||
257 | printf(" %s created\n", filename); | ||
258 | |||
259 | } | ||
260 | |||
261 | closedir(dir); | ||
262 | free(user_apps_dir); | ||
263 | } | ||
264 | |||
265 | |||