chiark / gitweb /
5c441cf6ae7af414dfb78e95e7de1e73e7045cea
[qmail] / qmail-local.c
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include "readwrite.h"
4 #include "sig.h"
5 #include "env.h"
6 #include "byte.h"
7 #include "exit.h"
8 #include "fork.h"
9 #include "open.h"
10 #include "wait.h"
11 #include "lock.h"
12 #include "seek.h"
13 #include "substdio.h"
14 #include "getln.h"
15 #include "subfd.h"
16 #include "sgetopt.h"
17 #include "alloc.h"
18 #include "error.h"
19 #include "stralloc.h"
20 #include "fmt.h"
21 #include "str.h"
22 #include "now.h"
23 #include "case.h"
24 #include "quote.h"
25 #include "qmail.h"
26 #include "slurpclose.h"
27 #include "myctime.h"
28 #include "gfrom.h"
29 #include "auto_patrn.h"
30
31 void err(s) char *s; { substdio_putsflush(subfderr,s); }
32 void soft() { _exit(111); }
33 void hard() { _exit(100); }
34
35 void temp_childcrashed() { err("Aack, child crashed. (#4.3.0)\n"); soft(); }
36 void temp_rewind() { err("Unable to rewind message. (#4.3.0)\n"); soft(); }
37 void temp_fork() { err("Unable to fork. (#4.3.0)\n"); soft(); }
38 void temp_read() { err("Error while reading message. (#4.3.0)\n"); soft(); }
39 void temp_write() { err("Error while writing message. (#4.3.0)\n"); soft(); }
40 void temp_child() { err("Temporary error in forwarding message. (#4.3.0)\n"); soft(); }
41 void temp_maildirtimeout() { err("Timeout on maildir delivery. (#4.3.0)\n"); soft(); }
42 void temp_maildir() { err("Temporary error on maildir delivery. (#4.3.0)\n"); soft(); }
43 void temp_nomaildir() { err("Unable to chdir to maildir. (#4.2.1)\n"); soft(); }
44 void temp_open(fn) char *fn; { err("Unable to open "); err(fn); err(". (#4.2.1)\n"); soft(); }
45
46 void temp_blankline() { err("Uh-oh: first line of .qmail file is blank. (#4.2.1)\n"); soft(); }
47 void temp_fofile() { err("Uh-oh: .qmail has file delivery but has x bit set. (#4.7.0)\n"); soft(); }
48 void temp_foprog() { err("Uh-oh: .qmail has prog delivery but has x bit set. (#4.7.0)\n"); soft(); }
49 void temp_nomem() { err("Out of memory. (#4.3.0)\n"); soft(); }
50 void temp_chdir() { err("Unable to switch to home directory. (#4.3.0)\n"); soft(); }
51 void temp_homestat() { err("Unable to stat home directory. (#4.3.0)\n"); soft(); }
52 void temp_homesticky() { err("Home directory is sticky: user is editing his .qmail file. (#4.2.1)\n"); soft(); }
53 void temp_homewritable() { err("Uh-oh: home directory is writable. (#4.7.0)\n"); soft(); }
54 void temp_qmwritable() { err("Uh-oh: .qmail file is writable. (#4.7.0)\n"); soft(); }
55 void temp_nfsqmail() { err("Temporary error trying to open .qmail file. (#4.3.0)\n"); soft(); }
56 void temp_denyqmail() { err("Permission error trying to open .qmail file. (#4.3.0)\n"); soft(); }
57 void temp_slowlock() { err("File has been locked for 30 seconds straight. (#4.3.0)\n"); soft(); }
58
59 void bounce_childperm() { err("Permanent error in forwarding message. (#5.2.4)\n"); hard(); }
60 void bounce_loop() { err("This message is looping: it already has my Delivered-To line. (#5.4.6)\n"); hard(); }
61 void bounce_ext() { err("Sorry, no mailbox here by that name. (#5.1.1)\n"); hard(); }
62 void usage() { err("qmail-local: usage: qmail-local [ -nN ] user homedir local dash ext domain sender aliasempty\n"); hard(); }
63
64 void warn_homesticky() { err("Warning: home directory is sticky.\n"); }
65
66 int flagdoit;
67 int flag99;
68
69 char *user;
70 char *homedir;
71 char *local;
72 char *dash;
73 char *ext;
74 char *host;
75 char *sender;
76 char *aliasempty;
77
78 stralloc dashext = {0};
79 stralloc ufline = {0};
80 stralloc rpline = {0};
81 stralloc envrecip = {0};
82 stralloc dtline = {0};
83 stralloc qme = {0};
84 stralloc ueo = {0};
85 stralloc cmds = {0};
86 stralloc messline = {0};
87 stralloc foo = {0};
88
89 char buf[1024];
90 char outbuf[1024];
91
92 /* child process */
93
94 char fntmptph[80 + FMT_ULONG * 2];
95 char fnnewtph[80 + FMT_ULONG * 2];
96 void tryunlinktmp() { unlink(fntmptph); }
97 void sigalrm() { tryunlinktmp(); _exit(3); }
98
99 void maildir_child(dir)
100 char *dir;
101 {
102  unsigned long pid;
103  unsigned long time;
104  char host[64];
105  char *s;
106  int loop;
107  struct stat st;
108  int fd;
109  substdio ss;
110  substdio ssout;
111
112  sig_alarmcatch(sigalrm);
113  if (chdir(dir) == -1) { if (error_temp(errno)) _exit(1); _exit(2); }
114  pid = getpid();
115  host[0] = 0;
116  gethostname(host,sizeof(host));
117  for (loop = 0;;++loop)
118   {
119    time = now();
120    s = fntmptph;
121    s += fmt_str(s,"tmp/");
122    s += fmt_ulong(s,time); *s++ = '.';
123    s += fmt_ulong(s,pid); *s++ = '.';
124    s += fmt_strn(s,host,sizeof(host)); *s++ = 0;
125    if (stat(fntmptph,&st) == -1) if (errno == error_noent) break;
126    /* really should never get to this point */
127    if (loop == 2) _exit(1);
128    sleep(2);
129   }
130  str_copy(fnnewtph,fntmptph);
131  byte_copy(fnnewtph,3,"new");
132
133  alarm(86400);
134  fd = open_excl(fntmptph);
135  if (fd == -1) _exit(1);
136
137  substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
138  substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
139  if (substdio_put(&ssout,rpline.s,rpline.len) == -1) goto fail;
140  if (substdio_put(&ssout,dtline.s,dtline.len) == -1) goto fail;
141
142  switch(substdio_copy(&ssout,&ss))
143   {
144    case -2: tryunlinktmp(); _exit(4);
145    case -3: goto fail;
146   }
147
148  if (substdio_flush(&ssout) == -1) goto fail;
149  if (fsync(fd) == -1) goto fail;
150  if (close(fd) == -1) goto fail; /* NFS dorks */
151
152  if (link(fntmptph,fnnewtph) == -1) goto fail;
153    /* if it was error_exist, almost certainly successful; i hate NFS */
154  tryunlinktmp(); _exit(0);
155
156  fail: tryunlinktmp(); _exit(1);
157 }
158
159 /* end child process */
160
161 void maildir(fn)
162 char *fn;
163 {
164  int child;
165  int wstat;
166
167  if (seek_begin(0) == -1) temp_rewind();
168
169  switch(child = fork())
170   {
171    case -1:
172      temp_fork();
173    case 0:
174      maildir_child(fn);
175      soft();
176   }
177
178  wait_pid(&wstat,child);
179  if (wait_crashed(wstat))
180    temp_childcrashed();
181  switch(wait_exitcode(wstat))
182   {
183    case 0: break;
184    case 2: temp_nomaildir();
185    case 3: temp_maildirtimeout();
186    case 4: temp_read();
187    default: temp_maildir();
188   }
189 }
190
191 void slowlock() { temp_slowlock(); }
192
193 void mailfile(fn)
194 char *fn;
195 {
196  int fd;
197  substdio ss;
198  substdio ssout;
199  int match;
200  seek_pos pos;
201  int flaglocked;
202
203  if (seek_begin(0) == -1) temp_rewind();
204
205  fd = open_append(fn);
206  if (fd == -1) temp_open(fn);
207
208  sig_alarmcatch(slowlock);
209  alarm(30);
210  flaglocked = (lock_ex(fd) != -1);
211  alarm(0);
212  sig_alarmdefault();
213
214  seek_end(fd);
215  pos = seek_cur(fd);
216
217  substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
218  substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
219  if (substdio_put(&ssout,ufline.s,ufline.len)) goto writeerrs;
220  if (substdio_put(&ssout,rpline.s,rpline.len)) goto writeerrs;
221  if (substdio_put(&ssout,dtline.s,dtline.len)) goto writeerrs;
222  for (;;)
223   {
224    if (getln(&ss,&messline,&match,'\n') != 0) 
225     { if (flaglocked) seek_trunc(fd,pos); close(fd); temp_read(); }
226    if (!match && !messline.len) break;
227    if (gfrom(messline.s,messline.len))
228      if (substdio_bput(&ssout,">",1)) goto writeerrs;
229    if (substdio_bput(&ssout,messline.s,messline.len)) goto writeerrs;
230    if (!match)
231     {
232      if (substdio_bputs(&ssout,"\n")) goto writeerrs;
233      break;
234     }
235   }
236  if (substdio_bputs(&ssout,"\n")) goto writeerrs;
237  if (substdio_flush(&ssout)) goto writeerrs;
238  if (fsync(fd) == -1) goto writeerrs;
239  close(fd);
240  return;
241
242  writeerrs:
243  if (flaglocked) seek_trunc(fd,pos);
244  close(fd);
245  temp_write();
246 }
247
248 void mailprogram(prog)
249 char *prog;
250 {
251  int child;
252  char *(args[4]);
253  int wstat;
254
255  if (seek_begin(0) == -1) temp_rewind();
256
257  switch(child = fork())
258   {
259    case -1:
260      temp_fork();
261    case 0:
262      args[0] = "sh"; args[1] = "-c"; args[2] = prog; args[3] = 0;
263      sig_pipedefault();
264      execvp(*args,args);
265      if (errno == error_txtbsy) { err("Text busy. (#4.3.0)\n"); soft(); }
266      if (errno == error_nomem) { err("Out of memory. (#4.3.0)\n"); soft(); }
267      if (errno == error_io) { err("I/O error. (#4.3.0)\n"); soft(); }
268      if (error_temp(errno)) { err("Temporary error. (#4.3.0)\n"); soft(); }
269      err("Unable to execute "); err(*args); err(" (#5.2.4)\n");
270      hard();
271   }
272
273  wait_pid(&wstat,child);
274  if (wait_crashed(wstat))
275    temp_childcrashed();
276  switch(wait_exitcode(wstat))
277   {
278    case 100:
279    case 64: case 65: case 70: case 76: case 77: case 78: case 112: hard();
280    case 0: break;
281    case 99: flag99 = 1; break;
282    default: soft();
283   }
284 }
285
286 unsigned long mailforward_qp = 0;
287
288 void mailforward(recips)
289 char **recips;
290 {
291  struct qmail qqt;
292  substdio ss;
293  int match;
294
295  if (seek_begin(0) == -1) temp_rewind();
296  substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
297
298  if (qmail_open(&qqt) == -1) temp_fork();
299  mailforward_qp = qmail_qp(&qqt);
300  qmail_put(&qqt,dtline.s,dtline.len);
301  do
302   {
303    if (getln(&ss,&messline,&match,'\n') != 0) { qmail_fail(&qqt); break; }
304    qmail_put(&qqt,messline.s,messline.len);
305   }
306  while (match);
307  qmail_from(&qqt,ueo.s);
308  while (*recips) qmail_to(&qqt,*recips++);
309  switch(qmail_close(&qqt))
310   {
311    case QMAIL_TOOLONG: bounce_childperm();
312    case QMAIL_READ: temp_read();
313    case 0: return;
314    default: temp_child();
315   }
316 }
317
318 void bouncexf()
319 {
320  int match;
321  substdio ss;
322
323  if (seek_begin(0) == -1) temp_rewind();
324  substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
325  for (;;)
326   {
327    if (getln(&ss,&messline,&match,'\n') != 0) temp_read();
328    if (!match) break;
329    if (messline.len <= 1)
330      break;
331    if (messline.len == dtline.len)
332      if (!str_diffn(messline.s,dtline.s,dtline.len))
333        bounce_loop();
334   }
335 }
336
337 void checkhome()
338 {
339  struct stat st;
340
341  if (stat(".",&st) == -1) temp_homestat();
342  if (st.st_mode & auto_patrn) temp_homewritable();
343  if (st.st_mode & 01000)
344    if (flagdoit) temp_homesticky(); else warn_homesticky();
345 }
346
347 int qmeox(dashowner)
348 char *dashowner;
349 {
350  struct stat st;
351
352  if (!stralloc_copys(&qme,".qmail")) temp_nomem();
353  if (!stralloc_cat(&qme,&dashext)) temp_nomem();
354  if (!stralloc_cats(&qme,dashowner)) temp_nomem();
355  if (!stralloc_0(&qme)) temp_nomem();
356  if (stat(qme.s,&st) == -1)
357   {
358    if (error_temp(errno)) temp_nfsqmail();
359    return -1;
360   }
361  return 0;
362 }
363
364 int qmeopen(cutable)
365 int *cutable;
366 {
367  int fd;
368  struct stat st;
369  int i;
370
371  i = dashext.len;
372  for (;;)
373   {
374    if (!stralloc_copys(&qme,".qmail")) temp_nomem();
375    if (!stralloc_catb(&qme,dashext.s,i)) temp_nomem();
376    if (i < dashext.len) if (!stralloc_cats(&qme,"-default")) temp_nomem();
377    if (!stralloc_0(&qme)) temp_nomem();
378    fd = open_read(qme.s);
379    if (fd == -1)
380     {
381      if (error_temp(errno)) temp_nfsqmail();
382      if (errno == error_perm) temp_denyqmail();
383      if (errno == error_acces) temp_denyqmail();
384     }
385    else
386     {
387      if (fstat(fd,&st) == -1) temp_nfsqmail();
388      if ((st.st_mode & S_IFMT) == S_IFREG)
389       {
390        if (st.st_mode & auto_patrn) temp_qmwritable();
391        *cutable = !!(st.st_mode & 0100);
392        return fd;
393       }
394      close(fd);
395     }
396    if (!i) return -1;
397    do
398      if (dashext.s[--i] == '-') break;
399    while (i);
400   }
401 }
402
403 unsigned long count_file = 0;
404 unsigned long count_forward = 0;
405 unsigned long count_program = 0;
406 char count_buf[FMT_ULONG];
407
408 void count_print()
409 {
410  substdio_puts(subfdoutsmall,"did ");
411  substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_file));
412  substdio_puts(subfdoutsmall,"+");
413  substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_forward));
414  substdio_puts(subfdoutsmall,"+");
415  substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_program));
416  substdio_puts(subfdoutsmall,"\n");
417  if (mailforward_qp)
418   {
419    substdio_puts(subfdoutsmall,"qp ");
420    substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,mailforward_qp));
421    substdio_puts(subfdoutsmall,"\n");
422   }
423  substdio_flush(subfdoutsmall);
424 }
425
426 void sayit(type,cmd,len)
427 char *type;
428 char *cmd;
429 int len;
430 {
431  substdio_puts(subfdoutsmall,type);
432  substdio_put(subfdoutsmall,cmd,len);
433  substdio_putsflush(subfdoutsmall,"\n");
434 }
435
436 void main(argc,argv)
437 int argc;
438 char **argv;
439 {
440  int opt;
441  int i;
442  int j;
443  int k;
444  int fd;
445  int numforward;
446  char **recips;
447  datetime_sec starttime;
448  int flagforwardonly;
449  char *extx;
450
451  umask(077);
452  sig_pipeignore();
453
454  if (!env_init()) temp_nomem();
455
456  flagdoit = 1;
457  while ((opt = getopt(argc,argv,"nN")) != opteof)
458    switch(opt)
459     {
460      case 'n': flagdoit = 0; break;
461      case 'N': flagdoit = 1; break;
462      case '?':
463      default:
464        hard();
465     }
466  argc -= optind;
467  argv += optind;
468
469  if (!(user = *argv++)) usage();
470  if (!(homedir = *argv++)) usage();
471  if (!(local = *argv++)) usage();
472  if (!(dash = *argv++)) usage();
473  if (!(ext = *argv++)) usage();
474  if (!(host = *argv++)) usage();
475  if (!(sender = *argv++)) usage();
476  if (!(aliasempty = *argv++)) usage();
477  if (*argv) usage();
478
479  if (homedir[0] != '/') usage();
480  if (chdir(homedir) == -1) temp_chdir();
481  checkhome();
482
483  if (!env_put2("HOST",host)) temp_nomem();
484  if (!env_put2("HOME",homedir)) temp_nomem();
485  if (!env_put2("USER",user)) temp_nomem();
486  if (!env_put2("LOCAL",local)) temp_nomem();
487
488  if (!stralloc_copys(&envrecip,local)) temp_nomem();
489  if (!stralloc_cats(&envrecip,"@")) temp_nomem();
490  if (!stralloc_cats(&envrecip,host)) temp_nomem();
491
492  if (!stralloc_copy(&foo,&envrecip)) temp_nomem();
493  if (!stralloc_0(&foo)) temp_nomem();
494  if (!env_put2("RECIPIENT",foo.s)) temp_nomem();
495
496  if (!stralloc_copys(&dtline,"Delivered-To: ")) temp_nomem();
497  if (!stralloc_cat(&dtline,&envrecip)) temp_nomem();
498  for (i = 0;i < dtline.len;++i) if (dtline.s[i] == '\n') dtline.s[i] = '_';
499  if (!stralloc_cats(&dtline,"\n")) temp_nomem();
500
501  if (!stralloc_copy(&foo,&dtline)) temp_nomem();
502  if (!stralloc_0(&foo)) temp_nomem();
503  if (!env_put2("DTLINE",foo.s)) temp_nomem();
504
505  if (flagdoit)
506    bouncexf();
507
508  if (!env_put2("SENDER",sender)) temp_nomem();
509
510  if (!quote2(&foo,sender)) temp_nomem();
511  if (!stralloc_copys(&rpline,"Return-Path: <")) temp_nomem();
512  if (!stralloc_cat(&rpline,&foo)) temp_nomem();
513  for (i = 0;i < rpline.len;++i) if (rpline.s[i] == '\n') rpline.s[i] = '_';
514  if (!stralloc_cats(&rpline,">\n")) temp_nomem();
515
516  if (!stralloc_copy(&foo,&rpline)) temp_nomem();
517  if (!stralloc_0(&foo)) temp_nomem();
518  if (!env_put2("RPLINE",foo.s)) temp_nomem();
519
520  if (!stralloc_copys(&ufline,"From ")) temp_nomem();
521  if (*sender)
522   {
523    int len; int i; char ch;
524
525    len = str_len(sender);
526    if (!stralloc_readyplus(&ufline,len)) temp_nomem();
527    for (i = 0;i < len;++i)
528     {
529      ch = sender[i];
530      if ((ch == ' ') || (ch == '\t') || (ch == '\n')) ch = '-';
531      ufline.s[ufline.len + i] = ch;
532     }
533    ufline.len += len;
534   }
535  else
536    if (!stralloc_cats(&ufline,"MAILER-DAEMON")) temp_nomem();
537  if (!stralloc_cats(&ufline," ")) temp_nomem();
538  starttime = now();
539  if (!stralloc_cats(&ufline,myctime(starttime))) temp_nomem();
540
541  if (!stralloc_copy(&foo,&ufline)) temp_nomem();
542  if (!stralloc_0(&foo)) temp_nomem();
543  if (!env_put2("UFLINE",foo.s)) temp_nomem();
544
545  if (!stralloc_copys(&dashext,dash)) temp_nomem();
546  if (!stralloc_cats(&dashext,ext)) temp_nomem();
547  for (i = 0;i < dashext.len;++i)
548    if (dashext.s[i] == '.')
549      dashext.s[i] = ':';
550  case_lowerb(dashext.s,dashext.len);
551
552  extx = ext;
553  if (!env_put2("EXT",extx)) temp_nomem();
554  extx += str_chr(extx,'-'); if (*extx) ++extx;
555  if (!env_put2("EXT2",extx)) temp_nomem();
556  extx += str_chr(extx,'-'); if (*extx) ++extx;
557  if (!env_put2("EXT3",extx)) temp_nomem();
558  extx += str_chr(extx,'-'); if (*extx) ++extx;
559  if (!env_put2("EXT4",extx)) temp_nomem();
560
561  flagforwardonly = 0;
562  fd = qmeopen(&flagforwardonly);
563  if (fd == -1) if (*dash) bounce_ext();
564
565  if (!stralloc_copys(&ueo,sender)) temp_nomem();
566  if (str_diff(sender,""))
567    if (str_diff(sender,"#@[]"))
568      if (qmeox("-owner") == 0)
569       {
570        if (qmeox("-owner-default") == 0)
571         {
572          if (!stralloc_copys(&ueo,local)) temp_nomem();
573          if (!stralloc_cats(&ueo,"-owner-@")) temp_nomem();
574          if (!stralloc_cats(&ueo,host)) temp_nomem();
575          if (!stralloc_cats(&ueo,"-@[]")) temp_nomem();
576         }
577        else
578         {
579          if (!stralloc_copys(&ueo,local)) temp_nomem();
580          if (!stralloc_cats(&ueo,"-owner@")) temp_nomem();
581          if (!stralloc_cats(&ueo,host)) temp_nomem();
582         }
583       }
584  if (!stralloc_0(&ueo)) temp_nomem();
585  if (!env_put2("NEWSENDER",ueo.s)) temp_nomem();
586
587  if (!stralloc_ready(&cmds,0)) temp_nomem();
588  cmds.len = 0;
589  if (fd != -1)
590    if (slurpclose(fd,&cmds,256) == -1) temp_nomem();
591
592  if (!cmds.len)
593   {
594    if (!stralloc_copys(&cmds,aliasempty)) temp_nomem();
595    flagforwardonly = 0;
596   }
597  if (!cmds.len || (cmds.s[cmds.len - 1] != '\n'))
598    if (!stralloc_cats(&cmds,"\n")) temp_nomem();
599
600  numforward = 0;
601  i = 0;
602  for (j = 0;j < cmds.len;++j)
603    if (cmds.s[j] == '\n')
604     {
605      switch(cmds.s[i]) { case '#': case '.': case '/': case '|': break;
606        default: ++numforward; }
607      i = j + 1;
608     }
609
610  recips = (char **) alloc((numforward + 1) * sizeof(char *));
611  if (!recips) temp_nomem();
612  numforward = 0;
613
614  flag99 = 0;
615
616  i = 0;
617  for (j = 0;j < cmds.len;++j)
618    if (cmds.s[j] == '\n')
619     {
620      cmds.s[j] = 0;
621      k = j;
622      while ((k > i) && (cmds.s[k - 1] == ' ') || (cmds.s[k - 1] == '\t'))
623        cmds.s[--k] = 0;
624      switch(cmds.s[i])
625       {
626        case 0: /* k == i */
627          if (i) break;
628          temp_blankline();
629        case '#':
630          break;
631        case '.':
632        case '/':
633          ++count_file;
634          if (flagforwardonly) temp_fofile();
635          if (cmds.s[k - 1] == '/')
636            if (flagdoit) maildir(cmds.s + i);
637            else sayit("maildir ",cmds.s + i,k - i);
638          else
639            if (flagdoit) mailfile(cmds.s + i);
640            else sayit("mbox ",cmds.s + i,k - i);
641          break;
642        case '|':
643          ++count_program;
644          if (flagforwardonly) temp_foprog();
645          if (flagdoit) mailprogram(cmds.s + i + 1);
646          else sayit("program ",cmds.s + i + 1,k - i - 1);
647          break;
648        case '+':
649          if (str_equal(cmds.s + i + 1,"list"))
650            flagforwardonly = 1;
651          break;
652        case '&':
653          ++i;
654        default:
655          ++count_forward;
656          if (flagdoit) recips[numforward++] = cmds.s + i;
657          else sayit("forward ",cmds.s + i,k - i);
658          break;
659       }
660      i = j + 1;
661      if (flag99) break;
662     }
663
664  if (numforward) if (flagdoit)
665   {
666    recips[numforward] = 0;
667    mailforward(recips);
668   }
669
670  count_print();
671  _exit(0);
672 }