978895af0734b9c6f356b1d13ca3c12b6b40f1c0
[apps/madmutt.git] / lib-lib / file.c
1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or (at
5  *  your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful, but
8  *  WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
15  *  MA 02110-1301, USA.
16  *
17  *  Copyright © 2006 Pierre Habouzit
18  */
19 /*
20  * Copyright notice from original mutt:
21  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
22  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
23  *
24  * This file is part of mutt-ng, see http://www.muttng.org/.
25  * It's licensed under the GNU General Public License,
26  * please see the file GPL in the top level source directory.
27  */
28
29 #include "lib-lib.h"
30
31 #ifndef O_NOFOLLOW
32 #  define O_NOFOLLOW  0
33 #endif
34
35 /* FIXME: ugly to the max */
36 extern short Umask;
37
38 static int compare_stat(struct stat *osb, struct stat *nsb)
39 {
40     if (osb->st_dev  != nsb->st_dev || osb->st_ino != nsb->st_ino
41     || osb->st_rdev != nsb->st_rdev)
42     {
43         return -1;
44     }
45
46     return 0;
47 }
48
49 /****************************************************************************/
50 /* fd ops                                                                   */
51 /****************************************************************************/
52
53 int safe_open(const char *path, int flags)
54 {
55     struct stat osb, nsb;
56     int fd;
57
58     umask(Umask);
59
60     if ((fd = open(path, flags, 0666)) < 0)
61         return fd;
62
63     /* make sure the file is not symlink */
64     if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
65     ||  compare_stat(&osb, &nsb) == -1)
66     {
67         close(fd);
68         return -1;
69     }
70
71     return (fd);
72 }
73
74
75 int safe_symlink(const char *oldpath, const char *newpath)
76 {
77     struct stat osb, nsb;
78
79     if (!oldpath || !newpath)
80         return -1;
81
82     if (unlink(newpath) < 0 && errno != ENOENT)
83         return -1;
84
85     if (oldpath[0] == '/') {
86         if (symlink(oldpath, newpath) < 0)
87             return -1;
88     } else {
89         char abs_oldpath[_POSIX_PATH_MAX];
90         ssize_t len;
91
92         if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
93             return -1;
94
95         len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
96         if (len >= ssizeof(abs_oldpath))
97             return -1;
98
99         len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
100         if (len >= ssizeof(abs_oldpath))
101             return -1;
102
103         if (symlink (abs_oldpath, newpath) < 0)
104             return -1;
105     }
106
107     if (stat(oldpath, &osb) < 0
108     ||  stat(newpath, &nsb) < 0
109     ||  compare_stat(&osb, &nsb) < 0)
110     {
111         unlink (newpath);
112         return -1;
113     }
114
115     return 0;
116 }
117
118 /*
119  * This function is supposed to do nfs-safe renaming of files.
120  *
121  * Warning: We don't check whether src and target are equal.
122  */
123 int safe_rename(const char *src, const char *target)
124 {
125     struct stat ssb, tsb;
126
127     if (!src || !target)
128         return -1;
129
130     if (link(src, target) != 0) {
131         /*
132          * Coda does not allow cross-directory links, but tells
133          * us it's a cross-filesystem linking attempt.
134          *
135          * However, the Coda rename call is allegedly safe to use.
136          *
137          * With other file systems, rename should just fail when 
138          * the files reside on different file systems, so it's safe
139          * to try it here.
140          *
141          */
142
143         if (errno == EXDEV)
144             return rename(src, target);
145
146         return -1;
147     }
148
149     /* Stat both links and check if they are equal. */
150
151     if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
152         return -1;
153     }
154
155     /* 
156      * pretend that the link failed because the target file
157      * did already exist.
158      */
159
160     if (compare_stat(&ssb, &tsb) == -1) {
161         errno = EEXIST;
162         return -1;
163     }
164
165     /*
166      * Unlink the original link.  Should we really ignore the return
167      * value here? XXX
168      */
169
170     unlink(src);
171     return 0;
172 }
173
174 void mutt_unlink(const char *s)
175 {
176     struct stat sb, sb2;
177
178     if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
179         int fd;
180         FILE *f;
181
182         /* Defend against symlink attacks */
183
184         if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
185             return;
186
187         if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
188         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
189         {
190             close (fd);
191             return;
192         }
193
194         if ((f = fdopen(fd, "r+"))) {
195             char buf[BUFSIZ];
196             unlink(s);
197
198             p_clear(buf, countof(buf));
199             while (sb.st_size > 0) {
200                 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
201                 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
202             }
203             fclose (f);
204         }
205     }
206 }
207
208
209 /****************************************************************************/
210 /* FILE* ops                                                                */
211 /****************************************************************************/
212
213 /* when opening files for writing, make sure the file doesn't already exist
214    to avoid race conditions.                                                */
215 FILE *safe_fopen(const char *path, const char *mode)
216 {
217     /* first set the current umask */
218     umask(Umask);
219
220     if (mode[0] == 'w') {
221         int fd;
222         int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
223
224         flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
225
226         if ((fd = safe_open(path, flags)) < 0)
227             return (NULL);
228
229         return fdopen (fd, mode);
230     }
231
232     return fopen(path, mode);
233 }
234
235 int safe_fclose(FILE **f)
236 {
237     int r = 0;
238
239     if (*f)
240         r = fclose (*f);
241     *f = NULL;
242     return r;
243 }
244
245 /* If rfc1524_expand_command() is used on a recv'd message, then
246  * the filename doesn't exist yet, but if its used while sending a message,
247  * then we need to rename the existing file.
248  *
249  * This function returns 0 on successful move, 1 on old file doesn't exist,
250  * 2 on new file already exists, and 3 on other failure.
251  */
252
253 /* note on access(2) use: No dangling symlink problems here due to
254  * safe_fopen().
255  */
256 int mutt_rename_file(char *oldfile, char *newfile)
257 {
258     FILE *ofp, *nfp;
259
260     if (access(oldfile, F_OK) != 0)
261         return 1;
262     if (access(newfile, F_OK) == 0)
263         return 2;
264
265     ofp = fopen(oldfile, "r");
266     if (!ofp)
267         return 3;
268
269     nfp = safe_fopen(newfile, "w");
270     if (!nfp) {
271         fclose (ofp);
272         return 3;
273     }
274
275     mutt_copy_stream(ofp, nfp);
276     fclose(nfp);
277     fclose(ofp);
278     mutt_unlink(oldfile);
279     return 0;
280 }
281
282 /* Read a line from ``fp'' into the dynamically allocated ``s'',
283  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
284  * If a line ends with "\", this char and the linefeed is removed,
285  * and the next line is read too.
286  */
287 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
288 {
289     ssize_t offset = 0;
290     char *ch;
291
292     if (!s) {
293         s = p_new(char, STRING);
294         *size = STRING;
295     }
296
297     for (;;) {
298         if (fgets(s + offset, *size - offset, fp) == NULL) {
299             p_delete(&s);
300             return NULL;
301         }
302         if ((ch = strchr(s + offset, '\n')) != NULL) {
303             (*line)++;
304             *ch = 0;
305             if (ch > s && *(ch - 1) == '\r')
306                 *--ch = 0;
307             if (ch == s || *(ch - 1) != '\\')
308                 return s;
309             offset = ch - s - 1;
310         } else {
311             int c;
312
313             c = getc (fp);            /* This is kind of a hack. We want to know if the
314                                          char at the current point in the input stream is EOF.
315                                          feof() will only tell us if we've already hit EOF, not
316                                          if the next character is EOF. So, we need to read in
317                                          the next character and manually check if it is EOF. */
318             if (c == EOF) {
319                 /* The last line of fp isn't \n terminated */
320                 (*line)++;
321                 return s;
322             } else {
323                 ungetc (c, fp);         /* undo our dammage */
324                 /* There wasn't room for the line -- increase ``s'' */
325                 offset = *size - 1;     /* overwrite the terminating 0 */
326                 *size += STRING;
327                 p_realloc(&s, *size);
328             }
329         }
330     }
331 }
332
333 int mutt_copy_stream(FILE *fin, FILE *fout)
334 {
335     char buf[BUFSIZ];
336     size_t l;
337
338     while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
339         if (fwrite(buf, 1, l, fout) != l)
340             return -1;
341     }
342
343     return 0;
344 }
345
346 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
347 {
348     char buf[BUFSIZ];
349
350     while (size > 0) {
351         size_t chunk = MIN(size, ssizeof(buf));
352
353         if ((chunk = fread(buf, 1, chunk, in)) < 1)
354             break;
355         if (fwrite(buf, 1, chunk, out) != chunk) {
356             return -1;
357         }
358         size -= chunk;
359     }
360
361     return 0;
362 }
363
364
365 /****************************************************************************/
366 /* ligben-like funcs                                                        */
367 /****************************************************************************/
368
369 const char *mutt_basename(const char *f)
370 {
371     const char *p = strrchr(f, '/');
372     return p ? p + 1 : f;
373 }
374
375
376 char *
377 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
378 {
379     int pos;
380
381     pos = m_strcpy(d, n, dir);
382     if (pos >= n - 1)
383         return d;
384
385     if (pos && d[pos - 1] != '/')
386         d[pos++] = '/';
387
388     m_strcpy(d + pos, n - pos, fname);
389     return d;
390 }
391
392
393 void mutt_sanitize_filename(char *s, short slash)
394 {
395     static char safe_chars[] =
396         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
397
398     if (!s)
399         return;
400
401     for (; *s; s++) {
402         if ((slash && *s == '/') || !strchr (safe_chars, *s))
403             *s = '_';
404     }
405 }
406
407 /* prepare a file name to survive the shell's quoting rules.
408  * From the Unix programming FAQ by way of Liviu.
409  */
410
411 /* FIXME: API is very wrong */
412 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
413 {
414     ssize_t i, j = 0;
415
416     if (!s) {
417         *d = '\0';
418         return 0;
419     }
420
421     /* leave some space for the trailing characters. */
422     l -= 6;
423
424     d[j++] = '\'';
425
426     for (i = 0; j < l && s[i]; i++) {
427         if (s[i] == '\'' || s[i] == '`') {
428             d[j++] = '\'';
429             d[j++] = '\\';
430             d[j++] = s[i];
431             d[j++] = '\'';
432         } else {
433             d[j++] = s[i];
434         }
435     }
436
437     d[j++] = '\'';
438     d[j] = '\0';
439
440     return j;
441 }
442
443 ssize_t m_file_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
444 {
445     ssize_t pos = 0;
446     const char *p;
447     int hadfname = 0;
448
449     for (p = strchr(fmt, '%'); p; p = strchr(fmt, '%')) {
450         if (p[1] == 's') {
451             pos += m_strncpy(dst + pos, n - pos, fmt, p - fmt);
452             pos += m_strcpy(dst + pos, n - pos, src);
453             fmt  = p + 2;
454             hadfname = 1;
455         } else {
456             pos += m_strncpy(dst + pos, n - pos, fmt, p + 1 - fmt);
457             fmt = p + 1 + (p[1] == '%');
458         }
459     }
460     pos += m_strcpy(dst + pos, n - pos, fmt);
461
462     if (!hadfname)
463         pos += snprintf(dst + pos, n - pos, " %s", src);
464
465     return pos;
466 }
467
468 static ssize_t
469 m_tempftplize(char *dst, ssize_t dlen, const char *fmt, const char *s)
470 {
471     const char *p;
472
473     while ((p = strchr(fmt, '/'))) {
474         fmt = p + 1;
475     }
476
477     if (!*fmt)
478         return m_strcpy(dst, dlen, s);
479
480     for (p = strchr(fmt, '%'); p; p = strchr(p, '%')) {
481         if (p[1] == 's')
482             return m_file_fmt(dst, dlen, fmt, s);
483
484         p += 1 + (p[1] == '%');
485     }
486
487     p = strrchr(fmt, '.');
488     if (p) {
489         return snprintf(dst, dlen, "%s%s", s, p);
490     } else {
491         return snprintf(dst, dlen, "%s.%s", s, fmt);
492     }
493 }
494
495 int m_tempfd(char *dst, ssize_t n, const char *dir, const char *fmt)
496 {
497     char raw[_POSIX_PATH_MAX], tpl[_POSIX_PATH_MAX];
498     const char *path = fmt ? tpl : raw;
499     int fd;
500
501   rand_again:
502     snprintf(raw, sizeof(raw), "%s/madmutt-%04x-%04x-%08x",
503              dir, (int)getuid(), (int)getpid(), (int)rand());
504
505     if (fmt) {
506         m_tempftplize(tpl, sizeof(tpl), fmt, raw);
507     }
508
509     fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW, 0600);
510
511     if (fd < 0) {
512         if (errno == EEXIST)
513             goto rand_again;
514         return -1;
515     }
516
517     m_strcpy(dst, n, path);
518     return fd;
519 }
520
521 FILE *m_tempfile(char *dst, ssize_t n, const char *dir, const char *fmt)
522 {
523     int fd = m_tempfd(dst, n, dir, fmt);
524     return fd < 0 ? NULL : fdopen(fd, "w+");
525 }