1: // Copyright (c) 2011 Mog Project. All rights reserved.
2:
3: #pragma comment(lib, "cryptlib.lib")
4:
5: #include <fstream>
6: #include <vector>
7:
8: #include <boost/scoped_ptr.hpp>
9: #include <boost/foreach.hpp>
10: #pragma warning(disable: 4512)
11: #include <boost/program_options.hpp>
12: #pragma warning(default: 4512)
13: #include <boost/format.hpp>
14: #include <boost/filesystem.hpp>
15: #include <boost/date_time/local_time_adjustor.hpp>
16: #include <boost/date_time/c_local_time_adjustor.hpp>
17: #include <boost/xpressive/xpressive.hpp>
18: #include <boost/mpl/if.hpp>
19:
20: #pragma warning(push)
21: #pragma warning(disable: 4083 100 127 189 244 505 512 615)
22: #include <cryptopp/aes.h>
23: #include <cryptopp/dh.h>
24: #include <cryptopp/modes.h>
25: #include <cryptopp/osrng.h>
26: #pragma warning(pop)
27:
28: #include "pop3.h"
29: #include "base64.h"
30:
31: using boost::posix_time::ptime;
32: using CryptoPP::AES;
33:
34: namespace po = boost::program_options;
35: namespace xp = boost::xpressive;
36:
37: namespace {
38:
39: enum ExitCode { kOK = 0, kErrorSyntax, kErrorSetup, kErrorFetch, kErrorWriteResult, kErrorFatal };
40: enum ExecutionMode { kModeFetch, kModeUsage, kModeSetup };
41: struct ProgramOption {
42: ProgramOption() : mode(kModeFetch) {}
43: ExecutionMode mode;
44: int out_count;
45: std::string out_format;
46: std::string ini_file_path;
47: };
48: struct ConnectOption {
49: ConnectOption()
50: :server_port("110") {}
51: std::string server_address;
52: std::string server_port;
53: std::string username;
54: std::string password;
55: };
56:
57: // Copy string with trailing null characters.
58: void CopyWholeString(std::string const& src, std::string * dst) {
59: std::string::const_iterator it_src = src.begin();
60: std::string::iterator it_dst = dst->begin();
61: while (it_src != src.end() && it_dst != dst->end()) { *it_dst++ = *it_src++; }
62: }
63:
64: // AES encrypt and decrypt.
65: template <bool IS_ENCRYPT>
66: std::string FilterString(std::string const& in_value, std::string const& iv) {
67: std::string const kEncryptKey = "mogmog";
68: std::string encrypt_key(AES::DEFAULT_KEYLENGTH, 0);
69: std::string initialization_vector(AES::BLOCKSIZE, 0);
70: CopyWholeString(kEncryptKey, &encrypt_key);
71: CopyWholeString(iv, &initialization_vector);
72:
73: boost::mpl::if_<boost::mpl::bool_<IS_ENCRYPT>,
74: CryptoPP::CTR_Mode<AES>::Encryption,
75: CryptoPP::CTR_Mode<AES>::Decryption>::type processor;
76: processor.SetKeyWithIV(reinterpret_cast<byte const*>(
77: encrypt_key.c_str()), encrypt_key.size(), reinterpret_cast<byte const*>(initialization_vector.c_str()));
78:
79: std::string out_value;
80: CryptoPP::StreamTransformationFilter stf(processor, new CryptoPP::StringSink(out_value));
81: stf.Put(reinterpret_cast<byte const*>(in_value.c_str()), in_value.size());
82: stf.MessageEnd();
83: return out_value;
84: }
85:
86: // Parse command line.
87: void ParseCommandLine(int argc, char * argv[], ProgramOption * option) {
88: po::options_description opt("Usage");
89: opt.add_options()
90: ("help", "Print this message.")
91: ("count", po::value<int>()->default_value(5), "Number of headers to get.")
92: ("format", po::value<std::string>()->default_value("[%m/%d %H:%M] %N <%A>%n %J"),
93: "Output format.\n"
94: "%y Last two digits of the year number\n"
95: "%Y Year number\n"
96: "%m Month number\n"
97: "%d Day number in the month\n"
98: "%H Hour number (24 hour system)\n"
99: "%M Minute number\n"
100: "%S Second number\n"
101: "%J Mail subject\n"
102: "%N Mail from-name\n"
103: "%A Mail from-address\n"
104: "%n New line\n"
105: "%% Character %")
106: ("ini-file", po::value<std::string>()->default_value("mogmail.ini"), "Path to the initial file.")
107: ("setup", "Remake the initial file.");
108: po::variables_map vm;
109: po::store(po::parse_command_line(argc, argv, opt), vm);
110: po::notify(vm);
111:
112: if (vm.count("help")) {
113: std::cout << opt << std::endl;
114: option->mode = kModeUsage;
115: return;
116: }
117: if (vm.count("setup")) { option->mode = kModeSetup; }
118: option->ini_file_path = vm["ini-file"].as<std::string>();
119: option->out_count = vm["count"].as<int>();
120: if (option->out_count <= 0) {
121: throw std::runtime_error("Count should be over 0.");
122: }
123: option->out_format = vm["format"].as<std::string>();
124: }
125:
126: // Read input from std::cin.
127: bool GetLine(std::string const& prompt, std::string * line) {
128: std::cout << prompt;
129: std::getline(std::cin, *line);
130: return !line->empty();
131: }
132:
133: // Save configuration.
134: void SaveConnectOption(std::string const& path, ConnectOption const& opt) {
135: std::ofstream ini_file(path);
136: if (!ini_file) { throw std::runtime_error("Failed to save ini-file."); }
137:
138: ini_file << "[POP3]\n";
139: ini_file << "SERVER=" << opt.server_address << "\n";
140: ini_file << "PORT=" << opt.server_port << "\n";
141: ini_file << "USERNAME=" << opt.username << "\n";
142: ini_file << "PASSWORD=" << mog::util::Base64::Encode(
143: FilterString<true>(opt.password, opt.username)) << "\n";
144: }
145:
146: // Load configuration.
147: void LoadConnectOption(std::string const& path, ConnectOption *opt) {
148: std::ifstream ini_file(path);
149: if (!ini_file) {
150: throw std::runtime_error("Failed to open " + path +".\n" +
151: "Execute '--setup' first.\n");
152: }
153:
154: std::string line;
155: std::string password_tmp;
156: xp::smatch match;
157: while (std::getline(ini_file, line)) {
158: if (xp::regex_match(line, match,
159: xp::sregex(xp::bos >> (xp::s1=+~(xp::set='=')) >> "=" >> (xp::s2=*xp::_)))) {
160: if ("SERVER" == match[1]) {
161: opt->server_address = match[2];
162: } else if ("PORT" == match[1]) {
163: opt->server_port = match[2];
164: } else if ("USERNAME" == match[1]) {
165: opt->username = match[2];
166: } else if ("PASSWORD" == match[1]) {
167: password_tmp = mog::util::Base64::Decode(match[2]);
168: }
169: }
170: }
171: opt->password = FilterString<false>(password_tmp, opt->username);
172: }
173:
174: // Interactive configuration setup.
175: void SetupConfig(std::string const& path) {
176: ConnectOption conn_opt;
177: std::cout << boost::format("*** SETUP MODE ***") << std::endl;
178: std::string line;
179: for (;;) { if (GetLine("POP3 Server Address?: ", &conn_opt.server_address)) { break; } }
180: if (GetLine("POP3 Server Port?[110]: ", &line)) { conn_opt.server_port = line; }
181: for (;;) { if (GetLine("Username: ", &conn_opt.username)) { break; } }
182: for (;;) { if (GetLine("Password: ", &conn_opt.password)) { break; } }
183:
184: // Test connection.
185: std::cout << "Testing connection..." << std::endl;
186: boost::scoped_ptr<mog::mail::POP3Client> pop_client;
187: pop_client.reset(new mog::mail::POP3Client(
188: conn_opt.server_address, conn_opt.server_port, conn_opt.username, conn_opt.password));
189: pop_client.reset();
190: std::cout << "OK." << std::endl;
191:
192: // Save configuration.
193: SaveConnectOption(path, conn_opt);
194: std::cout << "Saved configuration to " << path << "." << std::endl;
195: }
196:
197: // Format result.
198: std::string FormatResult(mog::mail::MailHeader const& mh, std::string const& format) {
199: std::string out_value;
200: tm local_time = boost::posix_time::to_tm(boost::date_time::c_local_adjustor<ptime>::utc_to_local(mh.date));
201:
202: bool flag_escaped = false;
203: for (std::string::const_iterator it = format.begin(); it != format.end(); ++it) {
204: if (flag_escaped) {
205: switch (*it) {
206: case 'y': out_value.append((boost::format("%02d") % (local_time.tm_year % 100)).str()); break;
207: case 'Y': out_value.append((boost::format("%04d") % (local_time.tm_year + 1900)).str()); break;
208: case 'm': out_value.append((boost::format("%02d") % (local_time.tm_mon + 1)).str()); break;
209: case 'd': out_value.append((boost::format("%02d") % local_time.tm_mday).str()); break;
210: case 'H': out_value.append((boost::format("%02d") % local_time.tm_hour).str()); break;
211: case 'M': out_value.append((boost::format("%02d") % local_time.tm_min).str()); break;
212: case 'S': out_value.append((boost::format("%02d") % local_time.tm_sec).str()); break;
213: case 'J': out_value.append(mh.subject); break;
214: case 'N': out_value.append(mh.from_name); break;
215: case 'A': out_value.append(mh.from_address); break;
216: case 'n': out_value.push_back('\n'); break;
217: case '%': out_value.push_back('%'); break;
218: default: throw std::runtime_error("Invalid output format: " + format);
219: }
220: flag_escaped = false;
221: } else if ('%' == *it) {
222: flag_escaped = true;
223: } else {
224: out_value.push_back(*it);
225: }
226: }
227: return out_value;
228: }
229:
230: } // namespace
231:
232: ////////////////////////////////////////////////////////////////////////////////
233: // main
234: int main(int argc, char * argv[]) {
235: ExitCode exit_code = kOK;
236: try {
237: ProgramOption option;
238:
239: // Parse command line options.
240: try {
241: ParseCommandLine(argc, argv, &option);
242: } catch(std::exception const& e) {
243: std::cout << "Error: " << e.what() << std::endl;
244: std::cout << "Check '--help' option.";
245: return kErrorSyntax;
246: }
247:
248: // Check mode.
249: switch (option.mode) {
250: case kModeUsage:
251: return kOK;
252: case kModeSetup:
253: // Save configuration file.
254: try {
255: SetupConfig(option.ini_file_path);
256: } catch(std::exception const& e) {
257: std::cout << "Error: " << e.what() << std::endl;
258: return kErrorSetup;
259: }
260: return kOK;
261: case kModeFetch:
262: break;
263: default:
264: assert(!"Unexpected mode.");
265: }
266:
267: int num_mails = 0;
268: std::vector<mog::mail::MailHeader> mail_header;
269: try {
270: // Load parameters.
271: ConnectOption conn_opt;
272: LoadConnectOption(option.ini_file_path, &conn_opt);
273:
274: // Connect to the POP3 server.
275: boost::scoped_ptr<mog::mail::POP3Client> pop_client;
276: pop_client.reset(new mog::mail::POP3Client(
277: conn_opt.server_address, conn_opt.server_port, conn_opt.username, conn_opt.password));
278: // Get mail count.
279: num_mails = pop_client->num_mails();
280: // Get headers.
281: for (int i = num_mails; i > num_mails - option.out_count && i > 0; --i) {
282: mail_header.push_back(pop_client->GetHeader(i));
283: }
284: // Disconnect from the server.
285: pop_client.reset();
286: } catch(std::exception const& e) {
287: std::cout << std::string("Error: ") + e.what();
288: exit_code = kErrorFetch;
289: }
290: // Format result.
291: try {
292: for (int i = 0; i < option.out_count; ++i) {
293: if (i < num_mails) {
294: std::cout << FormatResult(mail_header[i], option.out_format) << std::endl;
295: }
296: }
297: } catch(std::exception const& e) {
298: std::cout << std::string("Error: ") + e.what();
299: exit_code = kErrorWriteResult;
300: }
301: } catch(...) {
302: std::cout << "FATAL EXCEPTION!\n";
303: return kErrorFatal;
304: }
305: // system("pause");
306: return exit_code;
307: }