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