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