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