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