Revert "FIXME: this can be rather slow and blocks until the size is known"
[geeqie.git] / src / secure-save.cc
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 <utime.h>
24
25 #include "secure-save.h"
26
27 /**
28  * @file secure-save.cc
29  * 
30  * ABOUT SECURE SAVE \n
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  * If ssi->secure_save is TRUE:
37  *
38  * A call to secure_open("/home/me/.confdir/filename", mask) will open a file
39  * named "filename.tmp_XXXXXX" in /home/me/.confdir/ and return a pointer to a
40  * structure SecureSaveInfo on success or NULL on error.
41  *
42  * filename.tmp_XXXXXX can't conflict with any file since it's created using
43  * mkstemp(). XXXXXX is a random string.
44  *
45  * Subsequent write operations are done using returned SecureSaveInfo FILE *
46  * field named fp.
47  *
48  * If an error is encountered, SecureSaveInfo int field named err is set
49  * (automatically if using secure_fp*() functions or by programmer)
50  *
51  * When secure_close() is called, "filename.tmp_XXXXXX" is flushed and closed,
52  * and if SecureSaveInfo err field has a value of zero, "filename.tmp_XXXXXX"
53  * is renamed to "filename". If this succeeded, then secure_close() returns 0.
54  *
55  * WARNING: since rename() is used, any symlink called "filename" may be
56  * replaced by a regular file. If destination file isn't a regular file,
57  * then secsave is disabled for that file.
58  *
59  * If ssi->secure_save is FALSE:
60  *
61  * No temporary file is created, "filename" is truncated, all operations are
62  * done on it, no rename nor flush occur, symlinks are preserved.
63  *
64  * In both cases:
65  *
66  * Access rights are affected by secure_open() mask parameter.
67  */
68
69 /**
70  * @file
71  * @FIXME locking system on files about to be rewritten ?
72  * @FIXME Low risk race conditions about ssi->file_name.
73  */
74
75 SecureSaveErrno secsave_errno = SS_ERR_NONE;
76
77
78 /** Open a file for writing in a secure way. @returns a pointer to a
79  * structure secure_save_info on success, or NULL on failure. */
80 static SecureSaveInfo *
81 secure_open_umask(const gchar *file_name)
82 {
83         struct stat st;
84         SecureSaveInfo *ssi;
85
86         secsave_errno = SS_ERR_NONE;
87
88         ssi = g_new0(SecureSaveInfo, 1);
89         if (!ssi) {
90                 secsave_errno = SS_ERR_OUT_OF_MEM;
91                 goto end;
92         }
93
94         ssi->secure_save = TRUE;
95         ssi->preserve_perms = TRUE;
96         ssi->unlink_on_error = TRUE;
97
98         ssi->file_name = g_strdup(file_name);
99         if (!ssi->file_name) {
100                 secsave_errno = SS_ERR_OUT_OF_MEM;
101                 goto free_f;
102         }
103
104         /* Check properties of final file. */
105 #ifndef NO_UNIX_SOFTLINKS
106         if (lstat(ssi->file_name, &st)) {
107 #else
108         if (stat(ssi->file_name, &st)) {
109 #endif
110                 /* We ignore error caused by file inexistence. */
111                 if (errno != ENOENT) {
112                         /* lstat() error. */
113                         ssi->err = errno;
114                         secsave_errno = SS_ERR_STAT;
115                         goto free_file_name;
116                 }
117         } else {
118                 if (!S_ISREG(st.st_mode)) {
119                         /* Not a regular file, secure_save is disabled. */
120                         ssi->secure_save = FALSE;
121                 } else {
122 #ifdef HAVE_ACCESS
123                         /* XXX: access() do not work with setuid programs. */
124                         if (access(ssi->file_name, R_OK | W_OK) < 0) {
125                                 ssi->err = errno;
126                                 secsave_errno = SS_ERR_ACCESS;
127                                 goto free_file_name;
128                         }
129 #else
130                         FILE *f1;
131
132                         /* We still have a race condition here between
133                          * [l]stat() and fopen() */
134
135                         f1 = fopen(ssi->file_name, "rb+");
136                         if (f1) {
137                                 fclose(f1);
138                         } else {
139                                 ssi->err = errno;
140                                 secsave_errno = SS_ERR_OPEN_READ;
141                                 goto free_file_name;
142                         }
143 #endif
144                 }
145         }
146
147         if (ssi->secure_save) {
148                 /* We use a random name for temporary file, mkstemp() opens
149                  * the file and return a file descriptor named fd, which is
150                  * then converted to FILE * using fdopen().
151                  */
152                 gint fd;
153                 gchar *randname = g_strconcat(ssi->file_name, ".tmp_XXXXXX", NULL);
154
155                 if (!randname) {
156                         secsave_errno = SS_ERR_OUT_OF_MEM;
157                         goto free_file_name;
158                 }
159
160                 /* No need to use safe_mkstemp() here. --Zas */
161                 fd = g_mkstemp(randname);
162                 if (fd == -1) {
163                         secsave_errno = SS_ERR_MKSTEMP;
164                         g_free(randname);
165                         goto free_file_name;
166                 }
167
168                 ssi->fp = fdopen(fd, "wb");
169                 if (!ssi->fp) {
170                         secsave_errno = SS_ERR_OPEN_WRITE;
171                         ssi->err = errno;
172                         g_free(randname);
173                         goto free_file_name;
174                 }
175
176                 ssi->tmp_file_name = randname;
177         } else {
178                 /* No need to create a temporary file here. */
179                 ssi->fp = fopen(ssi->file_name, "wb");
180                 if (!ssi->fp) {
181                         secsave_errno = SS_ERR_OPEN_WRITE;
182                         ssi->err = errno;
183                         goto free_file_name;
184                 }
185         }
186
187         return ssi;
188
189 free_file_name:
190         g_free(ssi->file_name);
191         ssi->file_name = NULL;
192
193 free_f:
194         g_free(ssi);
195         ssi = NULL;
196
197 end:
198         return NULL;
199 }
200
201 SecureSaveInfo *
202 secure_open(const gchar *file_name)
203 {
204         SecureSaveInfo *ssi;
205         mode_t saved_mask;
206 #ifdef CONFIG_OS_WIN32
207         /* There is neither S_IRWXG nor S_IRWXO under crossmingw32-gcc */
208         const mode_t mask = 0177;
209 #else
210         const mode_t mask = S_IXUSR | S_IRWXG | S_IRWXO;
211 #endif
212
213         saved_mask = umask(mask);
214         ssi = secure_open_umask(file_name);
215         umask(saved_mask);
216
217         return ssi;
218 }
219
220 /** Close a file opened with secure_open(). Rreturns 0 on success,
221  * errno or -1 on failure.
222  */
223 gint
224 secure_close(SecureSaveInfo *ssi)
225 {
226         gint ret = -1;
227
228         if (!ssi) return ret;
229         if (!ssi->fp) goto free;
230
231         if (ssi->err) { /* Keep previous errno. */
232                 ret = ssi->err;
233                 fclose(ssi->fp); /* Close file */
234                 goto free;
235         }
236
237         /* Ensure data is effectively written to disk, we first flush libc buffers
238          * using fflush(), then fsync() to flush kernel buffers, and finally call
239          * fclose() (which call fflush() again, but the first one is needed since
240          * it doesn't make much sense to flush kernel buffers and then libc buffers,
241          * while closing file releases file descriptor we need to call fsync(). */
242 #if defined(HAVE_FFLUSH) || defined(HAVE_FSYNC)
243         if (ssi->secure_save) {
244                 gboolean fail = FALSE;
245
246 #ifdef HAVE_FFLUSH
247                 fail = (fflush(ssi->fp) == EOF);
248 #endif
249
250 #ifdef HAVE_FSYNC
251                 if (!fail) fail = fsync(fileno(ssi->fp));
252 #endif
253
254                 if (fail) {
255                         ret = errno;
256                         secsave_errno = SS_ERR_OTHER;
257
258                         fclose(ssi->fp); /* Close file, ignore errors. */
259                         goto free;
260                 }
261         }
262 #endif
263
264         /* Close file. */
265         if (fclose(ssi->fp) == EOF) {
266                 ret = errno;
267                 secsave_errno = SS_ERR_OTHER;
268                 goto free;
269         }
270
271         if (ssi->secure_save && ssi->file_name && ssi->tmp_file_name) {
272                 struct stat st;
273
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 immediately 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 immediately 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 immediately 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 immediately 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: */