Use m_strncmp instead of strncmp
[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 #include <utime.h>
32
33 #ifndef O_NOFOLLOW
34 #  define O_NOFOLLOW  0
35 #endif
36
37 /* FIXME: ugly to the max */
38 extern short Umask;
39
40 static int compare_stat(struct stat *osb, struct stat *nsb)
41 {
42     if (osb->st_dev  != nsb->st_dev || osb->st_ino != nsb->st_ino
43     || osb->st_rdev != nsb->st_rdev)
44     {
45         return -1;
46     }
47
48     return 0;
49 }
50
51 /****************************************************************************/
52 /* fd ops                                                                   */
53 /****************************************************************************/
54
55 int safe_open(const char *path, int flags)
56 {
57     struct stat osb, nsb;
58     int fd;
59
60     umask(Umask);
61
62     if ((fd = open(path, flags, 0666)) < 0)
63         return fd;
64
65     /* make sure the file is not symlink */
66     if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
67     ||  compare_stat(&osb, &nsb) == -1)
68     {
69         close(fd);
70         return -1;
71     }
72
73     return (fd);
74 }
75
76
77 int safe_symlink(const char *oldpath, const char *newpath)
78 {
79     struct stat osb, nsb;
80
81     if (!oldpath || !newpath)
82         return -1;
83
84     if (unlink(newpath) < 0 && errno != ENOENT)
85         return -1;
86
87     if (oldpath[0] == '/') {
88         if (symlink(oldpath, newpath) < 0)
89             return -1;
90     } else {
91         char abs_oldpath[_POSIX_PATH_MAX];
92         ssize_t len;
93
94         if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
95             return -1;
96
97         len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
98         if (len >= ssizeof(abs_oldpath))
99             return -1;
100
101         len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
102         if (len >= ssizeof(abs_oldpath))
103             return -1;
104
105         if (symlink (abs_oldpath, newpath) < 0)
106             return -1;
107     }
108
109     if (stat(oldpath, &osb) < 0
110     ||  stat(newpath, &nsb) < 0
111     ||  compare_stat(&osb, &nsb) < 0)
112     {
113         unlink (newpath);
114         return -1;
115     }
116
117     return 0;
118 }
119
120 /*
121  * This function is supposed to do nfs-safe renaming of files.
122  *
123  * Warning: We don't check whether src and target are equal.
124  */
125 int safe_rename(const char *src, const char *target)
126 {
127     struct stat ssb, tsb;
128
129     if (!src || !target)
130         return -1;
131
132     if (link(src, target) != 0) {
133         /*
134          * Coda does not allow cross-directory links, but tells
135          * us it's a cross-filesystem linking attempt.
136          *
137          * However, the Coda rename call is allegedly safe to use.
138          *
139          * With other file systems, rename should just fail when 
140          * the files reside on different file systems, so it's safe
141          * to try it here.
142          *
143          */
144
145         if (errno == EXDEV)
146             return rename(src, target);
147
148         return -1;
149     }
150
151     /* Stat both links and check if they are equal. */
152
153     if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
154         return -1;
155     }
156
157     /* 
158      * pretend that the link failed because the target file
159      * did already exist.
160      */
161
162     if (compare_stat(&ssb, &tsb) == -1) {
163         errno = EEXIST;
164         return -1;
165     }
166
167     /*
168      * Unlink the original link.  Should we really ignore the return
169      * value here? XXX
170      */
171
172     unlink(src);
173     return 0;
174 }
175
176 void mutt_unlink(const char *s)
177 {
178     struct stat sb, sb2;
179
180     if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
181         int fd;
182         FILE *f;
183
184         /* Defend against symlink attacks */
185
186         if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
187             return;
188
189         if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
190         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
191         {
192             close (fd);
193             return;
194         }
195
196         if ((f = fdopen(fd, "r+"))) {
197             char buf[BUFSIZ];
198             unlink(s);
199
200             p_clear(buf, countof(buf));
201             while (sb.st_size > 0) {
202                 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
203                 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
204             }
205             m_fclose(&f);
206         }
207     }
208 }
209
210
211 /****************************************************************************/
212 /* FILE* ops                                                                */
213 /****************************************************************************/
214
215 /* when opening files for writing, make sure the file doesn't already exist
216    to avoid race conditions.                                                */
217 FILE *safe_fopen(const char *path, const char *mode)
218 {
219     /* first set the current umask */
220     umask(Umask);
221
222     if (mode[0] == 'w') {
223         int fd;
224         int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
225
226         flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
227
228         if ((fd = safe_open(path, flags)) < 0)
229             return (NULL);
230
231         return fdopen (fd, mode);
232     }
233
234     return fopen(path, mode);
235 }
236
237 /* If rfc1524_expand_command() is used on a recv'd message, then
238  * the filename doesn't exist yet, but if its used while sending a message,
239  * then we need to rename the existing file.
240  *
241  * This function returns 0 on successful move, 1 on old file doesn't exist,
242  * 2 on new file already exists, and 3 on other failure.
243  */
244
245 /* note on access(2) use: No dangling symlink problems here due to
246  * safe_fopen().
247  */
248 int mutt_rename_file(char *oldfile, char *newfile)
249 {
250     FILE *ofp, *nfp;
251
252     if (access(oldfile, F_OK) != 0)
253         return 1;
254     if (access(newfile, F_OK) == 0)
255         return 2;
256
257     ofp = fopen(oldfile, "r");
258     if (!ofp)
259         return 3;
260
261     nfp = safe_fopen(newfile, "w");
262     if (!nfp) {
263         m_fclose(&ofp);
264         return 3;
265     }
266
267     mutt_copy_stream(ofp, nfp);
268     m_fclose(&nfp);
269     m_fclose(&ofp);
270     mutt_unlink(oldfile);
271     return 0;
272 }
273
274 /* Read a line from ``fp'' into the dynamically allocated ``s'',
275  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
276  * If a line ends with "\", this char and the linefeed is removed,
277  * and the next line is read too.
278  */
279 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
280 {
281     ssize_t offset = 0;
282     char *ch;
283
284     if (!s) {
285         s = p_new(char, STRING);
286         *size = STRING;
287     }
288
289     for (;;) {
290         if (fgets(s + offset, *size - offset, fp) == NULL) {
291             p_delete(&s);
292             return NULL;
293         }
294         if ((ch = strchr(s + offset, '\n')) != NULL) {
295             (*line)++;
296             *ch = 0;
297             if (ch > s && *(ch - 1) == '\r')
298                 *--ch = 0;
299             if (ch == s || *(ch - 1) != '\\')
300                 return s;
301             offset = ch - s - 1;
302         } else {
303             int c;
304
305             c = getc (fp);            /* This is kind of a hack. We want to know if the
306                                          char at the current point in the input stream is EOF.
307                                          feof() will only tell us if we've already hit EOF, not
308                                          if the next character is EOF. So, we need to read in
309                                          the next character and manually check if it is EOF. */
310             if (c == EOF) {
311                 /* The last line of fp isn't \n terminated */
312                 (*line)++;
313                 return s;
314             } else {
315                 ungetc (c, fp);         /* undo our dammage */
316                 /* There wasn't room for the line -- increase ``s'' */
317                 offset = *size - 1;     /* overwrite the terminating 0 */
318                 *size += STRING;
319                 p_realloc(&s, *size);
320             }
321         }
322     }
323 }
324
325 int mutt_copy_stream(FILE *fin, FILE *fout)
326 {
327     char buf[BUFSIZ];
328     size_t l;
329
330     while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
331         if (fwrite(buf, 1, l, fout) != l)
332             return -1;
333     }
334
335     return 0;
336 }
337
338 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
339 {
340     char buf[BUFSIZ];
341
342     while (size > 0) {
343         size_t chunk = MIN(size, ssizeof(buf));
344
345         if ((chunk = fread(buf, 1, chunk, in)) < 1)
346             break;
347         if (fwrite(buf, 1, chunk, out) != chunk) {
348             return -1;
349         }
350         size -= chunk;
351     }
352
353     return 0;
354 }
355
356
357 /****************************************************************************/
358 /* path manipulations                                                       */
359 /****************************************************************************/
360
361 const char *mutt_basename(const char *f)
362 {
363     const char *p = strrchr(f, '/');
364     return p ? p + 1 : f;
365 }
366
367
368 char *
369 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
370 {
371     int pos;
372
373     pos = m_strcpy(d, n, dir);
374     if (pos >= n - 1)
375         return d;
376
377     if (pos && d[pos - 1] != '/')
378         d[pos++] = '/';
379
380     m_strcpy(d + pos, n - pos, fname);
381     return d;
382 }
383
384
385 void mutt_sanitize_filename(char *s, short slash)
386 {
387     static char safe_chars[] =
388         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
389
390     if (!s)
391         return;
392
393     for (; *s; s++) {
394         if ((slash && *s == '/') || !strchr (safe_chars, *s))
395             *s = '_';
396     }
397 }
398
399 /* prepare a file name to survive the shell's quoting rules.
400  * From the Unix programming FAQ by way of Liviu.
401  */
402
403 /* FIXME: API is very wrong */
404 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
405 {
406     ssize_t i, j = 0;
407
408     if (!s) {
409         *d = '\0';
410         return 0;
411     }
412
413     /* leave some space for the trailing characters. */
414     l -= 6;
415
416     d[j++] = '\'';
417
418     for (i = 0; j < l && s[i]; i++) {
419         if (s[i] == '\'' || s[i] == '`') {
420             d[j++] = '\'';
421             d[j++] = '\\';
422             d[j++] = s[i];
423             d[j++] = '\'';
424         } else {
425             d[j++] = s[i];
426         }
427     }
428
429     d[j++] = '\'';
430     d[j] = '\0';
431
432     return j;
433 }
434
435 ssize_t m_file_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
436 {
437     ssize_t pos = 0;
438     const char *p;
439     int hadfname = 0;
440
441     for (p = strchr(fmt, '%'); p; p = strchr(fmt, '%')) {
442         if (p[1] == 's') {
443             pos += m_strncpy(dst + pos, n - pos, fmt, p - fmt);
444             pos += m_strcpy(dst + pos, n - pos, src);
445             fmt  = p + 2;
446             hadfname = 1;
447         } else {
448             pos += m_strncpy(dst + pos, n - pos, fmt, p + 1 - fmt);
449             fmt = p + 1 + (p[1] == '%');
450         }
451     }
452     pos += m_strcpy(dst + pos, n - pos, fmt);
453
454     if (!hadfname)
455         pos += snprintf(dst + pos, n - pos, " %s", src);
456
457     return pos;
458 }
459
460 ssize_t
461 m_quotefile_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
462 {
463     char tmp[LONG_STRING];
464     mutt_quote_filename(tmp, sizeof(tmp), src);
465     return m_file_fmt(dst, n, fmt, tmp);
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_RDWR | 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 }
526
527 /****************************************************************************/
528 /* misc                                                                     */
529 /****************************************************************************/
530
531 /* Decrease a file's modification time by 1 second */
532 time_t m_decrease_mtime(const char *path, struct stat *st)
533 {
534     struct utimbuf utim;
535     struct stat _st;
536     time_t mtime;
537
538     if (!st) {
539         if (stat(path, &_st) == -1)
540             return -1;
541         st = &_st;
542     }
543
544     if ((mtime = st->st_mtime) == time(NULL)) {
545         mtime -= 1;
546         utim.actime = mtime;
547         utim.modtime = mtime;
548         utime(path, &utim);
549     }
550
551     return mtime;
552 }
553