Rocco Rutte:
[apps/madmutt.git] / lib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 /*
12  * This file used to contain some more functions, namely those
13  * which are now in muttlib.c.  They have been removed, so we have
14  * some of our "standard" functions in external programs, too.
15  */
16
17 #if HAVE_CONFIG_H
18 # include "config.h"
19 #endif
20
21 #include <string.h>
22 #include <ctype.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/wait.h>
26 #include <errno.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <pwd.h>
30
31 #ifdef HAVE_SYSEXITS_H
32 #include <sysexits.h>
33 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
34 #define EX_OK 0
35 #endif
36
37 #include "lib.h"
38
39 #include "lib/mem.h"
40 #include "lib/str.h"
41 #include "lib/debug.h"
42
43 extern short Umask;
44
45 static struct sysexits
46 {
47   int v;
48   const char *str;
49
50 sysexits_h[] = 
51 {
52 #ifdef EX_USAGE
53   { 0xff & EX_USAGE, "Bad usage." },
54 #endif
55 #ifdef EX_DATAERR
56   { 0xff & EX_DATAERR, "Data format error." },
57 #endif
58 #ifdef EX_NOINPUT
59   { 0xff & EX_NOINPUT, "Cannot open input." },
60 #endif
61 #ifdef EX_NOUSER
62   { 0xff & EX_NOUSER, "User unknown." },
63 #endif
64 #ifdef EX_NOHOST
65   { 0xff & EX_NOHOST, "Host unknown." },
66 #endif
67 #ifdef EX_UNAVAILABLE
68   { 0xff & EX_UNAVAILABLE, "Service unavailable." },
69 #endif
70 #ifdef EX_SOFTWARE
71   { 0xff & EX_SOFTWARE, "Internal error." },
72 #endif
73 #ifdef EX_OSERR
74   { 0xff & EX_OSERR, "Operating system error." },
75 #endif
76 #ifdef EX_OSFILE
77   { 0xff & EX_OSFILE, "System file missing." },
78 #endif
79 #ifdef EX_CANTCREAT
80   { 0xff & EX_CANTCREAT, "Can't create output." },
81 #endif
82 #ifdef EX_IOERR
83   { 0xff & EX_IOERR, "I/O error." },
84 #endif
85 #ifdef EX_TEMPFAIL
86   { 0xff & EX_TEMPFAIL, "Deferred." },
87 #endif
88 #ifdef EX_PROTOCOL
89   { 0xff & EX_PROTOCOL, "Remote protocol error." },
90 #endif
91 #ifdef EX_NOPERM
92   { 0xff & EX_NOPERM, "Insufficient permission." },
93 #endif
94 #ifdef EX_CONFIG
95   { 0xff & EX_NOPERM, "Local configuration error." },
96 #endif
97   { S_ERR, "Exec error." },
98   { -1, NULL}
99 };
100
101 void mutt_nocurses_error (const char *fmt, ...)
102 {
103   va_list ap;
104
105   va_start (ap, fmt);
106   vfprintf (stderr, fmt, ap);
107   va_end (ap);
108   fputc ('\n', stderr);
109 }
110
111 int safe_fclose (FILE ** f)
112 {
113   int r = 0;
114
115   if (*f)
116     r = fclose (*f);
117
118   *f = NULL;
119   return r;
120 }
121
122 void mutt_unlink (const char *s)
123 {
124   int fd;
125   int flags;
126   FILE *f;
127   struct stat sb, sb2;
128   char buf[2048];
129
130   /* Defend against symlink attacks */
131
132 #ifdef O_NOFOLLOW
133   flags = O_RDWR | O_NOFOLLOW;
134 #else
135   flags = O_RDWR;
136 #endif
137
138   if (lstat (s, &sb) == 0 && S_ISREG (sb.st_mode)) {
139     if ((fd = open (s, flags)) < 0)
140       return;
141
142     if ((fstat (fd, &sb2) != 0) || !S_ISREG (sb2.st_mode)
143         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino)) {
144       close (fd);
145       return;
146     }
147
148     if ((f = fdopen (fd, "r+"))) {
149       unlink (s);
150       memset (buf, 0, sizeof (buf));
151       while (sb.st_size > 0) {
152         fwrite (buf, 1, MIN (sizeof (buf), sb.st_size), f);
153         sb.st_size -= MIN (sizeof (buf), sb.st_size);
154       }
155       fclose (f);
156     }
157   }
158 }
159
160 int mutt_copy_bytes (FILE * in, FILE * out, size_t size)
161 {
162   char buf[2048];
163   size_t chunk;
164
165   while (size > 0) {
166     chunk = (size > sizeof (buf)) ? sizeof (buf) : size;
167     if ((chunk = fread (buf, 1, chunk, in)) < 1)
168       break;
169     if (fwrite (buf, 1, chunk, out) != chunk) {
170       debug_print (1, ("fwrite() returned short byte count\n"));
171       return (-1);
172     }
173     size -= chunk;
174   }
175
176   return 0;
177 }
178
179 int mutt_copy_stream (FILE * fin, FILE * fout)
180 {
181   size_t l;
182   char buf[LONG_STRING];
183
184   while ((l = fread (buf, 1, sizeof (buf), fin)) > 0) {
185     if (fwrite (buf, 1, l, fout) != l)
186       return (-1);
187   }
188
189   return 0;
190 }
191
192 static int compare_stat (struct stat *osb, struct stat *nsb)
193 {
194   if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino ||
195       osb->st_rdev != nsb->st_rdev) {
196     return -1;
197   }
198
199   return 0;
200 }
201
202 int safe_symlink (const char *oldpath, const char *newpath)
203 {
204   struct stat osb, nsb;
205
206   if (!oldpath || !newpath)
207     return -1;
208
209   if (unlink (newpath) == -1 && errno != ENOENT)
210     return -1;
211
212   if (oldpath[0] == '/') {
213     if (symlink (oldpath, newpath) == -1)
214       return -1;
215   }
216   else {
217     char abs_oldpath[_POSIX_PATH_MAX];
218
219     if ((getcwd (abs_oldpath, sizeof abs_oldpath) == NULL) ||
220         (str_len (abs_oldpath) + 1 + str_len (oldpath) + 1 >
221          sizeof abs_oldpath))
222       return -1;
223
224     strcat (abs_oldpath, "/");  /* __STRCAT_CHECKED__ */
225     strcat (abs_oldpath, oldpath);      /* __STRCAT_CHECKED__ */
226     if (symlink (abs_oldpath, newpath) == -1)
227       return -1;
228   }
229
230   if (stat (oldpath, &osb) == -1 || stat (newpath, &nsb) == -1
231       || compare_stat (&osb, &nsb) == -1) {
232     unlink (newpath);
233     return -1;
234   }
235
236   return 0;
237 }
238
239 /* 
240  * This function is supposed to do nfs-safe renaming of files.
241  * 
242  * Warning: We don't check whether src and target are equal.
243  */
244
245 int safe_rename (const char *src, const char *target)
246 {
247   struct stat ssb, tsb;
248
249   if (!src || !target)
250     return -1;
251
252   if (link (src, target) != 0) {
253
254     /*
255      * Coda does not allow cross-directory links, but tells
256      * us it's a cross-filesystem linking attempt.
257      * 
258      * However, the Coda rename call is allegedly safe to use.
259      * 
260      * With other file systems, rename should just fail when 
261      * the files reside on different file systems, so it's safe
262      * to try it here.
263      *
264      */
265
266     if (errno == EXDEV)
267       return rename (src, target);
268
269     return -1;
270   }
271
272   /*
273    * Stat both links and check if they are equal.
274    */
275
276   if (stat (src, &ssb) == -1) {
277     return -1;
278   }
279
280   if (stat (target, &tsb) == -1) {
281     return -1;
282   }
283
284   /* 
285    * pretend that the link failed because the target file
286    * did already exist.
287    */
288
289   if (compare_stat (&ssb, &tsb) == -1) {
290     errno = EEXIST;
291     return -1;
292   }
293
294   /*
295    * Unlink the original link.  Should we really ignore the return
296    * value here? XXX
297    */
298
299   unlink (src);
300
301   return 0;
302 }
303
304 int safe_open (const char *path, int flags)
305 {
306   struct stat osb, nsb;
307   int fd;
308
309   umask (Umask);
310   if ((fd = open (path, flags, 0666)) < 0)
311     return fd;
312
313   /* make sure the file is not symlink */
314   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
315       compare_stat (&osb, &nsb) == -1) {
316     debug_print (1, ("%s is a symlink!\n", path));
317     close (fd);
318     return (-1);
319   }
320
321   return (fd);
322 }
323
324 /* when opening files for writing, make sure the file doesn't already exist
325  * to avoid race conditions.
326  */
327 FILE *safe_fopen (const char *path, const char *mode)
328 {
329   /* first set the current umask */
330   umask (Umask);
331   if (mode[0] == 'w') {
332     int fd;
333     int flags = O_CREAT | O_EXCL;
334
335 #ifdef O_NOFOLLOW
336     flags |= O_NOFOLLOW;
337 #endif
338
339     if (mode[1] == '+')
340       flags |= O_RDWR;
341     else
342       flags |= O_WRONLY;
343
344     if ((fd = safe_open (path, flags)) < 0)
345       return (NULL);
346
347     return (fdopen (fd, mode));
348   }
349   else
350     return (fopen (path, mode));
351 }
352
353 static char safe_chars[] =
354   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
355
356 void mutt_sanitize_filename (char *f, short slash)
357 {
358   if (!f)
359     return;
360
361   for (; *f; f++) {
362     if ((slash && *f == '/') || !strchr (safe_chars, *f))
363       *f = '_';
364   }
365 }
366
367 /* these characters must be escaped in regular expressions */
368
369 static char rx_special_chars[] = "^.[$()|*+?{\\";
370
371 int mutt_rx_sanitize_string (char *dest, size_t destlen, const char *src)
372 {
373   while (*src && --destlen > 2) {
374     if (strchr (rx_special_chars, *src)) {
375       *dest++ = '\\';
376       destlen--;
377     }
378     *dest++ = *src++;
379   }
380
381   *dest = '\0';
382
383   if (*src)
384     return -1;
385   else
386     return 0;
387 }
388
389 /* Read a line from ``fp'' into the dynamically allocated ``s'',
390  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
391  * If a line ends with "\", this char and the linefeed is removed,
392  * and the next line is read too.
393  */
394 char *mutt_read_line (char *s, size_t * size, FILE * fp, int *line)
395 {
396   size_t offset = 0;
397   char *ch;
398
399   if (!s) {
400     s = mem_malloc (STRING);
401     *size = STRING;
402   }
403
404   FOREVER {
405     if (fgets (s + offset, *size - offset, fp) == NULL) {
406       mem_free (&s);
407       return NULL;
408     }
409     if ((ch = strchr (s + offset, '\n')) != NULL) {
410       (*line)++;
411       *ch = 0;
412       if (ch > s && *(ch - 1) == '\r')
413         *--ch = 0;
414       if (ch == s || *(ch - 1) != '\\')
415         return s;
416       offset = ch - s - 1;
417     }
418     else {
419       int c;
420
421       c = getc (fp);            /* This is kind of a hack. We want to know if the
422                                    char at the current point in the input stream is EOF.
423                                    feof() will only tell us if we've already hit EOF, not
424                                    if the next character is EOF. So, we need to read in
425                                    the next character and manually check if it is EOF. */
426       if (c == EOF) {
427         /* The last line of fp isn't \n terminated */
428         (*line)++;
429         return s;
430       }
431       else {
432         ungetc (c, fp);         /* undo our dammage */
433         /* There wasn't room for the line -- increase ``s'' */
434         offset = *size - 1;     /* overwrite the terminating 0 */
435         *size += STRING;
436         mem_realloc (&s, *size);
437       }
438     }
439   }
440 }
441
442 /* prepare a file name to survive the shell's quoting rules.
443  * From the Unix programming FAQ by way of Liviu.
444  */
445
446 size_t mutt_quote_filename (char *d, size_t l, const char *f)
447 {
448   size_t i, j = 0;
449
450   if (!f) {
451     *d = '\0';
452     return 0;
453   }
454
455   /* leave some space for the trailing characters. */
456   l -= 6;
457
458   d[j++] = '\'';
459
460   for (i = 0; j < l && f[i]; i++) {
461     if (f[i] == '\'' || f[i] == '`') {
462       d[j++] = '\'';
463       d[j++] = '\\';
464       d[j++] = f[i];
465       d[j++] = '\'';
466     }
467     else
468       d[j++] = f[i];
469   }
470
471   d[j++] = '\'';
472   d[j] = '\0';
473
474   return j;
475 }
476
477 char *mutt_concat_path (char *d, const char *dir, const char *fname, size_t l)
478 {
479   const char *fmt = "%s/%s";
480
481   if (!*fname || (*dir && dir[str_len (dir) - 1] == '/'))
482     fmt = "%s%s";
483
484   snprintf (d, l, fmt, dir, fname);
485   return d;
486 }
487
488 const char *mutt_basename (const char *f)
489 {
490   const char *p = strrchr (f, '/');
491
492   if (p)
493     return p + 1;
494   else
495     return f;
496 }
497
498 const char *
499 mutt_strsysexit(int e)
500 {
501   int i;
502   
503   for(i = 0; sysexits_h[i].str; i++)
504   {
505     if(e == sysexits_h[i].v)
506       break;
507   }
508   
509   return sysexits_h[i].str;
510 }