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 int messcompare(const WvPopClient::MsgInfo
*a
,
154 const WvPopClient::MsgInfo
*b
)
157 long alen
= a
->len
/ base
, blen
= b
->len
/ base
;
158 return (alen
*base
+ a
->num
) - (blen
*base
+ b
->num
);
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
);
257 mess
.set_capacity(nmsgs
);
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
.append(new MsgInfo(), true);
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 mess
.qsort(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 WvSendmailCallback(this, &WvPopClient::send_done
));
405 while ((line
= blocking_getline(60*1000)) != NULL
)
411 seterr("Connection dropped while reading message contents!");
415 // remove \r character, if the server gave one
416 cptr
= strchr(line
, 0) - 1;
420 if (line
[0]==0 || line
[0]=='\r')
424 if (!strcmp(line
, ".") || !strcmp(line
, ".\r"))
427 break; // dot on a line by itself: done this message.
431 // POP servers turn any line *starting with* a dot into
432 // a line starting with dot-dot; so remove the extra
438 if (in_head
&& !strncasecmp(line
, "From: ", 6))
440 cptr
= strchr(line
+6, '<');
443 WvString
tmp(cptr
+ 1);
444 cptr
= strchr(tmp
.edit(), '>');
447 from
= WvString("<%s", tmp
);
451 trim_string(from
.edit());
453 else if (in_head
&& explode
&& !p
454 && !strncasecmp(line
, "X-Envelope-To: ", 14))
456 WvString sendto
= line
+strlen("X-Envelope-To:");
458 cptr
= strchr(sendto
, '@');
461 trim_string(sendto
.edit());
462 const char *argvE
[] = {mda
, sendto
, NULL
};
463 p
= new WvSendmailProc(argvE
, next_ack
-1,
464 WvSendmailCallback(this, &WvPopClient::send_done
));
467 else if (in_head
&& !strncasecmp(line
, "Subject: ", 9))
470 trim_string(subj
.edit());
473 if (!!from
&& !!subj
&& !printed
)
475 log(WvLog::Debug1
, format
, from
, subj
);
479 // ideally we might try to fix the Return-Path here but that
480 // could turn out to be a parsing nightmare so we're going to
481 // just dump it if their POP3 server mangles it
483 if (p
&& (!in_head
|| !ignorerp
||
484 strncasecmp(line
, "Return-Path: ", 13)))
485 p
->print("%s\n", line
);
488 if (isok() && !msgdone
)
496 if (isok() && !printed
)
498 if (!subj
) subj
= "<NO SUBJECT>";
499 if (!from
) from
= "<NO SENDER";
500 log(WvLog::Debug1
, format
, from
, subj
);
506 WvIStreamList::globallist
.append(p
, true, "sendmail");
507 sendprocs
.add(p
, false);
512 if (!response()) goto fail
;
518 if (cloned
&& cloned
->geterr())
519 seterr("Server connection error: %s", cloned
->errstr());
520 else if (!cloned
|| !cloned
->isok())
521 seterr("Server connection closed unexpectedly (%s bytes left)!",
522 cloned
? inbuf
.used() : 0);
524 seterr("MDA could not be executed!");
526 seterr("Server said something unexpected!");
531 void WvPopClient::send_done(int count
, bool success
)
533 log(WvLog::Debug4
, "send_done %s (success=%s)\n", count
+1, success
);
535 WvSendmailProc
*proc
= sendprocs
[count
];
537 sendprocs
.remove(proc
);
543 log(WvLog::Warning
, "Error delivering message %s to the MDA.\n",
548 mess
[count
]->sent
= true;
550 // remember that we had one more delete message after the most recent
551 // request (which might have no relation to the message we're deleting,
552 // but we have to count responses carefully!)
555 // in safe mode we do all deletes at the end
558 log(WvLog::Debug3
, "Queueing message %s for deletion\n",
561 new WvString("dele %s", mess
[count
]->num
), true);
565 cmd("dele %s", mess
[count
]->num
);
566 mess
[next_req
-1]->deletes_after_this
++;
571 // wake up the WvPopClient, which may have been waiting for a sendmail
573 never_select
= false;