| 1 | /*$Id: ezmlm-clean.c,v 1.30 1999/05/12 22:15:26 lindberg Exp $*/ |
| 2 | /*$Name: ezmlm-idx-040 $*/ |
| 3 | #include <sys/types.h> |
| 4 | #include <sys/stat.h> |
| 5 | #include "error.h" |
| 6 | #include "stralloc.h" |
| 7 | #include "str.h" |
| 8 | #include "env.h" |
| 9 | #include "sig.h" |
| 10 | #include "slurp.h" |
| 11 | #include "getconf.h" |
| 12 | #include "strerr.h" |
| 13 | #include "byte.h" |
| 14 | #include "getln.h" |
| 15 | #include "case.h" |
| 16 | #include "qmail.h" |
| 17 | #include "substdio.h" |
| 18 | #include "readwrite.h" |
| 19 | #include "seek.h" |
| 20 | #include "quote.h" |
| 21 | #include "datetime.h" |
| 22 | #include "now.h" |
| 23 | #include "date822fmt.h" |
| 24 | #include "direntry.h" |
| 25 | #include "cookie.h" |
| 26 | #include "sgetopt.h" |
| 27 | #include "fmt.h" |
| 28 | #include "errtxt.h" |
| 29 | #include "copy.h" |
| 30 | #include "idx.h" |
| 31 | #include "mime.h" |
| 32 | |
| 33 | int flagmime = MOD_MIME; /* default is message as attachment */ |
| 34 | int flagreturn = 1; /* default return timed-out messages */ |
| 35 | char flagcd = '\0'; /* default: no transferencoding */ |
| 36 | stralloc fnmsg = {0}; |
| 37 | |
| 38 | /* When ezmlm-clean is run, messages and message stubs in pending/ */ |
| 39 | /* rejected/accepted are erased if they are older than delay hours. */ |
| 40 | /* Timeouts in h for messages. If modtime has a number, it is made to be*/ |
| 41 | /* in the range DELAY_MIN..DELAY_MAX. If the number is 0 or there is no */ |
| 42 | /* number, DELAY_DEFAULT is used. Messages that are read-only are */ |
| 43 | /* ignored. Messages in 'pending' that have the execute bit set result */ |
| 44 | /* in an informative reply to the poster. Any defects in the message */ |
| 45 | /* format, inability to open the file, etc, result in a maillog entry */ |
| 46 | /* whereafter the message is erased. */ |
| 47 | |
| 48 | /* The defines are in "idx.h" */ |
| 49 | |
| 50 | #define FATAL "ezmlm-clean: fatal: " |
| 51 | |
| 52 | void die_read() |
| 53 | { |
| 54 | strerr_die4x(111,FATAL,ERR_READ,fnmsg.s,": "); |
| 55 | } |
| 56 | |
| 57 | void die_usage() |
| 58 | { |
| 59 | strerr_die1x(100,"ezmlm-clean: usage: ezmlm-clean [-mMrRvV] dir"); |
| 60 | } |
| 61 | |
| 62 | void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); } |
| 63 | |
| 64 | datetime_sec when; |
| 65 | unsigned int older; |
| 66 | struct datetime dt; |
| 67 | |
| 68 | char textbuf[1024]; |
| 69 | substdio sstext; |
| 70 | |
| 71 | struct qmail qq; |
| 72 | int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len; |
| 73 | { |
| 74 | qmail_put(&qq,buf,len); |
| 75 | return len; |
| 76 | } |
| 77 | char qqbuf[1]; |
| 78 | substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf)); |
| 79 | |
| 80 | char *dir; |
| 81 | char strnum[FMT_ULONG]; |
| 82 | char date[DATE822FMT]; |
| 83 | char boundary[COOKIE]; |
| 84 | datetime_sec hashdate; |
| 85 | |
| 86 | stralloc outhost = {0}; |
| 87 | stralloc outlocal = {0}; |
| 88 | stralloc mailinglist = {0}; |
| 89 | stralloc listid = {0}; |
| 90 | stralloc quoted = {0}; |
| 91 | stralloc line = {0}; |
| 92 | stralloc modtime = {0}; |
| 93 | stralloc to = {0}; |
| 94 | stralloc charset = {0}; |
| 95 | |
| 96 | int flagconf; |
| 97 | int fd; |
| 98 | int match; |
| 99 | unsigned long msgnum = 0; |
| 100 | /* counter to make message-id unique, since we may */ |
| 101 | /* send out several msgs. This is not bullet-proof.*/ |
| 102 | /* Duplication occurs if we do x>1 msg && another */ |
| 103 | /* ezmlm started within x seconds, and with the */ |
| 104 | /* same pid. Very unlikely. */ |
| 105 | |
| 106 | void transferenc() |
| 107 | { |
| 108 | if (flagcd) { |
| 109 | qmail_puts(&qq,"\nContent-Transfer-Encoding: "); |
| 110 | if (flagcd == 'Q') |
| 111 | qmail_puts(&qq,"Quoted-Printable\n\n"); |
| 112 | else |
| 113 | qmail_puts(&qq,"base64\n\n"); |
| 114 | } else |
| 115 | qmail_puts(&qq,"\n\n"); |
| 116 | } |
| 117 | void readconfigs() |
| 118 | /* gets outlocal, outhost, etc. This is done only if there are any timed-out*/ |
| 119 | /* messages found, that merit a reply to the author. */ |
| 120 | { |
| 121 | |
| 122 | getconf_line(&mailinglist,"mailinglist",1,FATAL,dir); |
| 123 | getconf_line(&listid,"listid",0,FATAL,dir); |
| 124 | getconf_line(&outhost,"outhost",1,FATAL,dir); |
| 125 | getconf_line(&outlocal,"outlocal",1,FATAL,dir); |
| 126 | set_cpouthost(&outlocal); |
| 127 | set_cpoutlocal(&outlocal); |
| 128 | } |
| 129 | |
| 130 | void sendnotice(d) |
| 131 | char *d; |
| 132 | /* sends file pointed to by d to the address in the return-path of the */ |
| 133 | /* message. */ |
| 134 | { |
| 135 | unsigned int x,y; |
| 136 | char *err; |
| 137 | |
| 138 | if (!flagconf) { |
| 139 | readconfigs(); |
| 140 | } |
| 141 | if (qmail_open(&qq, (stralloc *) 0) == -1) |
| 142 | strerr_die2x(111,FATAL,ERR_QMAIL_QUEUE); |
| 143 | |
| 144 | fd = open_read(d); |
| 145 | if (fd == -1) |
| 146 | strerr_die4sys(111,FATAL,ERR_OPEN,d,": "); |
| 147 | substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf)); |
| 148 | if (getln(&sstext,&line,&match,'\n') == -1) die_read(); |
| 149 | if (!match) die_read(); |
| 150 | if (!case_startb(line.s,line.len,"return-path:")) die_read(); |
| 151 | x = 12 + byte_chr(line.s + 12,line.len-12,'<'); |
| 152 | y = byte_rchr(line.s + x,line.len-x,'>'); |
| 153 | if (x != line.len && x+y != line.len) { |
| 154 | if (!stralloc_copyb(&to,line.s+x+1, y-1)) die_nomem(); |
| 155 | if (!stralloc_0(&to)) die_nomem(); |
| 156 | } else |
| 157 | die_read(); |
| 158 | qmail_puts(&qq,"Mailing-List: "); |
| 159 | qmail_put(&qq,mailinglist.s,mailinglist.len); |
| 160 | qmail_puts(&qq,"\nList-ID: "); |
| 161 | qmail_put(&qq,listid.s,listid.len); |
| 162 | qmail_puts(&qq,"\nDate: "); |
| 163 | datetime_tai(&dt,when); |
| 164 | qmail_put(&qq,date,date822fmt(date,&dt)); |
| 165 | qmail_puts(&qq,"Message-ID: <"); |
| 166 | if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum, |
| 167 | (unsigned long) when + msgnum++))) die_nomem(); |
| 168 | if (!stralloc_append(&line,".")) die_nomem(); |
| 169 | if (!stralloc_catb(&line,strnum, |
| 170 | fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem(); |
| 171 | if (!stralloc_cats(&line,".ezmlm@")) die_nomem(); |
| 172 | if (!stralloc_cat(&line,&outhost)) die_nomem(); |
| 173 | if (!stralloc_0(&line)) die_nomem(); |
| 174 | qmail_puts(&qq,line.s); |
| 175 | /* "unique" MIME boundary as hash of messageid */ |
| 176 | cookie(boundary,"",0,"",line.s,""); |
| 177 | qmail_puts(&qq,">\nFrom: "); |
| 178 | if (!quote("ed,&outlocal)) die_nomem(); |
| 179 | qmail_put(&qq,quoted.s,quoted.len); |
| 180 | qmail_puts(&qq,"-help@"); |
| 181 | qmail_put(&qq,outhost.s,outhost.len); |
| 182 | qmail_puts(&qq,"\nSubject: "); |
| 183 | qmail_puts(&qq,TXT_RETURNED_POST); |
| 184 | qmail_put(&qq,quoted.s,quoted.len); |
| 185 | qmail_puts(&qq,"@"); |
| 186 | qmail_put(&qq,outhost.s,outhost.len); |
| 187 | qmail_puts(&qq, "\nTo: "); |
| 188 | qmail_puts(&qq,to.s); |
| 189 | if (flagmime) { |
| 190 | if (getconf_line(&charset,"charset",0,FATAL,dir)) { |
| 191 | if (charset.len >= 2 && charset.s[charset.len - 2] == ':') { |
| 192 | if (charset.s[charset.len - 1] == 'B' || |
| 193 | charset.s[charset.len - 1] == 'Q') { |
| 194 | flagcd = charset.s[charset.len - 1]; |
| 195 | charset.s[charset.len - 2] = '\0'; |
| 196 | } |
| 197 | } |
| 198 | } else |
| 199 | if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem(); |
| 200 | if (!stralloc_0(&charset)) die_nomem(); |
| 201 | qmail_puts(&qq,"\nMIME-Version: 1.0\n"); |
| 202 | qmail_puts(&qq,"Content-Type: multipart/mixed;\n\tboundary="); |
| 203 | qmail_put(&qq,boundary,COOKIE); |
| 204 | qmail_puts(&qq,"\n\n--"); |
| 205 | qmail_put(&qq,boundary,COOKIE); |
| 206 | qmail_puts(&qq,"\nContent-Type: text/plain; charset="); |
| 207 | qmail_puts(&qq,charset.s); |
| 208 | transferenc(); |
| 209 | } else |
| 210 | qmail_puts(&qq,"\n\n"); |
| 211 | |
| 212 | copy(&qq,"text/top",flagcd,FATAL); |
| 213 | copy(&qq,"text/mod-timeout",flagcd,FATAL); |
| 214 | if (flagcd == 'B') { |
| 215 | encodeB("",0,&line,2,FATAL); |
| 216 | qmail_put(&qq,line.s,line.len); |
| 217 | } |
| 218 | |
| 219 | if (flagmime) { |
| 220 | qmail_puts(&qq,"\n--"); |
| 221 | qmail_put(&qq,boundary,COOKIE); |
| 222 | qmail_puts(&qq,"\nContent-Type: message/rfc822\n\n"); |
| 223 | } |
| 224 | |
| 225 | if (seek_begin(fd) == -1) |
| 226 | strerr_die4sys(111,FATAL,ERR_SEEK,d,": "); |
| 227 | |
| 228 | substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf)); |
| 229 | if (substdio_copy(&ssqq,&sstext) != 0) die_read(); |
| 230 | close (fd); |
| 231 | |
| 232 | if (flagmime) { |
| 233 | qmail_puts(&qq,"\n--"); |
| 234 | qmail_put(&qq,boundary,COOKIE); |
| 235 | qmail_puts(&qq,"--\n"); |
| 236 | } |
| 237 | |
| 238 | if (!stralloc_copy(&line,&outlocal)) die_nomem(); |
| 239 | if (!stralloc_cats(&line,"-return-@")) die_nomem(); |
| 240 | if (!stralloc_cat(&line,&outhost)) die_nomem(); |
| 241 | if (!stralloc_0(&line)) die_nomem(); |
| 242 | qmail_from(&qq,line.s); /* sender */ |
| 243 | qmail_to(&qq,to.s); |
| 244 | |
| 245 | if (*(err = qmail_close(&qq)) != '\0') |
| 246 | strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE, err + 1); |
| 247 | |
| 248 | strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0; |
| 249 | strerr_warn2("ezmlm-clean: info: qp ",strnum,0); |
| 250 | } |
| 251 | |
| 252 | void dodir(dirname,reply) |
| 253 | char *dirname; int reply; |
| 254 | /* parses file names in directory 'dirname'. Files that are not owner */ |
| 255 | /* writable (w) are ignored. If the files are older (by name!) than */ |
| 256 | /* now-delay, action is taken: */ |
| 257 | /* If the owner x bit is not set, the file is erased. */ |
| 258 | /* If it is set and reply is not set, the file is erased. If both are */ |
| 259 | /* set, a notice about the timeout is sent to the poster. If this */ |
| 260 | /* fails due to a message-related error (format, etc) the file is */ |
| 261 | /* erased even though no notice is sent. For temporary errors (like */ |
| 262 | /* out-of-memory) the message is left intact for the next run. If the */ |
| 263 | /* notice is sent successfully, the file is erased. All this is to */ |
| 264 | /* do the best possible without risking a rerun of the .qmail file, */ |
| 265 | /* which could result in a redelivery of the action request and a */ |
| 266 | /* second (incorrect) reply to the moderator's request. */ |
| 267 | |
| 268 | /* NOTE: ALL non-hidden files in this dir are processed and merci- */ |
| 269 | /* lessly deleted. No checks for proper file name. E.g. 'HELLO' */ |
| 270 | /* => time 0 => will be deleted on the next ezmlm-clean run. */ |
| 271 | { |
| 272 | DIR *moddir; |
| 273 | direntry *d; |
| 274 | unsigned long modtime; |
| 275 | struct stat st; |
| 276 | |
| 277 | moddir = opendir(dirname); |
| 278 | if (!moddir) |
| 279 | strerr_die6sys(0,FATAL,ERR_OPEN,dir,"/",dirname,": "); |
| 280 | while ((d = readdir(moddir))) { |
| 281 | if (d->d_name[0] == '.') continue; |
| 282 | scan_ulong(d->d_name,&modtime); |
| 283 | if (modtime < older) { |
| 284 | if (!stralloc_copys(&fnmsg,dirname)) die_nomem(); |
| 285 | if (!stralloc_cats(&fnmsg,d->d_name)) die_nomem(); |
| 286 | if (!stralloc_0(&fnmsg)) die_nomem(); |
| 287 | if((stat(fnmsg.s,&st) != -1) && (st.st_mode & 0200)) { |
| 288 | if(reply && (st.st_mode & 0100)) { |
| 289 | /* unlink unless there was a TEMPORARY */ |
| 290 | /* not message-related error notifying */ |
| 291 | /* poster and msg x bit set. Leave r/o*/ |
| 292 | /* messages alone. Non-x bit msg are */ |
| 293 | /* trash. Just unlink, don't notify */ |
| 294 | sendnotice(fnmsg.s); |
| 295 | unlink(fnmsg.s); |
| 296 | } else |
| 297 | unlink(fnmsg.s); |
| 298 | } |
| 299 | } |
| 300 | } |
| 301 | closedir(moddir); |
| 302 | } |
| 303 | |
| 304 | |
| 305 | void main(argc,argv) |
| 306 | int argc; |
| 307 | char **argv; |
| 308 | { |
| 309 | int fdlock; |
| 310 | int delay; |
| 311 | int opt; |
| 312 | (void) umask(022); |
| 313 | sig_pipeignore(); |
| 314 | when = now(); |
| 315 | |
| 316 | while ((opt = getopt(argc,argv,"mMrRvV")) != opteof) |
| 317 | switch(opt) { |
| 318 | case 'm': flagmime = 1; break; |
| 319 | case 'M': flagmime = 0; break; |
| 320 | case 'r': flagreturn = 1; break; |
| 321 | case 'R': flagreturn = 0; break; |
| 322 | case 'v': |
| 323 | case 'V': strerr_die2x(0,"ezmlm-clean version: ", EZIDX_VERSION); |
| 324 | /* not reached */ |
| 325 | default: |
| 326 | die_usage(); |
| 327 | } |
| 328 | |
| 329 | dir = argv[optind]; |
| 330 | if (!dir) die_usage(); |
| 331 | |
| 332 | if (chdir(dir) == -1) |
| 333 | strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": "); |
| 334 | |
| 335 | getconf_line(&modtime,"modtime",0,FATAL,dir); |
| 336 | if (!stralloc_0(&modtime)) die_nomem(); |
| 337 | scan_ulong(modtime.s,&delay); |
| 338 | if (!delay) delay = DELAY_DEFAULT; |
| 339 | else if (delay < DELAY_MIN) delay = DELAY_MIN; |
| 340 | else if (delay > DELAY_MAX) delay = DELAY_MAX; |
| 341 | older = (unsigned long) when - 3600L * delay; /* delay is in hours */ |
| 342 | |
| 343 | fdlock = open_append("mod/lock"); |
| 344 | if (fdlock == -1) |
| 345 | strerr_die4sys(0,FATAL,ERR_OPEN,dir,"/mod/lock: "); |
| 346 | if (lock_ex(fdlock) == -1) |
| 347 | strerr_die4sys(0,FATAL,ERR_OBTAIN,dir,"/mod/lock: "); |
| 348 | |
| 349 | flagconf = 0; |
| 350 | dodir("mod/pending/",flagreturn); |
| 351 | dodir("mod/accepted/",0); |
| 352 | dodir("mod/rejected/",0); |
| 353 | _exit(0); |
| 354 | } |
| 355 | |