1 /*
2  * COPYRIGHT INFORMATION - DO NOT REMOVE
3  *
4  * "Portions Copyright (c) 2000-2001 LinuxMagic Inc. All Rights Reserved.
5  * This file contains Original Code and/or Modifications of Original Code as
6  * defined in and that are subject to the Wizard Software License Version
7  * 1.0 (the 'License'). You may not use this file except in compliance with
8  * the License. Please obtain a copy of the License at:
9  *
10  * http://www.linuxmagic.com/opensource/licensing/GPL-2.text
11  *
12  * and read it before using this file.
13  *
14  * The Original Code and all software distributed under the License are
15  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16  * EXPRESS OR IMPLIED, AND LINUXMAGIC HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
18  * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see
19  * the License for the specific language governing rights and limitations
20  * under the License."
21  *
22  * Please read the terms of this license carefully. By using or downloading
23  * this software or file, you are accepting and agreeing to the terms of this
24  * license with LinuxMagic Inc. If you are agreeing to this license on behalf
25  * of a company, you represent that you are authorized to bind the company to
26  * such a license. If you do not meet this criterion or you do not agree to
27  * any of the terms of this license, do NOT download, distribute, use or alter
28  * this software or file in any way.
29  *
30  */
31 
32 /* $Id: qmail-remove.c,v 1.13 2005/01/13 20:28:15 josh Exp $ */
33 
34 /*
35  * This program is loosely based on the python script which
36  * does a similar thing and can be found at:
37  *
38  * http://www.redwoodsoft.com/~dru/programs/
39  *
40  */
41 
42 /*
43  * NOTES:
44  *
45  *     yanked directory is relative to queue directory unless an
46  *     absolute path is specified (begins with /) and assumed to
47  *     exist.
48  *
49  *     queue directory should be absolute path
50  *
51  *     originally tested on Linux and OpenBSD
52  *
53  */
54 
55 /* TODO:
56  *
57  *  cleanup the path generation routines
58  *
59  */
60 
61 /*
62  * Modified: 12/23/2003 Clint Martin ( c.martin*earthlink.net )
63  *
64  *  I have added two additional parameters. -X and -x  both are intended to aid in forcing the expiration
65  *  of messages in the qmail queue to happen in a different time frame than the default Qmail time frame.
66  *  this is done by modifying the time stamps on the files in the "INFO" directory of the queue.
67  *
68  *  -X takes a parameter of the number of seconds to offset the creation/modification time of matching
69  *      messages.  positive values cause the dates to go back in time, essentially making the message "OLDER"
70  *      when passing through the Queue, qmail will bounce any messages that are TOO old.
71  *      negative values essentially make the message YOUNGER. thereby lengthening the time they spend in the queue
72  *      Passing the value 0 will offset into the past all matching files by exactly 7 days.
73  *
74  *  -x takes a complete date/timestamp as it's parameter.  All matching messages are re-stamped to this exact value
75  *      The format of the parameter is the same as the output of the date(8) command. ie:
76  *      "Tue Dec 23 11:42:29 PST 2003"
77  *      This makes it easy to use the date command to change time stamps relative to "today" see examples below for details
78  *
79  *
80  *  I think the -X parameter is relatively self explanatory... however:
81  *
82  *      Make all messages matching Foo@bar.com 1 day older.
83  *
84  *      qmail-remove -p Foo@bar.com -X 86400
85  *
86  *      Newer?
87  *
88  *      qmail-remove -p Foo@bar.com -X -86400
89  *
90  *  Some examples of the -x option:
91  *
92  *      Make all messages matching Foo@bar.com look like they went into the Queue Today
93  *
94  *      qmail-remove -p Foo@bar.com -x "`date`"
95  *
96  *      Yesterday?
97  *
98  *      qmail-remove -p Foo@bar.com -x "`date -v1d`"
99  *
100  *      Tomorrow?
101  *
102  *      qmail-remove -p Foo@bar.com -x "`date -v+1d`"
103  *
104  * I think you get the idea.
105  *
106  * Notes:
107  *
108  *      I think the -X option may be a little confusing. perhaps I should change it so that positive values
109  *      offset into the future, whereas negative values offset into the past?  Thoughts?
110  *
111  *      The X, x and R options are mutually exclusive. Currently R will take precedence
112  *
113  *      Using the "X" option causes us to change the file time for all matching files
114  *      to be OLDER than the queue expiration time.
115  *
116  *      This is intended as an easy way to clean out crap from the queue without having to shut down qmail, to do it.
117  *      By changing the file timestamp, qmail will expire and clean out the queue for us.. of course we don't get to keep
118  *      the files when this happens
119  *
120  *      This can also be used to keep a message in the queue for a longer period of time.  Using a negative offset, or a
121  *      date in the future will accomplish this.
122  *
123  */
124 
125 #include <sys/types.h>
126 #include <sys/stat.h>
127 #include <dirent.h>
128 #include <errno.h>
129 #include <fcntl.h>
130 #include <limits.h>
131 #include <regex.h>
132 #include <stdio.h>
133 #include <stdlib.h>
134 #include <string.h>
135 #include <time.h>
136 #include <unistd.h>
137 #include <fts.h>
138 
139 /* for the utimes function */
140 #include <sys/time.h>
141 
142 
143 /* many linux fcntl.h's seem to be broken */
144 #ifndef O_NOFOLLOW
145 #define O_NOFOLLOW  0400000
146 #endif
147 
148 /* prototypes */
149 void usage(void);
150 int search_file(const char *filename, const char *regex);
151 int remove_file(const char *filename);
152 int delete_file(const char *filename);
153 int expire_file(const char *filename);
154 char *read_file(const char *filename);
155 int find_files(char *path[], const char *pattern);
156 unsigned long digits(unsigned long num);
157 char * mk_nohashpath(char *queue, int inode_name);
158 char * mk_hashpath(char *queue, int inode_name);
159 char * mk_newpath(char *queue, int inode_name);
160 
161 /* globals */
162 extern const char *__progname;
163 const char *default_queue = "/var/qmail/queue";
164 const char *default_pattern = ".*";
165 const char *queuedir;
166 int regex_flags = 0, verbosity = 0, conf_split = 23, remove_files = 0, delete_files = 0;
167 char *yank_dir = "yanked";
168 const char cvsrid[] = "$Id: qmail-remove.c,v 1.13 2005/01/13 20:28:15 josh Exp $";
169 int queue_fd = -1;
170 unsigned long read_bytes = 0;
171 
172 /* Added */
173 char *strptime(const char *s, const char *format, struct tm *tm);
174 int expire_files=0;                 /* if the eXpire option is specified on the command line, this will reflect that */
175 time_t expire_offset = 60*60*24*7;  /* one week in seconds -- this can be changed in the future by a parameter passed to us */
176 time_t expire_date = 0;             /* if specified, this is the timestamp the file will be stamped with */
177 
178 
179 /* queues i
180  *
181  * NOTE: the first queue must be mess as it is used as a key
182  *
183  */
184 const char  *queues[] = {"mess", "local", "remote", "info", "intd", "todo", "bounce", NULL};
185 
main(int argc,char ** argv)186 int main(int argc, char **argv)
187 {
188     long int split = 0;
189     int len, ch, matches;
190     char *yankdirstring, *p, *pattern = NULL;
191     struct tm stime;
192     struct stat statinfo;
193 
194     if (argc < 2) {
195         usage();
196     }
197 
198     while ((ch = getopt(argc, argv, "ehin:p:q:drs:vy:?X:x:")) != -1) {
199         switch (ch) {
200             case 'e':
201                 regex_flags |= REG_EXTENDED;
202             case 'i':
203                 regex_flags |= REG_ICASE;
204                 break;
205             case 'n':
206                 read_bytes = strtoul(optarg, NULL, 10);
207                 if(read_bytes == ULONG_MAX) {
208                     fprintf(stderr, "invalid number of bytes\n");
209                     exit(EXIT_FAILURE);
210                 }
211                 break;
212             case 'p':
213                 pattern = optarg;
214                 break;
215             case 'q':
216                 queue_fd = open(optarg, O_RDONLY);
217                 if(queue_fd < 0) {
218                     perror("open()");
219                     exit(1);
220                 }
221                 if(fchdir(queue_fd) == -1) {
222                     perror("fchdir()");
223                     exit(1);
224                 }
225                 queuedir = optarg;
226                 break;
227             case 'd':
228                 delete_files = 1;
229                 break;
230             case 'r':
231                 remove_files = 1;
232                 break;
233             case 's':
234                 split = strtoul(optarg, NULL, 10);
235                 if(split == (long) ULONG_MAX) {
236                     fprintf(stderr, "invalid split value\n");
237                     exit(EXIT_FAILURE);
238                 }
239                 conf_split = split;
240                 break;
241             case 'v':
242                 verbosity ++;
243                 break;
244 
245     /*** Added: 12/23/2003 Clint Martin ( c.martin*earthlink.net ) ***/
246             case 'X':
247                 expire_files = 1;
248                 /* lets see if they specified a parameter for seconds */
249                 split = strtol(optarg, NULL, 10);
250                 if(split == (long) ULONG_MAX) {
251                     fprintf(stderr, "invalid expire offset value [ %s ]\n", optarg);
252                     exit(EXIT_FAILURE);
253                 }
254                 expire_offset = (split == 0 ? expire_offset : split);
255                 fprintf(stderr, "Offsetting timestamps by %d seconds \n", (int)expire_offset);
256                 expire_date = -1;   /* make sure we only use the offset */
257                 break;
258             case 'x':
259                 expire_files = 1;
260                 /* lets test our parsing function to see what it can do */
261                 p = strptime(optarg, "%+", &stime);
262                 expire_date = mktime(&stime);
263                 if (expire_date >= 0){
264                     fprintf(stderr,"time in seconds: %d [ %s ]\n", (int)expire_date, optarg);
265                 } else{
266                     fprintf(stderr,"Error parsing the date specified at: [ %s ] col %d  [ %s ] ]\n", p, p - optarg, optarg);
267                     exit(EXIT_FAILURE);
268                 }
269                 expire_offset = 0;  /* make sure we only use the time stamp passed */
270                 break;
271     /*** End Section Added: 12/23/2003 Clint Martin ( c.martin*earthlink.net ) ***/
272 
273             case 'y':
274                 yank_dir = optarg;
275                 break;
276             default:
277                 usage();
278                 break;
279         }
280     }
281 
282     if (queue_fd == -1) {
283         queue_fd = open(default_queue, O_RDONLY);
284         if (queue_fd < 0) {
285             fprintf(stderr, "no queue directory specified and %s doesn't exist\n", default_queue);
286             exit(1);
287         }
288         if (fchdir(queue_fd) == -1) {
289             perror("fchdir()");
290             exit(1);
291         }
292         queuedir = default_queue;
293     }
294 
295     /* if we're going to yank something, make sure there's somewhere to yank it to */
296     if (remove_files == 1) {
297         /* make room for queuedir '/' yank_dir '\0' */
298         len = strlen(queuedir) + strlen(yank_dir) + 2;
299         yankdirstring = malloc(len);
300         if (yankdirstring == NULL) {
301             perror("malloc()");
302             exit(1);
303         }
304         snprintf(yankdirstring, len + 1, "%s/%s", queuedir, yank_dir);
305 
306         if (stat(yankdirstring, &statinfo) != 0) {
307             switch (errno) {
308                 case ENOENT:
309                     fprintf(stderr, "FATAL: yank_dir [%s] does not exist, you must create it first.\n", yankdirstring);
310                     break;
311                 default:
312                     perror("stat(yank_dir)");
313                     break;
314             }
315             exit(1);
316         }
317 
318         if (!S_ISDIR(statinfo.st_mode)) {
319             fprintf(stderr, "FATAL: yank_dir [%s] is not a directory.\n", yankdirstring);
320             exit(1);
321         }
322     }
323 
324     matches = find_files((char **)queues, (pattern ? pattern : default_pattern));
325     if (matches >= 0) {
326         fprintf(stderr, "%d file(s) match\n", matches);
327     }
328 
329     exit(EXIT_SUCCESS);
330 }
331 
332 
usage(void)333 void usage(void)
334 {
335     fprintf(stderr, "%s [options]\n", __progname);
336     fprintf(stderr, "  -e\t\tuse extended POSIX regular expressions\n");
337     fprintf(stderr, "  -h, -?\tthis help message\n");
338     fprintf(stderr, "  -i\t\tsearch case insensitively [default: case sensitive]\n");
339     fprintf(stderr, "  -n <bytes>\tlimit our search to the first <bytes> bytes of each file\n");
340     fprintf(stderr, "  -p <pattern>\tspecify the pattern to search for\n");
341     fprintf(stderr, "  -q <queuedir>\tspecify the base qmail queue dir [default: /var/qmail/queue]\n");
342     fprintf(stderr, "  -d\t\tactually remove files not yank them, no -p will delete all the messages!\n");
343     fprintf(stderr, "  -r\t\tactually remove files, without this we'll only print them\n");
344     fprintf(stderr, "  -s <split>\tspecify your conf-split value if non-standard [default: 23]\n");
345     fprintf(stderr, "  -v\t\tincrease verbosity (can be used more than once)\n");
346     fprintf(stderr, "  -y <yankdir>\tdirectory to put files yanked from the queue [default: <queuedir>/yanked]\n");
347 
348     /* Begin CLM 12/23/2003 */
349     fprintf(stderr, "  -X <secs> \tmodify timestamp on matching files, to make qmail expire mail\n");
350     fprintf(stderr, "\t\t <secs> is the number of seconds we want to move the file into the past.\n");
351     fprintf(stderr, "\t\t specifying a value of 0 causes this to default to (%d)\n", (int)expire_offset);
352     fprintf(stderr, "  -x <timespec>\tmodify timestamp on matching files, to make qmail expire mail\n");
353     fprintf(stderr, "\t\t <timespec> is a date/time string in the format of output of the \"date\" program.\n");
354     fprintf(stderr, "\t\t see manpage for strptime(2) for details of this format\n");
355     /* End CLM 12/23/2003 */
356 
357     exit(EXIT_FAILURE);
358 }
359 
360 
read_file(const char * filename)361 char * read_file(const char *filename)
362 {
363     off_t bytes;
364     int fd;
365     char *buff = NULL;
366     struct stat fd_stat;
367 
368     if (filename == NULL) {
369         return NULL;
370     }
371 
372     fd = open(filename, (O_RDONLY | O_NOFOLLOW), 0);
373     if (fd != -1) {
374         if (fstat(fd, &fd_stat) == 0) {
375 
376             /*
377              * If they specify the number of bytes they
378              * want to read read that many (or the whole file
379              * if it's smaller).  Otherwise read the whole file.
380              */
381             if (((long)read_bytes > 0) && (fd_stat.st_size > (long)read_bytes))
382                 bytes = read_bytes;
383             else
384                 bytes = fd_stat.st_size;
385 
386             buff = malloc(bytes + 1);
387             if(buff != NULL) {
388                 buff[bytes] = '\0';
389                 if(read(fd, buff, bytes) != bytes) {
390                     fprintf(stderr, "%s: read() too short\n", __progname);
391                     free(buff);
392                     buff = NULL;
393                 }
394             } else {
395                 perror("malloc()");
396             }
397         } else {
398             perror("fstat()");
399         }
400         close(fd);
401     }
402     return buff;
403 }
404 
405 
search_file(const char * filename,const char * pattern)406 int search_file(const char *filename, const char *pattern)
407 {
408     regex_t match_me;
409     int err_code, match = 0;
410     char *file_inards = NULL, error_str[20];
411 
412     if (pattern == NULL) {
413         return (-1);
414     }
415     if (filename == NULL) {
416         return (-1);
417     }
418 
419     file_inards = read_file(filename);
420     if (file_inards != NULL) {
421         err_code = regcomp(&match_me, pattern, regex_flags | REG_NOSUB);
422         if (err_code == 0) {
423             if (regexec(&match_me, file_inards, 0, NULL, 0) == 0) {
424                 match = 1;
425             }
426         } else {
427             /* regex error */
428             regerror(err_code, &match_me, error_str, 20);
429             fprintf(stderr, "regcomp(): %s\n", error_str);
430         }
431         regfree(&match_me);
432         free(file_inards);
433     }
434 
435     if(match == 1) {
436         return (0);
437     }
438 
439     return (-1);
440 }
441 
442 
find_files(char * dir_list[],const char * pattern)443 int find_files (char *dir_list[], const char *pattern)
444 {
445     FTS *fts;
446     FTSENT *ftsp;
447     char *error_str = NULL, *argv[2];
448     int i = 0, tmp_fd = -1;
449 
450     argv[0] = dir_list[0];
451     argv[1] = NULL;
452 
453     if ((fts = fts_open((char **)argv, FTS_PHYSICAL, NULL)) == NULL) {
454         perror("fts_open");
455         return -1;
456     }
457 
458     errno = 0;
459     while ((ftsp = fts_read(fts)) != NULL) {
460         switch (ftsp->fts_info) {
461             case FTS_F:
462                 printf("%s: ", ftsp->fts_accpath);
463                 if(search_file(ftsp->fts_accpath, pattern) == 0) {
464                     printf("yes\n");
465                     i++;
466                     tmp_fd = open(".", O_RDONLY);
467                     if((tmp_fd >= 0) && (remove_files == 1)) {
468                         if(fchdir(queue_fd) != 0) {
469                             perror("fchdir(queue_fd)");
470                             exit(1);
471                         }
472                         remove_file(ftsp->fts_name);
473                         if(fchdir(tmp_fd) != 0) {
474 			                perror("fchdir()");
475                         }
476 		    } else if ((tmp_fd >= 0) && (delete_files == 1)) {
477                         fchdir(queue_fd);
478                         delete_file(ftsp->fts_name);
479                         if(fchdir(tmp_fd) != 0) {
480                             perror("fchdir()");
481                         }
482                     } else if ((tmp_fd >= 0) && (expire_files == 1)) {  /* this makes sure the the Remove option takes precedence over the eXpire options. */
483                         fchdir(queue_fd);
484                         expire_file(ftsp->fts_name);
485                         if(fchdir(tmp_fd) != 0) {
486                             perror("fchdir()");
487                         }
488                     }
489 		            if(tmp_fd >= 0) {
490 			            close(tmp_fd);
491 			            tmp_fd = -1;
492 		            }
493                 } else {
494                     printf("no\n");
495                 }
496                 break;
497             case FTS_DNR: /* couldn't read */
498             case FTS_ERR: /* error */
499             case FTS_NS:  /* no stat info */
500                 error_str = strerror(ftsp->fts_errno);
501                 fprintf(stderr, "fts_read() %s: %s\n", ftsp->fts_path, error_str);
502                 break;
503             default:
504                 break;
505         }
506     }
507 
508     fts_close(fts);
509 
510     return i;
511 }
512 
513 
514 /*
515  * remove_file()
516  *
517  *     Takes a filename and assumes it is the path to a qmail queue file
518  * (named after its inode). It attempts to find it in all the queues and
519  * move them to the global "yank_dir". It returns the inode number of the
520  * file it removed from the queue or -1 on an error.
521  */
remove_file(const char * filename)522 int remove_file(const char *filename)
523 {
524     int i, count = 0;
525     unsigned long inode_num;
526     char *my_name, *old_name = NULL, *new_name = NULL;
527     struct stat statinfo;
528 
529     if (filename == NULL) {
530         fprintf(stderr, "remove_file(): no filename\n");
531         return -1;
532     }
533 
534     my_name = strrchr(filename, '/');
535     if (my_name == NULL) {
536         my_name = (char *)filename;
537     } else {
538         my_name++;
539     }
540 
541     inode_num = strtoul(my_name, NULL, 10);
542     if ((inode_num == ULONG_MAX) || (inode_num == 0)) {
543         fprintf(stderr, "%s doesn't look like an inode number\n", my_name);
544         return -1;
545     }
546 
547     for (i = 0; (queues[i] != NULL); i++) {
548         new_name = mk_newpath((char *)queues[i], inode_num);
549         if (new_name == NULL) {
550             fprintf(stderr, "remove_file(): unable to create new name\n");
551 	        return -1;
552         }
553         old_name = mk_hashpath((char *)queues[i], inode_num);
554         if (old_name == NULL) {
555 	        free(new_name);
556             fprintf(stderr, "remove_file(): unable to create old name\n");
557 	        return -1;
558         }
559         if (rename(old_name, new_name) == 0) {
560             /* succeeded */
561             fprintf(stderr, "moved %s to %s\n", old_name, new_name);
562             count ++;
563         } else {
564             if (errno == ENOENT) {
565 	            if (old_name) {
566                     if (verbosity >= 2) {
567                         fprintf(stderr, "remove_file(%s): not a file\n", old_name);
568                     }
569 		            free(old_name);
570 		            old_name = NULL;
571 	            }
572                 old_name = mk_nohashpath((char *)queues[i], inode_num);
573 	            if (old_name == NULL) {
574 		            free(new_name);
575 		            return -1;
576 	            }
577                 if (stat(old_name, &statinfo) == -1) {
578                     if (verbosity >= 2) {
579                         fprintf(stderr, "remove_file(%s): no stat info\n", old_name);
580                     }
581                     continue;
582                 }
583                 if ( !S_ISREG(statinfo.st_mode) ) {
584                     if (verbosity >= 2) {
585                         fprintf(stderr, "remove_file(%s): not a file\n", old_name);
586                     }
587                     continue;
588                 }
589                 if (rename(old_name, new_name) == 0) {
590                     /* succeeded */
591                     fprintf(stderr, "moved %s to %s\n", old_name, new_name);
592                     count ++;
593                 } else {
594                     if(errno != ENOENT) {
595                         perror("rename()");
596                     }
597                 }
598             } else {
599                 /* failed but exists */
600                 perror("rename()");
601             }
602         }
603     }
604 
605     /* garbage collection */
606     if (old_name) {
607         free(old_name);
608     }
609     if (new_name) {
610         free(new_name);
611     }
612 
613     return count;
614 }
615 
616 /*
617  * Clint Martin 12/23/2003
618  *
619  * expire_file()
620  *
621  *     Takes a filename and assumes it is the path to a qmail queue file
622  * (named after its inode). It attempts to find it in the INFO dir, and
623  * changes the file time stamp
624  *
625  * returns the number of successful file changes, or -1 on error.
626  */
expire_file(const char * filename)627 int expire_file(const char *filename)
628 {
629     int  count = 0;
630     unsigned long inode_num;
631     char *my_name;
632     char *old_name;
633     struct stat statinfo;
634     struct timeval times[2];        /* used to hold the new mtime and ctime values for the file */
635 
636     if (filename == NULL) {
637         fprintf(stderr, "expire_file(): no filename\n");
638         return -1;
639     }
640 
641     my_name = strrchr(filename, '/');
642     if (my_name == NULL) {
643         my_name = (char *)filename;
644     } else {
645         my_name++;
646     }
647 
648     /* make sure the INODE NUMBER is really an INODE */
649     inode_num = strtoul(my_name, NULL, 10);
650     if ((inode_num == ULONG_MAX) || (inode_num == 0)) {
651         fprintf(stderr, "%s doesn't look like an inode number\n", my_name);
652         return -1;
653     }
654 
655     /* generate the relative path to the specified file */
656     old_name = mk_hashpath("info", inode_num);
657     if (old_name == NULL) {
658         fprintf(stderr, "expire_file(): unable to create old name\n");
659         return -1;
660     }
661 
662     /* reset it's time stamp */
663 
664     if (expire_date > 0L) {
665         /* use the date specified if it was passed */
666         times[0].tv_sec = expire_date;
667         times[0].tv_usec = 0;
668         times[1].tv_sec  = expire_date;
669         times[1].tv_usec = 0;
670     } else {
671         /* otherwise use the relative date offset form */
672         if (stat(old_name, &statinfo) == 0) {
673             times[0].tv_sec = statinfo.st_mtime - expire_offset;
674             times[0].tv_usec = 0;
675             times[1].tv_sec  = statinfo.st_ctime - expire_offset;
676             times[1].tv_usec = 0;
677         } else {
678             /* do some error detection */
679             if (errno == ENOENT) {
680                 if (old_name) {
681                     if (verbosity >= 2) {
682                         fprintf(stderr, "expire_file(%s): not a file (stat) \n", old_name);
683                     }
684                     free(old_name);
685                     old_name = NULL;
686                     return -1;
687                 }
688             }
689         }
690     }
691 
692     /* now, update the time stamp */
693     if (utimes(old_name, times) == 0) {
694         /* succeeded */
695         fprintf(stderr, "Set timestamp on %s \n", old_name);
696         count ++;
697     } else {
698         if (errno == ENOENT) {
699             if (old_name) {
700                 if (verbosity >= 2) {
701                     fprintf(stderr, "expire_file(%s): not a file\n", old_name);
702                 }
703                 free(old_name);
704                 old_name = NULL;
705             }
706         }
707     }
708 
709     /* garbage collection */
710     if (old_name) {
711         free(old_name);
712     }
713 
714     return count;
715 }
716 
mk_nohashpath(char * queue,int inode_name)717 char * mk_nohashpath(char *queue, int inode_name)
718 {
719     int len = 0;
720     char * old_name = NULL;
721 
722     if ((queue == NULL) || (inode_name <= 0)) {
723         return NULL;
724     }
725 
726     len = strlen(queue);
727     len += digits(inode_name);
728     len += 4;
729     old_name = malloc(len);
730 
731     if (old_name) {
732         snprintf(old_name, len, "%s/%u", queue, inode_name);
733     /*    fprintf(stderr, "path: [%s][%u]\n", old_name, inode_name); */
734         return old_name;
735     } else {
736         perror("malloc()");
737         return NULL;
738     }
739 }
740 
mk_hashpath(char * queue,int inode_name)741 char * mk_hashpath(char *queue, int inode_name)
742 {
743     int len = 0, hash_num = 0;
744     char * old_name = NULL;
745 
746     if ((queue == NULL) || (inode_name <= 0)) {
747         return NULL;
748     }
749 
750     hash_num = (inode_name % conf_split);
751 
752     len = strlen(queue);
753     len += digits(hash_num);
754     len += digits(inode_name);
755     len += 4;
756     old_name = malloc(len);
757 
758     if (old_name) {
759         snprintf(old_name, len, "%s/%u/%u", queue, hash_num, inode_name);
760         /* fprintf(stderr, "path: [%s][%u]\n", old_name, inode_name); */
761         return old_name;
762     } else {
763         perror("malloc()");
764         return NULL;
765     }
766 }
767 
mk_newpath(char * queue,int inode_name)768 char * mk_newpath(char *queue, int inode_name)
769 {
770     int len = 0;
771     char *new_name = NULL;
772 
773     if ((queue == NULL) || (inode_name <= 0)) {
774         fprintf(stderr, "mk_newpath(): invalid queue\n");
775         return NULL;
776     }
777 
778     len = strlen(queue);
779     len += strlen(yank_dir);
780     len += digits(inode_name);
781     len += 4;
782     new_name = malloc(len);
783 
784     if (new_name) {
785         snprintf(new_name, len, "%s/%u.%s", yank_dir, inode_name, queue);
786         return new_name;
787     } else {
788         perror("malloc()");
789         return NULL;
790     }
791 }
792 
793 /*
794  * digits()
795  *
796  *     Returns the number of digits needed to represent the number "num".
797  *
798  */
digits(unsigned long num)799 unsigned long digits(unsigned long num)
800 {
801     unsigned long i = 0;
802     while (num >= 10) {
803         num /= 10;
804         i++;
805     }
806     i++;
807     return (i);
808 }
809 
810 
811 /*
812  * delete_file()
813  *
814  *     Takes a filename and assumes it is the path to a qmail queue file
815  * (named after its inode). It attempts to find it in all the queues and
816  * removes the file(s). It returns the inode number of the file it removed
817  * from the queue or -1 on an error. This works with version .94 of qmail
818  * -remove.
819  */
delete_file(const char * filename)820 int delete_file(const char *filename)
821 {
822     int i, count = 0;
823     unsigned long inode_num;
824     char *my_name, *old_name = NULL;
825     struct stat statinfo;
826 
827     if (filename == NULL) {
828         fprintf(stderr, "delete_file(): no filename\n");
829         return -1;
830     }
831     my_name = strrchr(filename, '/');
832     if (my_name == NULL) {
833         my_name = (char *)filename;
834     } else {
835         my_name++;
836     }
837 
838     inode_num = strtoul(my_name, NULL, 10);
839     if ((inode_num == ULONG_MAX) || (inode_num == 0)) {
840         fprintf(stderr, "%s doesn't look like an inode number\n", my_name);
841         return -1;
842     }
843 
844     for (i = 0; (queues[i] != NULL); i++) {
845         old_name = mk_hashpath((char *)queues[i], inode_num);
846         if (old_name == NULL) {
847             fprintf(stderr, "delete_file(): unable to create old name\n");
848 	        return -1;
849         }
850         if ( unlink (old_name) == 0) {
851             /* succeeded */
852             fprintf(stderr, "remove %s\n", old_name);
853             count ++;
854         } else {
855             if (errno == ENOENT) {
856 	            if (old_name) {
857                     if (verbosity >= 2) {
858                         fprintf(stderr, "delete_file(%s): not a file\n", old_name);
859                     }
860 		            free(old_name);
861 		            old_name = NULL;
862 	            }
863                 old_name = mk_nohashpath((char *)queues[i], inode_num);
864 	            if (old_name == NULL) {
865 		            return -1;
866 	            }
867                 if (stat(old_name, &statinfo) == -1) {
868                     if (verbosity >= 2) {
869                         fprintf(stderr, "delete_file(%s): no stat info\n", old_name);
870                     }
871                     continue;
872                 }
873                 if ( !S_ISREG(statinfo.st_mode) ) {
874                     if (verbosity >= 2) {
875                         fprintf(stderr, "delete_file(%s): not a file\n", old_name);
876                     }
877                     continue;
878                 }
879                 if (unlink(old_name) == 0) {
880                     /* succeeded */
881                     fprintf(stderr, "remove %s\n", old_name );
882                     count ++;
883                 } else {
884                     if(errno != ENOENT) {
885                         perror("unlink()");
886                     }
887                 }
888             } else {
889                 /* failed but exists */
890                 perror("unlink()");
891             }
892         }
893     }
894 
895     /* garbage collection */
896     if (old_name) {
897         free(old_name);
898     }
899 
900     return count;
901 }
902