Develop and Download Open Source Software

Browse Subversion Repository

Contents of /trunk/src/mailbox.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 132 - (show annotations) (download) (as text)
Tue Sep 13 17:40:57 2016 UTC (7 years, 8 months ago) by z0rac
File MIME type: text/x-c++src
File size: 14778 byte(s)


1 /*
2 * Copyright (C) 2009-2016 TSUBAKIMOTO Hiroya <z0rac@users.sourceforge.jp>
3 *
4 * This software comes with ABSOLUTELY NO WARRANTY; for details of
5 * the license terms, see the LICENSE.txt file included with the program.
6 */
7 #include "mailbox.h"
8 #include "winsock.h"
9 #include <cassert>
10
11 #ifdef _DEBUG
12 #include <iostream>
13 #define DBG(s) s
14 #define LOG(s) (cout << s)
15 #else
16 #define DBG(s)
17 #define LOG(s)
18 #endif
19
20 #define TCP_TIMEOUT 60
21
22 /** tcpstream - stream of TCP session.
23 * This instance should be created by the function mailbox::backend::tcp.
24 */
25 namespace {
26 class tcpstream : public mailbox::backend::stream {
27 winsock::tcpclient _socket;
28 int _verifylevel;
29 public:
30 tcpstream(int verifylevel) : _verifylevel(verifylevel) {}
31 ~tcpstream() { _socket.shutdown(); }
32 void connect(const string& host, const string& port, int domain);
33 size_t read(char* buf, size_t size);
34 size_t write(const char* data, size_t size);
35 int tls() const;
36 mailbox::backend::stream* starttls(const string& host);
37 };
38 }
39
40 void
41 tcpstream::connect(const string& host, const string& port, int domain)
42 {
43 assert(_socket == INVALID_SOCKET);
44 _socket.connect(host, port, domain).timeout(TCP_TIMEOUT);
45 }
46
47 size_t
48 tcpstream::read(char* buf, size_t size)
49 {
50 return _socket.recv(buf, size);
51 }
52
53 size_t
54 tcpstream::write(const char* data, size_t size)
55 {
56 return _socket.send(data, size);
57 }
58
59 /** sslstream - stream of SSL session.
60 * This instance should be created by the function mailbox::backend::ssl().
61 */
62 #if USE_OPENSSL
63 #include "win32.h"
64 namespace {
65 extern "C" {
66 typedef struct { int ssl; } SSL;
67 typedef struct { int ctx; } SSL_CTX;
68 typedef struct { int x509; } X509;
69 typedef struct { int name; } X509NAME;
70
71 typedef int (*SSL_library_init)(void);
72 typedef SSL_CTX* (*SSL_CTX_new)(void*);
73 typedef void (*SSL_CTX_free)(SSL_CTX*);
74 typedef void* (*SSLv23_client_method)(void);
75 typedef long (*SSL_CTX_set_options)(SSL_CTX*, long);
76 typedef SSL* (*SSL_new)(SSL_CTX*);
77 typedef void (*SSL_free)(SSL*);
78 typedef int (*SSL_set_fd)(SSL*, int);
79 typedef int (*SSL_connect)(SSL*);
80 typedef int (*SSL_read)(SSL*, void*, int);
81 typedef int (*SSL_write)(SSL*, const void*, int);
82 typedef int (*SSL_shutdown)(SSL*);
83 typedef int (*SSL_get_error)(const SSL*, int);
84 typedef int (*SSL_CTX_load_verify_locations)(SSL_CTX*, const char*, const char*);
85 typedef int (*SSL_get_verify_result)(const SSL*);
86 typedef X509* (*SSL_get_peer_certificate)(const SSL*);
87
88 typedef unsigned long (*ERR_get_error)(void);
89 typedef char* (*ERR_error_string)(unsigned long, char*);
90 typedef void (*X509_free)(X509*);
91 typedef int (*X509_get_ext_by_NID)(X509*, int, int);
92 typedef void* (*X509_get_ext)(X509*, int);
93 typedef void* (*X509V3_EXT_d2i)(void*);
94 typedef int (*sk_num)(const void*);
95 typedef void* (*sk_value)(const void*, int);
96 typedef void (*sk_free)(void*);
97 typedef char* (*ASN1_STRING_data)(void*);
98 typedef int (*ASN1_STRING_length)(void*);
99 typedef X509NAME* (*X509_get_subject_name)(X509*);
100 typedef int (*X509_NAME_get_text_by_NID)(X509NAME*, int, char*, int);
101 }
102
103 struct openssl {
104 win32::dll ssleay;
105 win32::dll libeay;
106 openssl();
107 static void np() {}
108 };
109 openssl _openssl;
110
111 #define SSL(name) name(_openssl.ssleay(#name))
112 #define XSSL(name) name(_openssl.ssleay(#name, FARPROC(openssl::np)))
113 #define LIB(name) name(_openssl.libeay(#name))
114
115 class sslstream : public mailbox::backend::stream {
116 SSL_CTX* _ctx;
117 SSL* _ssl;
118 SSL_read _read;
119 SSL_write _write;
120 winsock::tcpclient _socket;
121 int _verifylevel;
122 int _want(int rc);
123 void _connect();
124 void _shutdown();
125 bool _verify(const string& host);
126 static bool _match(const char* host, const char* subj, int len);
127 public:
128 sslstream(int verifylevel);
129 ~sslstream();
130 void connect(SOCKET socket, const string& host);
131 void connect(const string& host, const string& port, int domain);
132 size_t read(char* buf, size_t size);
133 size_t write(const char* data, size_t size);
134 int tls() const { return 1; }
135 mailbox::backend::stream* starttls(const string&) { return NULL; }
136 static bool avail() { return _openssl.ssleay && _openssl.libeay; }
137 public:
138 struct error : public mailbox::error {
139 error() : mailbox::error(emsg()) {}
140 static string emsg();
141 };
142 friend struct error;
143 };
144 }
145
146 openssl::openssl()
147 : ssleay("ssleay32.dll"), libeay("libeay32.dll")
148 {
149 if (ssleay && libeay) XSSL(SSL_library_init)();
150 }
151
152 string
153 sslstream::error::emsg()
154 {
155 char buf[256];
156 return LIB(ERR_error_string)(LIB(ERR_get_error)(), buf);
157 }
158
159 sslstream::sslstream(int verifylevel)
160 : _ssl(NULL), _read(SSL(SSL_read)), _write(SSL(SSL_write)),
161 _verifylevel(verifylevel)
162 {
163 _ctx = SSL(SSL_CTX_new)(SSL(SSLv23_client_method)());
164 if (!_ctx) throw error();
165 #if !USE_SSL2
166 XSSL(SSL_CTX_set_options)(_ctx, 0x01000000L /* SSL_OP_NO_SSLv2 */);
167 #endif
168 if (_verifylevel > 1) {
169 XSSL(SSL_CTX_load_verify_locations)(_ctx, "./ca-root.crt", NULL);
170 }
171 }
172
173 sslstream::~sslstream()
174 {
175 _shutdown();
176 XSSL(SSL_CTX_free)(_ctx);
177 }
178
179 int
180 sslstream::_want(int rc)
181 {
182 if (rc > 0) return rc;
183 switch (SSL(SSL_get_error)(_ssl, rc)) {
184 case 2: /* SSL_ERROR_WANT_READ */ return rc;
185 case 3: /* SSL_ERROR_WANT_WRITE */ return rc;
186 case 6: /* SSL_ERROR_ZERO_RETURN */ return 0;
187 }
188 throw error();
189 }
190
191 void
192 sslstream::_connect()
193 {
194 assert(_socket != INVALID_SOCKET && !_ssl);
195 try {
196 _ssl = SSL(SSL_new)(_ctx);
197 if (!_ssl ||
198 SSL(SSL_set_fd)(_ssl, _socket) != 1 ||
199 SSL(SSL_connect)(_ssl) != 1) throw error();
200 } catch (...) {
201 if (_ssl) XSSL(SSL_free)(_ssl), _ssl = NULL;
202 throw;
203 }
204 }
205
206 void
207 sslstream::_shutdown()
208 {
209 if (_ssl) {
210 XSSL(SSL_shutdown)(_ssl);
211 XSSL(SSL_free)(_ssl);
212 _ssl = NULL;
213 }
214 }
215
216 bool
217 sslstream::_verify(const string& host)
218 {
219 switch (_verifylevel) {
220 case 0: return true;
221 case 1: break;
222 default:
223 switch(SSL(SSL_get_verify_result)(_ssl)) {
224 case 0: break; /* X509_V_OK */
225 case 18: /* X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT */
226 if (_verifylevel < 3) break;
227 default: return false;
228 }
229 }
230
231 int hostn = host.size();
232 const char* hostp = host.c_str();
233 struct buf {
234 char* data;
235 buf(size_t n) : data(new char[n]) {}
236 ~buf() { delete [] data; }
237 } buf(hostn + 1);
238
239 X509* x509 = SSL(SSL_get_peer_certificate)(_ssl);
240 if (!x509) return false;
241 bool cn = true, ok = false;
242 int id = LIB(X509_get_ext_by_NID)(x509, 85 /*NID_subject_alt_name*/, -1);
243 if (id >= 0) {
244 void* alt = LIB(X509V3_EXT_d2i)(LIB(X509_get_ext)(x509, id));
245 if (alt) {
246 sk_value value = LIB(sk_value);
247 ASN1_STRING_data data = LIB(ASN1_STRING_data);
248 ASN1_STRING_length length = LIB(ASN1_STRING_length);
249 int n = LIB(sk_num)(alt);
250 for (int i = 0; i < n && !ok; ++i) {
251 typedef struct { int type; union { void* ia5; } d; } GENERAL_NAME;
252 GENERAL_NAME* p = (GENERAL_NAME*)value(alt, i);
253 if (p->type == 2 /*GEN_DNS*/) {
254 ok = _match(hostp, data(p->d.ia5), length(p->d.ia5));
255 cn = false;
256 }
257 }
258 LIB(sk_free)(alt);
259 }
260 }
261 if (cn) {
262 X509NAME* name = LIB(X509_get_subject_name)(x509);
263 if (name) {
264 X509_NAME_get_text_by_NID get = LIB(X509_NAME_get_text_by_NID);
265 int len = get(name, 13 /*NID_commonName*/, NULL, 0);
266 if (len > 0 && len <= hostn &&
267 get(name, 13 /*NID_commonName*/, buf.data, len + 1) > 0) {
268 ok = _match(hostp, buf.data, len);
269 }
270 }
271 }
272 LIB(X509_free)(x509);
273 return ok;
274 }
275
276 bool
277 sslstream::_match(const char* host, const char* subj, int len)
278 {
279 LOG(host << " : " << string(subj, len) << endl);
280 for (int i = 0; i < len; ++i) {
281 if (subj[i] == '*' && i + 1 < len && subj[i + 1] == '.') {
282 while (*host && *host != '.') ++host;
283 ++i;
284 } else {
285 if (tolower(*host) != tolower(subj[i])) return false;
286 }
287 if (!*host++) return false;
288 }
289 return !*host;
290 }
291
292 void
293 sslstream::connect(SOCKET socket, const string& host)
294 {
295 assert(_socket == INVALID_SOCKET);
296 _socket(socket).timeout(TCP_TIMEOUT);
297 _connect();
298 if (!_verify(host)) throw mailbox::error("invalid host");
299 }
300
301 void
302 sslstream::connect(const string& host, const string& port, int domain)
303 {
304 assert(_socket == INVALID_SOCKET);
305 _socket.connect(host, port, domain).timeout(TCP_TIMEOUT);
306 _connect();
307 if (!_verify(host)) throw mailbox::error("invalid host");
308 }
309
310 size_t
311 sslstream::read(char* buf, size_t size)
312 {
313 assert(_ssl);
314 int n = -1;
315 while (n < 0) n = _want(_read(_ssl, buf, size));
316 return size_t(n);
317 }
318
319 size_t
320 sslstream::write(const char* data, size_t size)
321 {
322 assert(_ssl);
323 int n = -1;
324 while (n < 0) n = _want(_write(_ssl, data, size));
325 return size_t(n);
326 }
327
328 int
329 tcpstream::tls() const
330 {
331 return sslstream::avail() ? 0 : -1;
332 }
333
334 #undef LIB
335 #undef XSSL
336 #undef SSL
337 #else // !USE_OPENSSL
338 namespace {
339 class sslstream : public mailbox::backend::stream {
340 struct tls : public winsock::tlsclient {
341 winsock::tcpclient socket;
342 #if USE_SSL2
343 tls() : winsock::tlsclient(SP_PROT_SSL2 | SP_PROT_SSL3) {}
344 #endif
345 ~tls() { shutdown(); }
346 size_t _recv(char* buf, size_t size);
347 size_t _send(const char* data, size_t size);
348 };
349 tls _tls;
350 int _verifylevel;
351 void _connect(const string& host);
352 public:
353 sslstream(int verifylevel) : _verifylevel(verifylevel) {}
354 void connect(SOCKET socket, const string& host);
355 void connect(const string& host, const string& port, int domain);
356 size_t read(char* buf, size_t size);
357 size_t write(const char* data, size_t size);
358 int tls() const { return 1; }
359 mailbox::backend::stream* starttls(const string&) { return NULL; }
360 };
361 }
362
363 size_t
364 sslstream::tls::_recv(char* buf, size_t size)
365 {
366 assert(socket != INVALID_SOCKET);
367 return socket.recv(buf, size);
368 }
369
370 size_t
371 sslstream::tls::_send(const char* data, size_t size)
372 {
373 assert(socket != INVALID_SOCKET);
374 return socket.send(data, size);
375 }
376
377 void
378 sslstream::_connect(const string& host)
379 {
380 _tls.connect();
381 if (_verifylevel) {
382 DWORD ignore = 0;
383 switch (_verifylevel) {
384 case 1: ignore |= (SECURITY_FLAG_IGNORE_REVOCATION |
385 SECURITY_FLAG_IGNORE_WRONG_USAGE |
386 SECURITY_FLAG_IGNORE_CERT_DATE_INVALID);
387 case 2: ignore |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
388 }
389 if (!_tls.verify(host, ignore)) throw mailbox::error("invalid host");
390 }
391 }
392
393 void
394 sslstream::connect(SOCKET socket, const string& host)
395 {
396 assert(_tls.socket == INVALID_SOCKET);
397 _tls.socket(socket).timeout(TCP_TIMEOUT);
398 _connect(host);
399 }
400
401 void
402 sslstream::connect(const string& host, const string& port, int domain)
403 {
404 assert(_tls.socket == INVALID_SOCKET);
405 _tls.socket.connect(host, port, domain).timeout(TCP_TIMEOUT);
406 _connect(host);
407 }
408
409 size_t
410 sslstream::read(char* buf, size_t size)
411 {
412 return _tls.recv(buf, size);
413 }
414
415 size_t
416 sslstream::write(const char* data, size_t size)
417 {
418 return _tls.send(data, size);
419 }
420
421 int
422 tcpstream::tls() const
423 {
424 return 0;
425 }
426 #endif // !USE_OPENSSL
427
428 mailbox::backend::stream*
429 tcpstream::starttls(const string& host)
430 {
431 assert(_socket != INVALID_SOCKET);
432 unique_ptr<sslstream> st(new sslstream(_verifylevel));
433 st->connect(_socket.release(), host);
434 return st.release();
435 }
436
437 /*
438 * Functions of the class mailbox::backend
439 */
440 void
441 mailbox::backend::tcp(const string& host, const string& port, int domain, int verify)
442 {
443 unique_ptr<tcpstream> st(new tcpstream(verify));
444 st->connect(host, port, domain);
445 _st.reset(st.release());
446 }
447
448 void
449 mailbox::backend::ssl(const string& host, const string& port, int domain, int verify)
450 {
451 unique_ptr<sslstream> st(new sslstream(verify));
452 st->connect(host, port, domain);
453 _st.reset(st.release());
454 }
455
456 void
457 mailbox::backend::starttls(const string& host)
458 {
459 stream* st = _st->starttls(host);
460 if (st) _st.reset(st);
461 }
462
463 string
464 mailbox::backend::read(size_t size)
465 {
466 string result;
467 while (size) {
468 char buf[1024];
469 size_t n = _st->read(buf, min(size, sizeof(buf)));
470 if (!n) throw mailbox::error("connection reset");
471 result.append(buf, n);
472 size -= n;
473 }
474 return result;
475 }
476
477 string
478 mailbox::backend::read()
479 {
480 char c;
481 if (!_st->read(&c, 1)) throw mailbox::error("connection reset");
482 string result(1, c);
483 do {
484 do {
485 if (!_st->read(&c, 1)) throw mailbox::error("connection reset");
486 result.push_back(c);
487 } while (c != '\012');
488 } while (result[result.size() - 2] != '\015');
489 return result.erase(result.size() - 2); // remove CRLF
490 }
491
492 void
493 mailbox::backend::write(const char* data, size_t size)
494 {
495 while (size) {
496 size_t n = _st->write(data, size);
497 data += n, size -= n;
498 }
499 }
500
501 void
502 mailbox::backend::write(const string& data)
503 {
504 string ln(data);
505 ln.append("\015\012");
506 write(ln.data(), ln.size());
507 }
508
509 /*
510 * Functions of the class mailbox
511 */
512 mailbox&
513 mailbox::uripasswd(const string& uri, const string& passwd)
514 {
515 _uri = ::uri(uri);
516 _passwd = passwd;
517 return *this;
518 }
519
520 const mail*
521 mailbox::find(const string& uid) const
522 {
523 list<mail>::const_iterator p = _mails.begin();
524 for (; p != _mails.end(); ++p) {
525 if (p->uid() == uid) return &*p;
526 }
527 return NULL;
528 }
529
530 void
531 mailbox::fetchmail()
532 {
533 extern backend* backendIMAP4();
534 extern backend* backendPOP3();
535
536 static const struct {
537 const char* scheme;
538 backend* (*make)();
539 void (backend::*stream)(const string&, const string&, int, int);
540 const char* port;
541 } backends[] = {
542 { "imap", backendIMAP4, &backend::tcp, "143" },
543 { "imap+ssl", backendIMAP4, &backend::ssl, "993" },
544 { "pop", backendPOP3, &backend::tcp, "110" },
545 { "pop+ssl", backendPOP3, &backend::ssl, "995" },
546 };
547
548 _recent = -1;
549 int i = sizeof(backends) / sizeof(*backends);
550 while (i-- && _uri[uri::scheme] != backends[i].scheme) continue;
551 if (i < 0) throw error("invalid scheme");
552
553 uri u(_uri);
554 string pw(_passwd);
555 if (u[uri::port].empty()) u[uri::port] = backends[i].port;
556 if (u[uri::user].empty()) {
557 u[uri::user] = "ANONYMOUS";
558 if (pw.empty()) pw = "befoo@";
559 }
560 unique_ptr<backend> be(backends[i].make());
561 ((*be).*backends[i].stream)(u[uri::host], u[uri::port], _domain, _verify);
562 be->login(u, pw);
563 _recent = static_cast<int>(be->fetch(*this, u));
564 be->logout();
565 }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26