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