]> git.llucax.com Git - software/mutt-debian.git/blob - lib.c
sidebar: don't overwrite the status if status_on_top is enabled (Closes: 494735)
[software/mutt-debian.git] / lib.c
1 /*
2  * Copyright (C) 1996-2000,2007 Michael R. Elkins <me@mutt.org>
3  * Copyright (C) 1999-2004,2006-7 Thomas Roessler <roessler@does-not-exist.org>
4  * 
5  *     This program is free software; you can redistribute it
6  *     and/or modify it under the terms of the GNU General Public
7  *     License as published by the Free Software Foundation; either
8  *     version 2 of the License, or (at your option) any later
9  *     version.
10  * 
11  *     This program is distributed in the hope that it will be
12  *     useful, but WITHOUT ANY WARRANTY; without even the implied
13  *     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  *     PURPOSE.  See the GNU General Public License for more
15  *     details.
16  * 
17  *     You should have received a copy of the GNU General Public
18  *     License along with this program; if not, write to the Free
19  *     Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  *     Boston, MA  02110-1301, USA.
21  */ 
22
23 /*
24  * This file used to contain some more functions, namely those
25  * which are now in muttlib.c.  They have been removed, so we have
26  * some of our "standard" functions in external programs, too.
27  */
28
29 #define _LIB_C 1
30
31 #if HAVE_CONFIG_H
32 # include "config.h"
33 #endif
34
35 #include <string.h>
36 #include <ctype.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <sys/wait.h>
40 #include <errno.h>
41 #include <sys/stat.h>
42 #include <fcntl.h>
43 #include <pwd.h>
44 #include <sys/types.h>
45 #include <dirent.h>
46
47 #ifdef HAVE_SYSEXITS_H
48 #include <sysexits.h>
49 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
50 #define EX_OK 0
51 #endif
52
53 #include "lib.h"
54
55
56 static struct sysexits
57 {
58   int v;
59   const char *str;
60
61 sysexits_h[] = 
62 {
63 #ifdef EX_USAGE
64   { 0xff & EX_USAGE, "Bad usage." },
65 #endif
66 #ifdef EX_DATAERR
67   { 0xff & EX_DATAERR, "Data format error." },
68 #endif
69 #ifdef EX_NOINPUT
70   { 0xff & EX_NOINPUT, "Cannot open input." },
71 #endif
72 #ifdef EX_NOUSER
73   { 0xff & EX_NOUSER, "User unknown." },
74 #endif
75 #ifdef EX_NOHOST
76   { 0xff & EX_NOHOST, "Host unknown." },
77 #endif
78 #ifdef EX_UNAVAILABLE
79   { 0xff & EX_UNAVAILABLE, "Service unavailable." },
80 #endif
81 #ifdef EX_SOFTWARE
82   { 0xff & EX_SOFTWARE, "Internal error." },
83 #endif
84 #ifdef EX_OSERR
85   { 0xff & EX_OSERR, "Operating system error." },
86 #endif
87 #ifdef EX_OSFILE
88   { 0xff & EX_OSFILE, "System file missing." },
89 #endif
90 #ifdef EX_CANTCREAT
91   { 0xff & EX_CANTCREAT, "Can't create output." },
92 #endif
93 #ifdef EX_IOERR
94   { 0xff & EX_IOERR, "I/O error." },
95 #endif
96 #ifdef EX_TEMPFAIL
97   { 0xff & EX_TEMPFAIL, "Deferred." },
98 #endif
99 #ifdef EX_PROTOCOL
100   { 0xff & EX_PROTOCOL, "Remote protocol error." },
101 #endif
102 #ifdef EX_NOPERM
103   { 0xff & EX_NOPERM, "Insufficient permission." },
104 #endif
105 #ifdef EX_CONFIG
106   { 0xff & EX_NOPERM, "Local configuration error." },
107 #endif
108   { S_ERR, "Exec error." },
109   { -1, NULL}
110 };
111
112 void mutt_nocurses_error (const char *fmt, ...)
113 {
114   va_list ap;
115
116   va_start (ap, fmt);
117   vfprintf (stderr, fmt, ap);
118   va_end (ap);
119   fputc ('\n', stderr);
120 }
121
122 void *safe_calloc (size_t nmemb, size_t size)
123 {
124   void *p;
125
126   if (!nmemb || !size)
127     return NULL;
128
129   if (((size_t) -1) / nmemb <= size)
130   {
131     mutt_error _("Integer overflow -- can't allocate memory!");
132     sleep (1);
133     mutt_exit (1);
134   }
135   
136   if (!(p = calloc (nmemb, size)))
137   {
138     mutt_error _("Out of memory!");
139     sleep (1);
140     mutt_exit (1);
141   }
142   return p;
143 }
144
145 void *safe_malloc (size_t siz)
146 {
147   void *p;
148
149   if (siz == 0)
150     return 0;
151   if ((p = (void *) malloc (siz)) == 0) /* __MEM_CHECKED__ */
152   {
153     mutt_error _("Out of memory!");
154     sleep (1);
155     mutt_exit (1);
156   }
157   return (p);
158 }
159
160 void safe_realloc (void *ptr, size_t siz)
161 {
162   void *r;
163   void **p = (void **)ptr;
164
165   if (siz == 0)
166   {
167     if (*p)
168     {
169       free (*p);                        /* __MEM_CHECKED__ */
170       *p = NULL;
171     }
172     return;
173   }
174
175   if (*p)
176     r = (void *) realloc (*p, siz);     /* __MEM_CHECKED__ */
177   else
178   {
179     /* realloc(NULL, nbytes) doesn't seem to work under SunOS 4.1.x  --- __MEM_CHECKED__ */
180     r = (void *) malloc (siz);          /* __MEM_CHECKED__ */
181   }
182
183   if (!r)
184   {
185     mutt_error _("Out of memory!");
186     sleep (1);
187     mutt_exit (1);
188   }
189
190   *p = r;
191 }
192
193 void safe_free (void *ptr)      /* __SAFE_FREE_CHECKED__ */
194 {
195   void **p = (void **)ptr;
196   if (*p)
197   {
198     free (*p);                          /* __MEM_CHECKED__ */
199     *p = 0;
200   }
201 }
202
203 int safe_fclose (FILE **f)
204 {
205   int r = 0;
206   
207   if (*f)
208     r = fclose (*f);
209       
210   *f = NULL;
211   return r;
212 }
213
214 int safe_fsync_close (FILE **f)
215 {
216   int r = 0;
217
218   if (*f)
219   {
220     if (fflush (*f) || fsync (fileno (*f)))
221     {
222       r = -1;
223       safe_fclose (f);
224     }
225     else
226       r = safe_fclose (f);
227   }
228
229   return r;
230 }
231
232 char *safe_strdup (const char *s)
233 {
234   char *p;
235   size_t l;
236
237   if (!s || !*s)
238     return 0;
239   l = strlen (s) + 1;
240   p = (char *)safe_malloc (l);
241   memcpy (p, s, l);
242   return (p);
243 }
244
245 char *safe_strcat (char *d, size_t l, const char *s)
246 {
247   char *p = d;
248
249   if (!l) 
250     return d;
251
252   l--; /* Space for the trailing '\0'. */
253   
254   for (; *d && l; l--)
255     d++;
256   for (; *s && l; l--)
257     *d++ = *s++;
258
259   *d = '\0';
260   
261   return p;
262 }
263
264 char *safe_strncat (char *d, size_t l, const char *s, size_t sl)
265 {
266   char *p = d;
267
268   if (!l)
269     return d;
270   
271   l--; /* Space for the trailing '\0'. */
272   
273   for (; *d && l; l--)
274     d++;
275   for (; *s && l && sl; l--, sl--)
276     *d++ = *s++;
277
278   *d = '\0';
279   
280   return p;
281 }
282
283
284 void mutt_str_replace (char **p, const char *s)
285 {
286   FREE (p);             /* __FREE_CHECKED__ */
287   *p = safe_strdup (s);
288 }
289
290 void mutt_str_adjust (char **p)
291 {
292   if (!p || !*p) return;
293   safe_realloc (p, strlen (*p) + 1);
294 }
295
296 /* convert all characters in the string to lowercase */
297 char *mutt_strlower (char *s)
298 {
299   char *p = s;
300
301   while (*p)
302   {
303     *p = tolower ((unsigned char) *p);
304     p++;
305   }
306
307   return (s);
308 }
309
310 void mutt_unlink (const char *s)
311 {
312   int fd;
313   int flags;
314   FILE *f;
315   struct stat sb, sb2;
316   char buf[2048];
317
318   /* Defend against symlink attacks */
319   
320 #ifdef O_NOFOLLOW 
321   flags = O_RDWR | O_NOFOLLOW;
322 #else
323   flags = O_RDWR;
324 #endif
325   
326   if (lstat (s, &sb) == 0 && S_ISREG(sb.st_mode))
327   {
328     if ((fd = open (s, flags)) < 0)
329       return;
330     
331     if ((fstat (fd, &sb2) != 0) || !S_ISREG (sb2.st_mode) 
332         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
333     {
334       close (fd);
335       return;
336     }
337     
338     if ((f = fdopen (fd, "r+")))
339     {
340       unlink (s);
341       memset (buf, 0, sizeof (buf));
342       while (sb.st_size > 0)
343       {
344         fwrite (buf, 1, MIN (sizeof (buf), sb.st_size), f);
345         sb.st_size -= MIN (sizeof (buf), sb.st_size);
346       }
347       safe_fclose (&f);
348     }
349   }
350 }
351
352 int mutt_copy_bytes (FILE *in, FILE *out, size_t size)
353 {
354   char buf[2048];
355   size_t chunk;
356
357   while (size > 0)
358   {
359     chunk = (size > sizeof (buf)) ? sizeof (buf) : size;
360     if ((chunk = fread (buf, 1, chunk, in)) < 1)
361       break;
362     if (fwrite (buf, 1, chunk, out) != chunk)
363     {
364       /* dprint (1, (debugfile, "mutt_copy_bytes(): fwrite() returned short byte count\n")); */
365       return (-1);
366     }
367     size -= chunk;
368   }
369
370   return 0;
371 }
372
373 int mutt_copy_stream (FILE *fin, FILE *fout)
374 {
375   size_t l;
376   char buf[LONG_STRING];
377
378   while ((l = fread (buf, 1, sizeof (buf), fin)) > 0)
379   {
380     if (fwrite (buf, 1, l, fout) != l)
381       return (-1);
382   }
383
384   return 0;
385 }
386
387 int 
388 compare_stat (struct stat *osb, struct stat *nsb)
389 {
390   if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino ||
391       osb->st_rdev != nsb->st_rdev)
392   {
393     return -1;
394   }
395
396   return 0;
397 }
398
399 int safe_symlink(const char *oldpath, const char *newpath)
400 {
401   struct stat osb, nsb;
402
403   if(!oldpath || !newpath)
404     return -1;
405   
406   if(unlink(newpath) == -1 && errno != ENOENT)
407     return -1;
408   
409   if (oldpath[0] == '/')
410   {
411     if (symlink (oldpath, newpath) == -1)
412       return -1;
413   }
414   else
415   {
416     char abs_oldpath[_POSIX_PATH_MAX];
417
418     if ((getcwd (abs_oldpath, sizeof abs_oldpath) == NULL) ||
419         (strlen (abs_oldpath) + 1 + strlen (oldpath) + 1 > sizeof abs_oldpath))
420     return -1;
421   
422     strcat (abs_oldpath, "/");          /* __STRCAT_CHECKED__ */
423     strcat (abs_oldpath, oldpath);      /* __STRCAT_CHECKED__ */
424     if (symlink (abs_oldpath, newpath) == -1)
425       return -1;
426   }
427
428   if(stat(oldpath, &osb) == -1 || stat(newpath, &nsb) == -1
429      || compare_stat(&osb, &nsb) == -1)
430   {
431     unlink(newpath);
432     return -1;
433   }
434   
435   return 0;
436 }
437
438
439
440 /* 
441  * This function is supposed to do nfs-safe renaming of files.
442  * 
443  * Warning: We don't check whether src and target are equal.
444  */
445
446 int safe_rename (const char *src, const char *target)
447 {
448   struct stat ssb, tsb;
449
450   if (!src || !target)
451     return -1;
452
453   if (link (src, target) != 0)
454   {
455
456     /*
457      * Coda does not allow cross-directory links, but tells
458      * us it's a cross-filesystem linking attempt.
459      * 
460      * However, the Coda rename call is allegedly safe to use.
461      * 
462      * With other file systems, rename should just fail when 
463      * the files reside on different file systems, so it's safe
464      * to try it here.
465      *
466      */
467     
468     dprint (1, (debugfile, "safe_rename: link (%s, %s) failed: %s (%d)\n", src, target, strerror (errno), errno));
469
470     /*
471      * FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
472      * msdosfs may return EOPNOTSUPP.  ENOTSUP can also appear.
473      */
474     if (errno == EXDEV || errno == ENOSYS || errno == EPERM
475 #ifdef ENOTSUP
476         || errno == ENOTSUP
477 #endif
478 #ifdef EOPNOTSUPP
479         || errno == EOPNOTSUPP
480 #endif
481         )
482     {
483       dprint (1, (debugfile, "safe_rename: trying rename...\n"));
484       if (rename (src, target) == -1) 
485       {
486         dprint (1, (debugfile, "safe_rename: rename (%s, %s) failed: %s (%d)\n", src, target, strerror (errno), errno));
487         return -1;
488       }
489       dprint (1, (debugfile, "safe_rename: rename succeeded.\n"));
490     
491       return 0;
492     }
493
494     return -1;
495   }
496
497   /*
498    * Stat both links and check if they are equal.
499    */
500   
501   if (lstat (src, &ssb) == -1)
502   {
503     dprint (1, (debugfile, "safe_rename: can't stat %s: %s (%d)\n",
504                 src, strerror (errno), errno));
505     return -1;
506   }
507   
508   if (lstat (target, &tsb) == -1)
509   {
510     dprint (1, (debugfile, "safe_rename: can't stat %s: %s (%d)\n",
511                 src, strerror (errno), errno));
512     return -1;
513   }
514
515   /* 
516    * pretend that the link failed because the target file
517    * did already exist.
518    */
519
520   if (compare_stat (&ssb, &tsb) == -1)
521   {
522     dprint (1, (debugfile, "safe_rename: stat blocks for %s and %s diverge; pretending EEXIST.\n", src, target));
523     errno = EEXIST;
524     return -1;
525   }
526
527   /*
528    * Unlink the original link.  Should we really ignore the return
529    * value here? XXX
530    */
531
532   if (unlink (src) == -1) 
533   {
534     dprint (1, (debugfile, "safe_rename: unlink (%s) failed: %s (%d)\n",
535                 src, strerror (errno), errno));
536   }
537   
538
539   return 0;
540 }
541
542
543 /* Create a temporary directory next to a file name */
544
545 static int mutt_mkwrapdir (const char *path, char *newfile, size_t nflen, 
546                     char *newdir, size_t ndlen)
547 {
548   const char *basename;
549   char parent[_POSIX_PATH_MAX];
550   char *p;
551   int rv;
552
553   strfcpy (parent, NONULL (path), sizeof (parent));
554   
555   if ((p = strrchr (parent, '/')))
556   {
557     *p = '\0';
558     basename = p + 1;
559   }
560   else
561   {
562     strfcpy (parent, ".", sizeof (parent));
563     basename = path;
564   }
565
566   do 
567   {
568     snprintf (newdir, ndlen, "%s/%s", parent, ".muttXXXXXX");
569     mktemp (newdir);
570   } 
571   while ((rv = mkdir (newdir, 0700)) == -1 && errno == EEXIST);
572   
573   if (rv == -1)
574     return -1;
575   
576   snprintf (newfile, nflen, "%s/%s", newdir, NONULL(basename));
577   return 0;  
578 }
579
580 /* remove a directory and everything under it */
581 int mutt_rmtree (const char* path)
582 {
583   DIR* dirp;
584   struct dirent* de;
585   char cur[_POSIX_PATH_MAX];
586   struct stat statbuf;
587   int rc = 0;
588
589   if (!(dirp = opendir (path)))
590   {
591     dprint (1, (debugfile, "mutt_rmtree: error opening directory %s\n", path));
592     return -1;
593   }
594   while ((de = readdir (dirp)))
595   {
596     if (!strcmp (".", de->d_name) || !strcmp ("..", de->d_name))
597       continue;
598
599     snprintf (cur, sizeof (cur), "%s/%s", path, de->d_name);
600     /* XXX make nonrecursive version */
601
602     if (stat(cur, &statbuf) == -1)
603     {
604       rc = 1;
605       continue;
606     }
607
608     if (S_ISDIR (statbuf.st_mode))
609       rc |= mutt_rmtree (cur);
610     else
611       rc |= unlink (cur);
612   }
613   closedir (dirp);
614
615   rc |= rmdir (path);
616
617   return rc;
618 }
619
620 static int mutt_put_file_in_place (const char *path, const char *safe_file, const char *safe_dir)
621 {
622   int rv;
623   
624   rv = safe_rename (safe_file, path);
625   unlink (safe_file);
626   rmdir (safe_dir);
627   return rv;
628 }
629
630 int safe_open (const char *path, int flags)
631 {
632   struct stat osb, nsb;
633   int fd;
634
635   if (flags & O_EXCL) 
636   {
637     char safe_file[_POSIX_PATH_MAX];
638     char safe_dir[_POSIX_PATH_MAX];
639
640     if (mutt_mkwrapdir (path, safe_file, sizeof (safe_file),
641                         safe_dir, sizeof (safe_dir)) == -1)
642       return -1;
643     
644     if ((fd = open (safe_file, flags, 0600)) < 0)
645     {
646       rmdir (safe_dir);
647       return fd;
648     }
649
650     /* NFS and I believe cygwin do not handle movement of open files well */
651     close (fd);
652     if (mutt_put_file_in_place (path, safe_file, safe_dir) == -1)
653       return -1;
654   }
655
656   if ((fd = open (path, flags & ~O_EXCL, 0600)) < 0)
657     return fd;
658     
659   /* make sure the file is not symlink */
660   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
661       compare_stat(&osb, &nsb) == -1)
662   {
663 /*    dprint (1, (debugfile, "safe_open(): %s is a symlink!\n", path)); */
664     close (fd);
665     return (-1);
666   }
667
668   return (fd);
669 }
670
671 /* when opening files for writing, make sure the file doesn't already exist
672  * to avoid race conditions.
673  */
674 FILE *safe_fopen (const char *path, const char *mode)
675 {
676   if (mode[0] == 'w')
677   {
678     int fd;
679     int flags = O_CREAT | O_EXCL;
680
681 #ifdef O_NOFOLLOW
682     flags |= O_NOFOLLOW;
683 #endif
684
685     if (mode[1] == '+')
686       flags |= O_RDWR;
687     else
688       flags |= O_WRONLY;
689
690     if ((fd = safe_open (path, flags)) < 0)
691       return (NULL);
692
693     return (fdopen (fd, mode));
694   }
695   else
696     return (fopen (path, mode));
697 }
698
699 static char safe_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
700
701 void mutt_sanitize_filename (char *f, short slash)
702 {
703   if (!f) return;
704
705   for (; *f; f++)
706   {
707     if ((slash && *f == '/') || !strchr (safe_chars, *f))
708       *f = '_';
709   }
710 }
711
712 /* these characters must be escaped in regular expressions */
713
714 static char rx_special_chars[] = "^.[$()|*+?{\\";
715
716 int mutt_rx_sanitize_string (char *dest, size_t destlen, const char *src)
717 {
718   while (*src && --destlen > 2)
719   {
720     if (strchr (rx_special_chars, *src))
721     {
722       *dest++ = '\\';
723       destlen--;
724     }
725     *dest++ = *src++;
726   }
727   
728   *dest = '\0';
729   
730   if (*src)
731     return -1;
732   else
733     return 0;
734 }
735
736 /* Read a line from ``fp'' into the dynamically allocated ``s'',
737  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
738  * If a line ends with "\", this char and the linefeed is removed,
739  * and the next line is read too.
740  */
741 char *mutt_read_line (char *s, size_t *size, FILE *fp, int *line, int flags)
742 {
743   size_t offset = 0;
744   char *ch;
745
746   if (!s)
747   {
748     s = safe_malloc (STRING);
749     *size = STRING;
750   }
751
752   FOREVER
753   {
754     if (fgets (s + offset, *size - offset, fp) == NULL)
755     {
756       FREE (&s);
757       return NULL;
758     }
759     if ((ch = strchr (s + offset, '\n')) != NULL)
760     {
761       if (line)
762         (*line)++;
763       if (flags & M_EOL)
764         return s;
765       *ch = 0;
766       if (ch > s && *(ch - 1) == '\r')
767         *--ch = 0;
768       if (!(flags & M_CONT) || ch == s || *(ch - 1) != '\\')
769         return s;
770       offset = ch - s - 1;
771     }
772     else
773     {
774       int c;
775       c = getc (fp); /* This is kind of a hack. We want to know if the
776                         char at the current point in the input stream is EOF.
777                         feof() will only tell us if we've already hit EOF, not
778                         if the next character is EOF. So, we need to read in
779                         the next character and manually check if it is EOF. */
780       if (c == EOF)
781       {
782         /* The last line of fp isn't \n terminated */
783         if (line)
784           (*line)++;
785         return s;
786       }
787       else
788       {
789         ungetc (c, fp); /* undo our dammage */
790         /* There wasn't room for the line -- increase ``s'' */
791         offset = *size - 1; /* overwrite the terminating 0 */
792         *size += STRING;
793         safe_realloc (&s, *size);
794       }
795     }
796   }
797 }
798
799 char *
800 mutt_substrcpy (char *dest, const char *beg, const char *end, size_t destlen)
801 {
802   size_t len;
803
804   len = end - beg;
805   if (len > destlen - 1)
806     len = destlen - 1;
807   memcpy (dest, beg, len);
808   dest[len] = 0;
809   return dest;
810 }
811
812 char *mutt_substrdup (const char *begin, const char *end)
813 {
814   size_t len;
815   char *p;
816
817   if (end)
818     len = end - begin;
819   else
820     len = strlen (begin);
821
822   p = safe_malloc (len + 1);
823   memcpy (p, begin, len);
824   p[len] = 0;
825   return p;
826 }
827
828 /* prepare a file name to survive the shell's quoting rules.
829  * From the Unix programming FAQ by way of Liviu.
830  */
831
832 size_t mutt_quote_filename (char *d, size_t l, const char *f)
833 {
834   size_t i, j = 0;
835
836   if(!f) 
837   {
838     *d = '\0';
839     return 0;
840   }
841
842   /* leave some space for the trailing characters. */
843   l -= 6;
844   
845   d[j++] = '\'';
846   
847   for(i = 0; j < l && f[i]; i++)
848   {
849     if(f[i] == '\'' || f[i] == '`')
850     {
851       d[j++] = '\'';
852       d[j++] = '\\';
853       d[j++] = f[i];
854       d[j++] = '\'';
855     }
856     else
857       d[j++] = f[i];
858   }
859   
860   d[j++] = '\'';
861   d[j]   = '\0';
862   
863   return j;
864 }
865
866 /* NULL-pointer aware string comparison functions */
867
868 int mutt_strcmp(const char *a, const char *b)
869 {
870   return strcmp(NONULL(a), NONULL(b));
871 }
872
873 int mutt_strcasecmp(const char *a, const char *b)
874 {
875   return strcasecmp(NONULL(a), NONULL(b));
876 }
877
878 int mutt_strncmp(const char *a, const char *b, size_t l)
879 {
880   return strncmp(NONULL(a), NONULL(b), l);
881 }
882
883 int mutt_strncasecmp(const char *a, const char *b, size_t l)
884 {
885   return strncasecmp(NONULL(a), NONULL(b), l);
886 }
887
888 size_t mutt_strlen(const char *a)
889 {
890   return a ? strlen (a) : 0;
891 }
892
893 int mutt_strcoll(const char *a, const char *b)
894 {
895   return strcoll(NONULL(a), NONULL(b));
896 }
897
898 const char *mutt_stristr (const char *haystack, const char *needle)
899 {
900   const char *p, *q;
901
902   if (!haystack)
903     return NULL;
904   if (!needle)
905     return (haystack);
906
907   while (*(p = haystack))
908   {
909     for (q = needle;
910          *p && *q &&
911            tolower ((unsigned char) *p) == tolower ((unsigned char) *q);
912          p++, q++)
913       ;
914     if (!*q)
915       return (haystack);
916     haystack++;
917   }
918   return NULL;
919 }
920
921 char *mutt_skip_whitespace (char *p)
922 {
923   SKIPWS (p);
924   return p;
925 }
926
927 void mutt_remove_trailing_ws (char *s)
928 {
929   char *p;
930   
931   for (p = s + mutt_strlen (s) - 1 ; p >= s && ISSPACE (*p) ; p--)
932     *p = 0;
933 }
934
935 /*
936  * Write the concatened pathname (dir + "/" + fname) into dst.
937  * The slash is ommitted when dir or fname is of 0 length.
938  * Returns NULL on error or a pointer to dst otherwise.
939  */
940 char *mutt_concatn_path (char *dst, size_t dstlen,
941     const char *dir, size_t dirlen, const char *fname, size_t fnamelen)
942 {
943   size_t req;
944   size_t offset = 0;
945
946   if (dstlen == 0)
947     return NULL; /* probably should not mask errors like this */
948
949   /* size check */
950   req = dirlen + fnamelen + 1; /* +1 for the trailing nul */
951   if (dirlen && fnamelen)
952     req++; /* when both components are non-nul, we add a "/" in between */
953   if (req > dstlen) { /* check for condition where the dst length is too short */
954     /* Two options here:
955      * 1) assert(0) or return NULL to signal error
956      * 2) copy as much of the path as will fit
957      * It doesn't appear that the return value is actually checked anywhere mutt_concat_path()
958      * is called, so we should just copy set dst to nul and let the calling function fail later.
959      */
960     dst[0] = 0; /* safe since we bail out early if dstlen == 0 */
961     return NULL;
962   }
963
964   if (dirlen) { /* when dir is not empty */
965     memcpy(dst, dir, dirlen);
966     offset = dirlen;
967     if (fnamelen)
968       dst[offset++] = '/';
969   }
970   if (fnamelen) { /* when fname is not empty */
971     memcpy(dst + offset, fname, fnamelen);
972     offset += fnamelen;
973   }
974   dst[offset] = 0;
975   return dst;
976 }
977
978 char *mutt_concat_path (char *d, const char *dir, const char *fname, size_t l)
979 {
980   const char *fmt = "%s/%s";
981   
982   if (!*fname || (*dir && dir[strlen(dir)-1] == '/'))
983     fmt = "%s%s";
984   
985   snprintf (d, l, fmt, dir, fname);
986   return d;
987 }
988
989 const char *mutt_basename (const char *f)
990 {
991   const char *p = strrchr (f, '/');
992   if (p)
993     return p + 1;
994   else
995     return f;
996 }
997
998 const char *
999 mutt_strsysexit(int e)
1000 {
1001   int i;
1002   
1003   for(i = 0; sysexits_h[i].str; i++)
1004   {
1005     if(e == sysexits_h[i].v)
1006       break;
1007   }
1008   
1009   return sysexits_h[i].str;
1010 }
1011
1012 void mutt_debug (FILE *fp, const char *fmt, ...)
1013 {
1014   va_list ap;
1015   time_t now = time (NULL);
1016   static char buf[23] = "";
1017   static time_t last = 0;
1018
1019   if (now > last)
1020   {
1021     strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S", localtime (&now));
1022     last = now;
1023   }
1024   fprintf (fp, "[%s] ", buf);
1025   va_start (ap, fmt);
1026   vfprintf (fp, fmt, ap);
1027   va_end (ap);
1028 }
1029
1030 int mutt_atos (const char *str, short *dst)
1031 {
1032   int rc;
1033   long res;
1034   short tmp;
1035   short *t = dst ? dst : &tmp;
1036
1037   *t = 0;
1038
1039   if ((rc = mutt_atol (str, &res)) < 0)
1040     return rc;
1041   if ((short) res != res)
1042     return -2;
1043
1044   *t = (short) res;
1045   return 0;
1046 }
1047
1048 int mutt_atoi (const char *str, int *dst)
1049 {
1050   int rc;
1051   long res;
1052   int tmp;
1053   int *t = dst ? dst : &tmp;
1054
1055   *t = 0;
1056
1057   if ((rc = mutt_atol (str, &res)) < 0)
1058     return rc;
1059   if ((int) res != res)
1060     return -2;
1061
1062   *t = (int) res;
1063   return 0;
1064 }
1065
1066 int mutt_atol (const char *str, long *dst)
1067 {
1068   long r;
1069   long *res = dst ? dst : &r;
1070   char *e = NULL;
1071
1072   /* no input: 0 */
1073   if (!str || !*str)
1074   {
1075     *res = 0;
1076     return 0;
1077   }
1078
1079   *res = strtol (str, &e, 10);
1080   if ((*res == LONG_MAX && errno == ERANGE) ||
1081       (e && *e != '\0'))
1082     return -1;
1083   return 0;
1084 }