summaryrefslogtreecommitdiff
path: root/tools/ward.c
blob: 277fd76df4e7832ec900b0c52f49ccece5f4344b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
//////////////////
// skrewt.c
//
// scrutinize email
//

#include <iostream>
#include <stdlib.h>             /* for exit() */
#include <string>               /* for strcmp() */
#include <ctype.h>              /* toupper */

#include <stdio.h>              /* perror */
#include <sstream>
#include <vector>
#include <list>

using namespace std;

void usage(const int sts){
  (sts ? cerr : cout) <<
"Usage: skrewt [options]\n"
"\n"
"  Scrutinizes email.  Reads stdin, copies it to stdout.\n"
"  Exit result 0 means good, 21 means rejection (spam).\n"
"  Writes reason for rejection to stderr.\n"
"\n"
"  Typically used as a filter in a pipeline, along with spamc -E\n"
"  Options\n"
"    -help              print this msg (and exit immediately).\n"
"    -maxsize ii        msg size in bytes; anything bigger will be rejected.\n"
"    -error-exit        exit early if errors have been detected.\n"
"\n"
"  Messages containing the string '-please-bounce-this-' will be rejected.\n"
"  Messages with no date will be rejected.\n"
;
  exit(sts);
}

#include "libskrewt.h"
#include "utils.h"

string progname, progid;
int mypid;

/* Content-Type: text/plain; charset="us-ascii"                                         */
/* Content-Type: multipart/mixed; boundary="1170861315-1262462055-1341954763=:92165"    */
//


#if 0   /* typical "Received: from" lines */
Received: from lists.sourceforge.net (216.34.181.88)
  by cloud.av8n.com with SMTP; 31 Jul 2012 22:13:48 -0000

Received: from 24-145-119-127-dhcp.gsv.md.atlanticbb.net (HELO mail.phys-l.org) (24.145.119.127)   by cloud.av8n.com with SMTP; 14 Jul 2012 23:56:54 -0000

Received: from ip68-231-191-153.tc.ph.cox.net (HELO asclepias.av8n.net) (smtp@68.231.191.153)   by cloud.av8n.com with SMTP; 15 Jul 2012 14:39:58 -0000
#endif

#if 0   /* good for testing */
// random mail from FAA
/home/jsd/Maildir/cur/1343769926.24228.cloud\:2\,

// has a good SPF result buried inside, at an earlier hop:
/home/jsd/Maildir/cur/1342372942.24810.cloud:2,

// has a good SPF as delivered to us:
/home/jsd/Maildir/cur/1343671179.10420.cloud:2,

// The following msg has no message-id, but does have an
// authorized submitter:
/home/jsd/Maildir/cur/1342363199.24320.cloud:2,
#endif

int skrewt::headers(){
  //xxxx cerr << progid << " begins" << endl;
  for (;;){             // outer loop over all records in the header
    if (cin.eof()) break;
    if (cin.bad()) return 1;

    string line;
// on fail, go back to top of outer loop and check for eof versus bad
    if (getline(cin, line).fail()) continue;
    msgsize += line.length()+1;
    if (msgsize > maxsize) {
      cerr << progid << " rejection: bigger than " << maxsize << endl;
      exeunt(ex_spam);
    }
    cout << line << endl;
    bigbuf.push_back(line);
    string headrec = noCR(line);       // for a folded record, this is the first line

    for (;;) {        // inner loop to build a multi-line record e.g. folded record:
      if (cin.eof()) break;
      if (cin.bad()) return 1;
      char ch;
      if (cin.get(ch).fail()) continue;
      cin.putback(ch);
      if (ch != ' ' && ch != '\t') break;
      string line;
// on fail, go back to top of inner loop and check for eof versus bad
      if (getline(cin, line).fail()) continue;
      msgsize += line.length()+1;
      if (msgsize > maxsize) {
        cerr << progid << " rejection: bigger than " << maxsize << endl;
        exeunt(ex_spam);
      }
      cout << line << endl;
      bigbuf.push_back(line);
      headrec += "\n" + noCR(line);
    }
// here with a fully assembled header record
// headrec (unlike line) contains no DOS CR characters
    int len = headrec.length();
    if (len == 0) {
      saw_blank_line = 1;
      break;            // no more headers in this message
    }

// here if it's a header line
    string headword;
    string rest;
    size_t where = headrec.find(":");
    if (where != string::npos) {
      headword = headrec.substr(0, where);
      rest = ltrim(headrec.substr(1+where));
    }
    headword = toLower(headword);
    if (0){
    } else if (headword == "from") {
      from = rest;
    } else if (headword == "to") {
      to = rest;
    } else if (headword == "return-path") {
      return_path = rest;
    } else if (headword == "message-id") {
      message_id = rest;
    } else if (headword == "received") {
      if (!received_from.length() && prefix("from ", rest)){
        received_from = rest;
      }
    } else if (headword == "date") {
      date = rest;
    } else if (headword == "subject") {
      subject = rest;
    } else if (headword == "content-type") {
      content_type = rest;
    } else if (headword == "delivered-to") {
      delivered_to = rest;
    }
    //xxxx  cout << headrec.length() << " ... ";
    recno++;
    if (0) if (recno <= 6) cerr << progid << "#" << recno
        << " " << headrec << endl;
  }
  return 0;
}


////////////////////////////////////////////////////////////
int main(int _argc, const char** _argv){

  int argc(_argc);
  const char **argv(_argv);
  {
    progname = *argv++; argc--;
    mypid = getpid();
    stringstream binder;
    binder << basename(progname) << "[" << mypid << "]";
    progid = binder.str();
  }

  skrewt mysk;

  while (argc) {
    string arg(*argv); argv++; argc--;
    if (arg.substr(0,2) == "--") arg = arg.substr(1);
    if (prefix(arg, "-help")) {
      usage(0);
    }
    if (0) {
    } else if (prefix(arg, "-mid-required")) {
      mysk.mid_required++;
    } else if (prefix(arg, "-error-exit")) {
      mysk.error_exit++;
    } else if (prefix(arg, "-maxsize")) {
      if (!argc) {
        cerr << "Option -maxsize requires an argument" << endl;
        exit(ex_usage);
      }
      mysk.maxsize = atoi(*argv); argv++; argc--;
    } else if (arg.substr(0,1) == "-") {
      cerr << "Unrecognized option '" << arg << "'" << endl;
      cerr << "For help, try:  " << progname << " -help" << endl;
      exit(ex_usage);
    } else {
      cerr << "Extraneous verbiage '" << arg << "'" << endl;
      cerr << "For help, try:  " << progname << " -help" << endl;
      exit(ex_usage);
    }
  }

  int rslt = mysk.headers();
  if (rslt) return rslt;

// Headers are done.
// Do some early-stage thinking.

  rslt = mysk.interstage();
  if (rslt) return rslt;

  rslt = mysk.body();
  return rslt;

}