2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2003 Net Integration Technologies, Inc.
5 * Avery's insanely fast alternative to Fetchmail -- Pop3 Client
7 * This code is LGPL - see the file COPYING.LIB for a full copy of the
13 #include "wvpopclient.h"
14 #include "wvistreamlist.h"
19 #define MAX_PROCESSES 5
20 #define MAX_REQUESTS 10
23 WvPopClient::WvPopClient(WvStream
*conn
, WvStringParm acct
,
24 WvStringParm _password
,
25 WvStringParm _deliverto
, WvStringParm _mda
,
26 bool _flushing
, bool _apop_enable
,
27 bool _apop_enable_fallback
, bool _explode
,
28 bool _safemode
, bool _ignorerp
)
29 : WvStreamClone(conn
), sendprocs(10),
30 username(acctparse(acct
)), // I hate constructors!
31 password(_password
), deliverto(_deliverto
), mda(_mda
),
32 log(WvString("PopRetriever %s", acct
), WvLog::Debug3
),
35 uses_continue_select
= true;
36 personal_stack_size
= 65536;
39 apop_enable
= _apop_enable
;
40 apop_enable_fallback
= _apop_enable_fallback
;
41 next_req
= next_ack
= sendmails
= 0;
45 max_requests
= MAX_REQUESTS
;
51 log(WvLog::Info
, "Retrieve mail from %s into %s.\n", acct
, deliverto
);
55 WvPopClient::~WvPopClient()
57 if (isok()) // someone is killing us before our time
58 seterr("Closed connection at user request.");
60 terminate_continue_select();
62 // disable callbacks for all remaining sendmail procs
63 WvSendmailProcDict::Iter
i(sendprocs
);
64 for (i
.rewind(); i
.next(); )
65 i
->cb
= WvSendmailCallback();
68 log(WvLog::Error
, "Aborted. Error was: %s\n", errstr());
70 log(WvLog::Info
, "Done.\n");
76 WvString
WvPopClient::acctparse(WvStringParm acct
)
79 char *cptr
= strrchr(u
.edit(), '@');
87 void WvPopClient::cmd(WvStringParm s
)
89 // If we're already dead, we should try doing anything...
95 if (!strncasecmp(s
, "pass ", 5))
96 trace
.append(new WvString("pass"), true);
97 else if (!strncasecmp(s
, "apop ", 5))
98 trace
.append(new WvString("apop"), true);
101 if (!strncasecmp(s
, "apop ", 5))
102 trace
.append(new WvString("apop"), true);
104 trace
.append(new WvString(s
), true);
107 if (!strncasecmp(s
, "retr ", 5)) // less important
108 log(WvLog::Debug3
, "Send: %s\n", s
);
109 else if (!strncasecmp(s
, "pass ", 5)) // hide passwords
110 log(WvLog::Debug2
, "Send: pass <NOT SHOWN>\n");
112 log(WvLog::Debug2
, "Send: %s\n", s
);
116 bool WvPopClient::response()
122 log(WvLog::Critical
,"Bailing out since the connection died: %s\n",
127 assert(!trace
.isempty());
128 WvStringList::Iter
i(trace
);
129 i
.rewind(); i
.next();
131 char *line
= blocking_getline(60*1000);
139 log(WvLog::Debug2
, "Recv(%s): %s\n", *i
, line
);
142 if (!strncmp(line
, "+OK", 3))
145 sscanf(line
, "+OK %ld %ld", &res1
, &res2
);
153 static bool messcompare(const WvPopClient::MsgInfo
&a
,
154 const WvPopClient::MsgInfo
&b
)
156 const long base
= 10240;
157 long alen
= a
.len
/ base
, blen
= b
.len
/ base
;
158 return ((alen
* base
+ a
.num
) - (blen
* base
+ b
.num
)) < 0;
162 void WvPopClient::pre_select(SelectInfo
&si
)
164 bool oldrd
= si
.wants
.readable
;
167 si
.wants
.readable
= false;
168 WvStreamClone::pre_select(si
);
169 si
.wants
.readable
= oldrd
;
173 bool WvPopClient::post_select(SelectInfo
&si
)
175 bool val
, oldrd
= si
.wants
.readable
;
178 si
.wants
.readable
= false;
179 val
= WvStreamClone::post_select(si
);
180 si
.wants
.readable
= oldrd
;
185 void WvPopClient::execute()
187 const char format
[] = "%20.20s> %-40.40s\n";
188 char *line
, *greeting
, *start
, *end
, *cptr
;
191 bool printed
, in_head
, msgdone
;
193 WvStreamClone::execute();
196 // Adapted for APOP by markj@luminas.co.uk
197 // trace.append(new WvString("HELLO"), true);
198 greeting
= blocking_getline(60*1000);
199 if (!greeting
|| strncmp(greeting
,"+OK", 3)) goto fail
;
200 // if (!response()) goto fail;
201 log(WvLog::Debug1
, "Recv(HELLO): %s", greeting
);
204 if (apop_enable
&& strrchr(greeting
, '>'))
206 // APOP login attempt -- early code from fetchmail
207 /* build MD5 digest from greeting timestamp + password */
208 /* find start of timestamp */
209 for (start
= greeting
; *start
!= 0 && *start
!= '<'; start
++)
211 if (*start
== 0) goto fail
;
213 /* find end of timestamp */
214 for (end
= start
; *end
!= 0 && *end
!= '>'; end
++)
216 if (*end
== 0 || end
== start
+ 1) goto fail
;
219 // end of fetchmail code
221 log(WvLog::Debug2
, "Using APOP seed: %s\n", start
);
223 /* copy timestamp and password into digestion buffer */
224 WvString
digestsecret("%s%s",start
,password
);
226 WvMD5Digest().flushstrbuf(digestsecret
, md5buf
, true /*finish*/);
227 WvString md5hex
= WvHexEncoder().strflushbuf(md5buf
, true);
229 log(WvLog::Debug2
, "Using APOP response: %s\n", md5hex
);
230 cmd("apop %s %s", username
, md5hex
);
233 if (!apop_enable
|| !strrchr(greeting
, '>')
234 || (!response() && apop_enable_fallback
))
237 // some providers (i.e.: gmail) get unhappy if you send the user+pass
238 // together, so wait for a response to the user cmd before sending
240 cmd("user %s", username
);
241 if (!response()) goto fail
;
242 cmd("pass %s", password
);
245 seterr("Server denied access. Wrong password?");
250 // get the number of messages
252 cmd("list"); // for later
253 if (!response()) goto fail
;
254 log(WvLog::Info
, "%s %s in %s bytes.\n", res1
,
255 res1
==1 ? "message" : "messages", res2
);
259 // get the list of messages and their sizes
260 if (!response()) goto fail
;
261 for (count
= 0; count
< nmsgs
; count
++)
263 line
= blocking_getline(60*1000);
264 mess
.push_back(MsgInfo());
265 if (!isok() || !line
||
266 sscanf(line
, "%d %ld",
267 &(mess
[count
].num
), &(mess
[count
].len
)) != 2)
269 log(WvLog::Error
, "Invalid LIST response: '%s'\n", line
);
273 line
= blocking_getline(60*1000);
274 if (!isok() || !line
|| strcmp(line
, ".") && strcmp(line
, ".\r"))
276 log(WvLog::Error
, "Invalid LIST terminator: '%s'\n", line
);
280 // sort the list in order of size
281 std::sort(mess
.begin(), mess
.end(), messcompare
);
283 while (next_ack
< nmsgs
284 || (next_ack
&& mess
[next_ack
-1].deletes_after_this
)
285 || sendmails
|| safe_deletes
.count() > 0)
288 log(WvLog::Debug4
, "next_ack=%s/%s dels=%s sendmails=%s/%s\n",
290 mess
[0].deletes_after_this
, sendmails
,
291 WvSendmailProc::num_sendmails
);
293 // do any necessary safe deletes
294 while (safemode
&& safe_deletes
.count() > 0 &&
295 next_req
-next_ack
< max_requests
)
297 cmd(safe_deletes
.popstr());
300 log(WvLog::Warning
, "Failed safe deleting a message!?\n");
302 log(WvLog::Warning
, "Canceling future deletions "
303 "to protect the innocent.\n");
312 while (next_req
< nmsgs
&& next_req
-next_ack
< max_requests
)
314 cmd("retr %s", mess
[next_req
].num
);
318 MsgInfo
*m
= &mess
[next_ack
];
320 while (next_ack
> 0 && mess
[next_ack
-1].deletes_after_this
)
324 log(WvLog::Warning
, "Failed deleting a message!?\n");
326 log(WvLog::Warning
, "Canceling future deletions "
327 "to protect the innocent.\n");
331 mess
[next_ack
-1].deletes_after_this
--;
337 if (next_ack
>= nmsgs
)
341 never_select
= false;
342 continue; // only deletions and/or sendmails remaining
345 if (!response() && isok())
348 log(WvLog::Warning
, "Hmm... missing message #%s.\n", m
->num
);
354 if (!geterr()) // Keep the older, better?, error message
355 seterr("Aborted.\n");
362 if (m
->len
> 10*1024*1024)
363 size
= WvString("%sM", m
->len
/1024/1024);
364 else if (m
->len
>= 10*1024)
365 size
= WvString("%sk", m
->len
/1024);
366 else if (m
->len
> 1024)
367 size
= WvString("%s.%sk", m
->len
/1024, (m
->len
*10/1024) % 10);
369 size
= WvString("%sb", m
->len
);
371 log(WvLog::Debug1
, "%-6s %6s ",
372 WvString("[%s]", mess
[next_ack
-1].num
), size
);
380 while (WvSendmailProc::num_sendmails
>= MAX_PROCESSES
&& isok())
383 continue_select(1000);
384 never_select
= false;
388 if (access(mda
, X_OK
))
390 log(WvLog::Error
, "%s: %s\n", mda
, strerror(errno
));
395 const char *argv
[] = {mda
, deliverto
, NULL
};
396 //const char *argv[] = {"dd", "of=/dev/null", NULL};
397 WvSendmailProc
*p
= NULL
;
400 p
= new WvSendmailProc(argv
, next_ack
-1,
401 wv::bind(&WvPopClient::send_done
, this,
406 while ((line
= blocking_getline(60*1000)) != NULL
)
412 seterr("Connection dropped while reading message contents!");
416 // remove \r character, if the server gave one
417 cptr
= strchr(line
, 0) - 1;
421 if (line
[0]==0 || line
[0]=='\r')
425 if (!strcmp(line
, ".") || !strcmp(line
, ".\r"))
428 break; // dot on a line by itself: done this message.
432 // POP servers turn any line *starting with* a dot into
433 // a line starting with dot-dot; so remove the extra
439 if (in_head
&& !strncasecmp(line
, "From: ", 6))
441 cptr
= strchr(line
+6, '<');
444 WvString
tmp(cptr
+ 1);
445 cptr
= strchr(tmp
.edit(), '>');
448 from
= WvString("<%s", tmp
);
452 trim_string(from
.edit());
454 else if (in_head
&& explode
&& !p
455 && !strncasecmp(line
, "X-Envelope-To: ", 14))
457 WvString sendto
= line
+strlen("X-Envelope-To:");
459 cptr
= strchr(sendto
, '@');
462 trim_string(sendto
.edit());
463 const char *argvE
[] = {mda
, sendto
, NULL
};
464 p
= new WvSendmailProc(argvE
, next_ack
-1,
465 wv::bind(&WvPopClient::send_done
, this,
469 else if (in_head
&& !strncasecmp(line
, "Subject: ", 9))
472 trim_string(subj
.edit());
475 if (!!from
&& !!subj
&& !printed
)
477 log(WvLog::Debug1
, format
, from
, subj
);
481 // ideally we might try to fix the Return-Path here but that
482 // could turn out to be a parsing nightmare so we're going to
483 // just dump it if their POP3 server mangles it
485 if (p
&& (!in_head
|| !ignorerp
||
486 strncasecmp(line
, "Return-Path: ", 13)))
487 p
->print("%s\n", line
);
490 if (isok() && !msgdone
)
498 if (isok() && !printed
)
500 if (!subj
) subj
= "<NO SUBJECT>";
501 if (!from
) from
= "<NO SENDER";
502 log(WvLog::Debug1
, format
, from
, subj
);
508 WvIStreamList::globallist
.append(p
, true, "sendmail");
509 sendprocs
.add(p
, false);
514 if (!response()) goto fail
;
520 if (cloned
&& cloned
->geterr())
521 seterr("Server connection error: %s", cloned
->errstr());
522 else if (!cloned
|| !cloned
->isok())
523 seterr("Server connection closed unexpectedly (%s bytes left)!",
524 cloned
? inbuf
.used() : 0);
526 seterr("MDA could not be executed!");
528 seterr("Server said something unexpected!");
533 void WvPopClient::send_done(int count
, bool success
)
535 log(WvLog::Debug4
, "send_done %s (success=%s)\n", count
+1, success
);
537 WvSendmailProc
*proc
= sendprocs
[count
];
539 sendprocs
.remove(proc
);
545 log(WvLog::Warning
, "Error delivering message %s to the MDA.\n",
550 mess
[count
].sent
= true;
552 // remember that we had one more delete message after the most recent
553 // request (which might have no relation to the message we're deleting,
554 // but we have to count responses carefully!)
557 // in safe mode we do all deletes at the end
560 log(WvLog::Debug3
, "Queueing message %s for deletion\n",
563 new WvString("dele %s", mess
[count
].num
), true);
567 cmd("dele %s", mess
[count
].num
);
568 mess
[next_req
-1].deletes_after_this
++;
573 // wake up the WvPopClient, which may have been waiting for a sendmail
575 never_select
= false;