Mercurial > geeqie.yaz
comparison src/secure_save.c @ 307:667e49f52168
Move secure save code to its own files: secure_save.{c,h}.
author | zas_ |
---|---|
date | Fri, 11 Apr 2008 12:31:32 +0000 |
parents | |
children | 99139bf9f380 |
comparison
equal
deleted
inserted
replaced
306:4dbeafae7a53 | 307:667e49f52168 |
---|---|
1 /* | |
2 * Geeqie | |
3 * | |
4 * Author: Vladimir Nadvornik | |
5 * based on the code developped for ELinks by Laurent Monin | |
6 * | |
7 * This software is released under the GNU General Public License (GNU GPL). | |
8 * Please read the included file COPYING for more information. | |
9 * This software comes with no warranty of any kind, use at your own risk! | |
10 */ | |
11 | |
12 #include <glib/gstdio.h> | |
13 #include <errno.h> | |
14 | |
15 #include "main.h" | |
16 #include "secure_save.h" | |
17 | |
18 | |
19 /* ABOUT SECURE SAVE */ | |
20 /* This code was borrowed from the ELinks project (http://elinks.cz) | |
21 * It was originally written by me (Laurent Monin aka Zas) and heavily | |
22 * modified and improved by all ELinks contributors. | |
23 * This code was released under the GPLv2 licence. | |
24 * It was modified to be included in geeqie on 2008/04/05 */ | |
25 | |
26 /* If ssi->secure_save is TRUE: | |
27 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
28 * | |
29 * A call to secure_open("/home/me/.confdir/filename", mask) will open a file | |
30 * named "filename.tmp_XXXXXX" in /home/me/.confdir/ and return a pointer to a | |
31 * structure SecureSaveInfo on success or NULL on error. | |
32 * | |
33 * filename.tmp_XXXXXX can't conflict with any file since it's created using | |
34 * mkstemp(). XXXXXX is a random string. | |
35 * | |
36 * Subsequent write operations are done using returned SecureSaveInfo FILE * | |
37 * field named fp. | |
38 * | |
39 * If an error is encountered, SecureSaveInfo int field named err is set | |
40 * (automatically if using secure_fp*() functions or by programmer) | |
41 * | |
42 * When secure_close() is called, "filename.tmp_XXXXXX" is flushed and closed, | |
43 * and if SecureSaveInfo err field has a value of zero, "filename.tmp_XXXXXX" | |
44 * is renamed to "filename". If this succeeded, then secure_close() returns 0. | |
45 * | |
46 * WARNING: since rename() is used, any symlink called "filename" may be | |
47 * replaced by a regular file. If destination file isn't a regular file, | |
48 * then secsave is disabled for that file. | |
49 * | |
50 * If ssi->secure_save is FALSE: | |
51 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
52 * | |
53 * No temporary file is created, "filename" is truncated, all operations are | |
54 * done on it, no rename nor flush occur, symlinks are preserved. | |
55 * | |
56 * In both cases: | |
57 * ~~~~~~~~~~~~~ | |
58 * | |
59 * Access rights are affected by secure_open() mask parameter. | |
60 */ | |
61 | |
62 /* FIXME: locking system on files about to be rewritten ? */ | |
63 /* FIXME: Low risk race conditions about ssi->file_name. */ | |
64 | |
65 SecureSaveErrno secsave_errno = SS_ERR_NONE; | |
66 | |
67 | |
68 /** Open a file for writing in a secure way. @returns a pointer to a | |
69 * structure secure_save_info on success, or NULL on failure. */ | |
70 static SecureSaveInfo * | |
71 secure_open_umask(gchar *file_name) | |
72 { | |
73 struct stat st; | |
74 SecureSaveInfo *ssi; | |
75 | |
76 secsave_errno = SS_ERR_NONE; | |
77 | |
78 ssi = g_new0(SecureSaveInfo, 1); | |
79 if (!ssi) { | |
80 secsave_errno = SS_ERR_OUT_OF_MEM; | |
81 goto end; | |
82 } | |
83 | |
84 ssi->secure_save = TRUE; | |
85 | |
86 ssi->file_name = g_strdup(file_name); | |
87 if (!ssi->file_name) { | |
88 secsave_errno = SS_ERR_OUT_OF_MEM; | |
89 goto free_f; | |
90 } | |
91 | |
92 /* Check properties of final file. */ | |
93 #ifndef NO_UNIX_SOFTLINKS | |
94 if (g_lstat(ssi->file_name, &st)) { | |
95 #else | |
96 if (g_stat(ssi->file_name, &st)) { | |
97 #endif | |
98 /* We ignore error caused by file inexistence. */ | |
99 if (errno != ENOENT) { | |
100 /* lstat() error. */ | |
101 ssi->err = errno; | |
102 secsave_errno = SS_ERR_STAT; | |
103 goto free_file_name; | |
104 } | |
105 } else { | |
106 if (!S_ISREG(st.st_mode)) { | |
107 /* Not a regular file, secure_save is disabled. */ | |
108 ssi->secure_save = 0; | |
109 } else { | |
110 #ifdef HAVE_ACCESS | |
111 /* XXX: access() do not work with setuid programs. */ | |
112 if (g_access(ssi->file_name, R_OK | W_OK) < 0) { | |
113 ssi->err = errno; | |
114 secsave_errno = SS_ERR_ACCESS; | |
115 goto free_file_name; | |
116 } | |
117 #else | |
118 FILE *f1; | |
119 | |
120 /* We still have a race condition here between | |
121 * [l]stat() and fopen() */ | |
122 | |
123 f1 = g_fopen(ssi->file_name, "rb+"); | |
124 if (f1) { | |
125 fclose(f1); | |
126 } else { | |
127 ssi->err = errno; | |
128 secsave_errno = SS_ERR_OPEN_READ; | |
129 goto free_file_name; | |
130 } | |
131 #endif | |
132 } | |
133 } | |
134 | |
135 if (ssi->secure_save) { | |
136 /* We use a random name for temporary file, mkstemp() opens | |
137 * the file and return a file descriptor named fd, which is | |
138 * then converted to FILE * using fdopen(). | |
139 */ | |
140 gint fd; | |
141 gchar *randname = g_strconcat(ssi->file_name, ".tmp_XXXXXX", NULL); | |
142 | |
143 if (!randname) { | |
144 secsave_errno = SS_ERR_OUT_OF_MEM; | |
145 goto free_file_name; | |
146 } | |
147 | |
148 /* No need to use safe_mkstemp() here. --Zas */ | |
149 fd = g_mkstemp(randname); | |
150 if (fd == -1) { | |
151 secsave_errno = SS_ERR_MKSTEMP; | |
152 g_free(randname); | |
153 goto free_file_name; | |
154 } | |
155 | |
156 ssi->fp = fdopen(fd, "wb"); | |
157 if (!ssi->fp) { | |
158 secsave_errno = SS_ERR_OPEN_WRITE; | |
159 ssi->err = errno; | |
160 g_free(randname); | |
161 goto free_file_name; | |
162 } | |
163 | |
164 ssi->tmp_file_name = randname; | |
165 } else { | |
166 /* No need to create a temporary file here. */ | |
167 ssi->fp = g_fopen(ssi->file_name, "wb"); | |
168 if (!ssi->fp) { | |
169 secsave_errno = SS_ERR_OPEN_WRITE; | |
170 ssi->err = errno; | |
171 goto free_file_name; | |
172 } | |
173 } | |
174 | |
175 return ssi; | |
176 | |
177 free_file_name: | |
178 g_free(ssi->file_name); | |
179 ssi->file_name = NULL; | |
180 | |
181 free_f: | |
182 g_free(ssi); | |
183 ssi = NULL; | |
184 | |
185 end: | |
186 return NULL; | |
187 } | |
188 | |
189 SecureSaveInfo * | |
190 secure_open(gchar *file_name) | |
191 { | |
192 SecureSaveInfo *ssi; | |
193 mode_t saved_mask; | |
194 #ifdef CONFIG_OS_WIN32 | |
195 /* There is neither S_IRWXG nor S_IRWXO under crossmingw32-gcc */ | |
196 const mode_t mask = 0177; | |
197 #else | |
198 const mode_t mask = S_IXUSR | S_IRWXG | S_IRWXO; | |
199 #endif | |
200 | |
201 saved_mask = umask(mask); | |
202 ssi = secure_open_umask(file_name); | |
203 umask(saved_mask); | |
204 | |
205 return ssi; | |
206 } | |
207 | |
208 /** Close a file opened with secure_open(). Rreturns 0 on success, | |
209 * errno or -1 on failure. | |
210 */ | |
211 gint | |
212 secure_close(SecureSaveInfo *ssi) | |
213 { | |
214 gint ret = -1; | |
215 | |
216 if (!ssi) return ret; | |
217 if (!ssi->fp) goto free; | |
218 | |
219 if (ssi->err) { /* Keep previous errno. */ | |
220 ret = ssi->err; | |
221 fclose(ssi->fp); /* Close file */ | |
222 goto free; | |
223 } | |
224 | |
225 /* Ensure data is effectively written to disk, we first flush libc buffers | |
226 * using fflush(), then fsync() to flush kernel buffers, and finally call | |
227 * fclose() (which call fflush() again, but the first one is needed since | |
228 * it doesn't make much sense to flush kernel buffers and then libc buffers, | |
229 * while closing file releases file descriptor we need to call fsync(). */ | |
230 #if defined(HAVE_FFLUSH) || defined(HAVE_FSYNC) | |
231 if (ssi->secure_save) { | |
232 int fail = 0; | |
233 | |
234 #ifdef HAVE_FFLUSH | |
235 fail = (fflush(ssi->fp) == EOF); | |
236 #endif | |
237 | |
238 #ifdef HAVE_FSYNC | |
239 if (!fail) fail = fsync(fileno(ssi->fp)); | |
240 #endif | |
241 | |
242 if (fail) { | |
243 ret = errno; | |
244 secsave_errno = SS_ERR_OTHER; | |
245 | |
246 fclose(ssi->fp); /* Close file, ignore errors. */ | |
247 goto free; | |
248 } | |
249 } | |
250 #endif | |
251 | |
252 /* Close file. */ | |
253 if (fclose(ssi->fp) == EOF) { | |
254 ret = errno; | |
255 secsave_errno = SS_ERR_OTHER; | |
256 goto free; | |
257 } | |
258 | |
259 if (ssi->secure_save && ssi->file_name && ssi->tmp_file_name) { | |
260 /* FIXME: Race condition on ssi->file_name. The file | |
261 * named ssi->file_name may have changed since | |
262 * secure_open() call (where we stat() file and | |
263 * more..). */ | |
264 if (debug > 2) g_printf("rename %s -> %s", ssi->tmp_file_name, ssi->file_name); | |
265 if (g_rename(ssi->tmp_file_name, ssi->file_name) == -1) { | |
266 ret = errno; | |
267 secsave_errno = SS_ERR_RENAME; | |
268 goto free; | |
269 } | |
270 } | |
271 | |
272 ret = 0; /* Success. */ | |
273 | |
274 free: | |
275 if (ssi->tmp_file_name) g_free(ssi->tmp_file_name); | |
276 if (ssi->file_name) g_free(ssi->file_name); | |
277 if (ssi) g_free(ssi); | |
278 | |
279 return ret; | |
280 } | |
281 | |
282 | |
283 /** fputs() wrapper, set ssi->err to errno on error. If ssi->err is set when | |
284 * called, it immediatly returns EOF. | |
285 */ | |
286 gint | |
287 secure_fputs(SecureSaveInfo *ssi, const gchar *s) | |
288 { | |
289 gint ret; | |
290 | |
291 if (!ssi || !ssi->fp || ssi->err) return EOF; | |
292 | |
293 ret = fputs(s, ssi->fp); | |
294 if (ret == EOF) { | |
295 secsave_errno = SS_ERR_OTHER; | |
296 ssi->err = errno; | |
297 } | |
298 | |
299 return ret; | |
300 } | |
301 | |
302 | |
303 /** fputc() wrapper, set ssi->err to errno on error. If ssi->err is set when | |
304 * called, it immediatly returns EOF. | |
305 */ | |
306 gint | |
307 secure_fputc(SecureSaveInfo *ssi, gint c) | |
308 { | |
309 gint ret; | |
310 | |
311 if (!ssi || !ssi->fp || ssi->err) return EOF; | |
312 | |
313 ret = fputc(c, ssi->fp); | |
314 if (ret == EOF) { | |
315 ssi->err = errno; | |
316 secsave_errno = SS_ERR_OTHER; | |
317 } | |
318 | |
319 return ret; | |
320 } | |
321 | |
322 /** fprintf() wrapper, set ssi->err to errno on error and return a negative | |
323 * value. If ssi->err is set when called, it immediatly returns -1. | |
324 */ | |
325 gint | |
326 secure_fprintf(SecureSaveInfo *ssi, const gchar *format, ...) | |
327 { | |
328 va_list ap; | |
329 gint ret; | |
330 | |
331 if (!ssi || !ssi->fp || ssi->err) return -1; | |
332 | |
333 va_start(ap, format); | |
334 ret = vfprintf(ssi->fp, format, ap); | |
335 if (ret < 0) ssi->err = errno; | |
336 va_end(ap); | |
337 | |
338 return ret; | |
339 } | |
340 | |
341 gchar * | |
342 secsave_strerror(SecureSaveErrno secsave_error) | |
343 { | |
344 switch (secsave_error) { | |
345 case SS_ERR_OPEN_READ: | |
346 return _("Cannot read the file"); | |
347 case SS_ERR_STAT: | |
348 return _("Cannot get file status"); | |
349 case SS_ERR_ACCESS: | |
350 return _("Cannot access the file"); | |
351 case SS_ERR_MKSTEMP: | |
352 return _("Cannot create temp file"); | |
353 case SS_ERR_RENAME: | |
354 return _("Cannot rename the file"); | |
355 case SS_ERR_DISABLED: | |
356 return _("File saving disabled by option"); | |
357 case SS_ERR_OUT_OF_MEM: | |
358 return _("Out of memory"); | |
359 case SS_ERR_OPEN_WRITE: | |
360 return _("Cannot write the file"); | |
361 case SS_ERR_NONE: /* Impossible. */ | |
362 case SS_ERR_OTHER: | |
363 default: | |
364 return _("Secure file saving error"); | |
365 } | |
366 } | |
367 |