00001 #ifndef SMTP_CLIENT_IMPLEMENTATION_FILE
00002 #define SMTP_CLIENT_IMPLEMENTATION_FILE
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #include "smtp_client.h"
00019
00020 #include <basis/function.h>
00021 #include <basis/istring.h>
00022 #include <basis/log_base.h>
00023 #include <basis/mutex.h>
00024 #include <basis/portable.h>
00025 #include <basis/string_array.h>
00026 #include <data_struct/static_memory_gremlin.h>
00027 #include <mechanisms/time_stamp.h>
00028 #include <opsystem/byte_filer.h>
00029 #include <loggers/console_logger.h>
00030 #include <opsystem/path_configuration.h>
00031 #include <opsystem/redirecter.h>
00032 #include <opsystem/registry_config.h>
00033
00034 #ifdef __WIN32__
00035 #include <atlenc.h>
00036 #endif
00037
00038 #ifndef OMIT_PROGRAM_WIDE_LOGGER
00039 #define LOG(s) CLASS_EMERGENCY_LOG((_logging? *_logging \
00040 : program_wide_logger()), s)
00041 #else
00042 #define LOG(s) CLASS_EMERGENCY_LOG(console_logger(), s)
00043 #endif
00044
00045 #define CHECK_STILL_RUNNING(to_return) \
00046 if (!msmtp.running()) { \
00047 \
00048 LOG("the msmtp application already exited."); \
00049 if (!okay_exit_value(msmtp.exit_value())) return to_return; \
00050 else return OKAY; \
00051 }
00052
00053 const int MAXIMUM_WRITE_ITERATIONS = 40;
00054
00055
00056
00057 const int MAXIMUM_PAUSE_FOR_SENDING = 30 * SECOND_ms;
00058
00059
00060
00061 const int DEFAULT_TIMEOUT = 0;
00062
00063
00064 smtp_client::smtp_client()
00065 : _logging(NIL),
00066 _sess_server(new istring),
00067 _sess_port(SC_SMTP_DEFAULT_PORT),
00068 _sess_user(new istring),
00069 _sess_password(new istring),
00070 _msg_sender(new istring),
00071 _msg_subject(new istring),
00072 _msg_body(new istring),
00073 _msg_attachments(new string_array),
00074 _msg_recipients(new string_array),
00075 _msg_carbons(new string_array),
00076 _timeout(DEFAULT_TIMEOUT)
00077 {}
00078
00079 smtp_client::smtp_client(log_base &logging)
00080 : _logging(&logging),
00081 _sess_server(new istring),
00082 _sess_port(SC_SMTP_DEFAULT_PORT),
00083 _sess_user(new istring),
00084 _sess_password(new istring),
00085 _msg_sender(new istring),
00086 _msg_subject(new istring),
00087 _msg_body(new istring),
00088 _msg_attachments(new string_array),
00089 _msg_recipients(new string_array),
00090 _msg_carbons(new string_array),
00091 _timeout(DEFAULT_TIMEOUT)
00092 {}
00093
00094 smtp_client::~smtp_client()
00095 {
00096 WHACK(_sess_server);
00097 WHACK(_sess_user);
00098 WHACK(_sess_password);
00099 WHACK(_msg_sender);
00100 WHACK(_msg_subject);
00101 WHACK(_msg_body);
00102 WHACK(_msg_attachments);
00103 WHACK(_msg_recipients);
00104 WHACK(_msg_carbons);
00105 }
00106
00107 const char *smtp_client::outcome_name(const outcome &to_name)
00108 { return communication_commons::outcome_name(to_name); }
00109
00110 void smtp_client::setup_session(const istring &server_name,
00111 int smtp_port, const istring &user_name, const istring &password)
00112 {
00113 FUNCDEF("setup_session");
00114 set_server(server_name);
00115 set_port(smtp_port);
00116 set_auth_user(user_name);
00117 set_auth_password(password);
00118
00119 #ifdef __WIN32__
00120
00121
00122
00123
00124
00125
00126 registry_configurator reg(registry_configurator::hkey_current_user,
00127 registry_configurator::RETURN_ONLY);
00128
00129 istring temp_home = reg.load("Software\\Microsoft\\Windows\\CurrentVersion"
00130 "\\Explorer\\User Shell Folders", "AppData", "");
00131
00132 if (!temp_home) {
00133
00134
00135
00136 temp_home = "C:/Documents and Settings/LocalService/Application Data";
00137 }
00138
00139
00140
00141 istring password_line("machine ");
00142 password_line += *_sess_server;
00143 password_line += " login ";
00144 password_line += *_sess_user;
00145
00146 istring line_to_seek = password_line;
00147
00148
00149
00150 password_line += " password ";
00151 password_line += *_sess_password;
00152 password_line += log_base::platform_ending();
00153 byte_filer netrc(temp_home + "/netrc.txt", "r");
00154 istring full_file;
00155
00156 while (!netrc.eof()) {
00157 const int MAX_LINE = 1000;
00158 istring line_read;
00159 int chars_read = netrc.getline(line_read, MAX_LINE);
00160 if (chars_read) {
00161 if (line_read.contains(line_to_seek)) continue;
00162
00163 full_file += line_read;
00164 }
00165 }
00166 netrc.close();
00167 netrc.open(temp_home + "/netrc.txt", "w");
00168 full_file += password_line;
00169
00170
00171 netrc.write(full_file);
00172
00173 netrc.close();
00174 #endif
00175 }
00176
00177 void smtp_client::set_server(const istring &server_name)
00178 { *_sess_server = server_name; }
00179
00180 void smtp_client::set_port(int smtp_port) { _sess_port = smtp_port; }
00181
00182 void smtp_client::set_auth_user(const istring &user_name)
00183 { *_sess_user = user_name; }
00184
00185 void smtp_client::set_auth_password(const istring &password)
00186 { *_sess_password = password; }
00187
00188 void smtp_client::setup_message(const istring &sender_email,
00189 const istring &subject, const istring &message_body,
00190 const istring &one_recipient)
00191 {
00192 set_sender(sender_email);
00193 set_subject(subject);
00194 set_body(message_body);
00195 clear_recipients();
00196 add_recipient(one_recipient);
00197 clear_carbons();
00198 }
00199
00200 void smtp_client::add_recipient(const istring &recipient)
00201 {
00202 if (!recipient) return;
00203 *_msg_recipients += recipient;
00204 }
00205
00206 bool smtp_client::remove_recipient(const istring &recipient)
00207 {
00208 if (!recipient) return false;
00209 bool found_one = false;
00210 for (int i = 0; i < _msg_recipients->length(); i++) {
00211
00212 if (_msg_recipients->get(i) == recipient) {
00213
00214 _msg_recipients->zap(i, i);
00215 i--;
00216 found_one = true;
00217 }
00218 }
00219 return found_one;
00220 }
00221
00222 void smtp_client::clear_recipients()
00223 {
00224 _msg_recipients->reset();
00225 }
00226
00227 void smtp_client::add_carbon(const istring &carbon)
00228 {
00229 if (!carbon) return;
00230 *_msg_carbons += carbon;
00231 }
00232
00233 bool smtp_client::remove_carbon(const istring &carbon)
00234 {
00235 if (!carbon) return false;
00236 bool found_one = false;
00237 for (int i = 0; i < _msg_carbons->length(); i++) {
00238
00239 if (_msg_carbons->get(i) == carbon) {
00240
00241 _msg_carbons->zap(i, i);
00242 i--;
00243 found_one = true;
00244 }
00245 }
00246 return found_one;
00247 }
00248
00249 void smtp_client::clear_carbons()
00250 {
00251 _msg_carbons->reset();
00252 }
00253
00254 void smtp_client::set_sender(const istring &sender_email)
00255 {
00256 *_msg_sender = sender_email;
00257 }
00258
00259 void smtp_client::set_subject(const istring &subject)
00260 {
00261 #ifdef __WIN32__
00262
00263
00264 if (GetExtendedChars(subject.s(), subject.length()) > 0)
00265 {
00266 int bufSize= QEncodeGetRequiredLength(subject.length(),
00267 ATL_MAX_ENC_CHARSET_LENGTH);
00268 istring encodedSubject;
00269 encodedSubject.pad(bufSize);
00270 QEncode((BYTE *)(subject.s()), subject.length(),
00271 encodedSubject.access(), &bufSize, "iso-8859-1");
00272 *_msg_subject = encodedSubject;
00273 }
00274 else
00275 {
00276 *_msg_subject = subject;
00277 }
00278 #else
00279 *_msg_subject = subject;
00280 #endif
00281 }
00282
00283 void smtp_client::set_body(const istring &message_body)
00284 {
00285 #ifdef __WIN32__
00286
00287
00288
00289 int bufSize= QPEncodeGetRequiredLength(message_body.length());
00290 istring encodedBody;
00291 encodedBody.pad(bufSize);
00292 QPEncode((BYTE *)(message_body.s()), message_body.length(),
00293 encodedBody.access(), &bufSize);
00294 *_msg_body = encodedBody;
00295 #else
00296 *_msg_body = message_body;
00297 #endif
00298 }
00299
00300 void smtp_client::set_timeout(int timeout)
00301 {
00302 _timeout = timeout;
00303 }
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331
00332 bool smtp_client::get_outputs(stdio_redirecter &msmtp, byte_array &feedback)
00333 {
00334 FUNCDEF("get_outputs");
00335 bool to_return = false;
00336 feedback.reset();
00337 outcome read_ret = msmtp.read(feedback);
00338 if (read_ret == stdio_redirecter::OKAY) {
00339 LOG(istring("out|") + istring(istring::UNTERMINATED,
00340 (char *)feedback.observe(), feedback.length()));
00341 to_return = true;
00342 }
00343 byte_array err_data;
00344 read_ret = msmtp.read_stderr(err_data);
00345 if (read_ret == stdio_redirecter::OKAY) {
00346 LOG(istring("err|") + istring(istring::UNTERMINATED,
00347 (char *)err_data.observe(), err_data.length()));
00348 feedback += err_data;
00349 to_return = true;
00350 }
00351 return to_return;
00352 }
00353
00354 bool smtp_client::validate_address(const istring &to_check)
00355 {
00356 istring name_portion;
00357 istring email_addr = prune_address(to_check, name_portion);
00358 int indy = email_addr.find('@');
00359 if (non_positive(indy)) return false;
00360
00361
00362 if (email_addr.length() - 1 == indy) return false;
00363
00364 int indy2 = email_addr.find('@', indy + 1);
00365 if (non_negative(indy2)) return false;
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378 return true;
00379
00380 }
00381
00382 istring smtp_client::prune_address(const istring &to_prune, istring &name)
00383 {
00384 name = "";
00385 istring pruned_sender = to_prune;
00386 int indy = pruned_sender.find("<");
00387 if (non_negative(indy)) {
00388 name = pruned_sender.substring(0, indy - 1);
00389 pruned_sender.zap(0, indy);
00390 }
00391 indy = pruned_sender.find(">");
00392 if (non_negative(indy)) {
00393 pruned_sender.zap(indy, pruned_sender.end());
00394 }
00395 return pruned_sender;
00396 }
00397
00398 bool smtp_client::okay_exit_value(int value) { return value == 0; }
00399
00400 #define CHECK_FOR_PASSWORD_PROMPT(bytes) { \
00401 istring text_gotten(istring::UNTERMINATED, (char *)bytes.observe(), \
00402 bytes.length()); \
00403 if (text_gotten.contains("password")) { \
00404 LOG("saw a password reference in the output; we may be missing " \
00405 "the password."); \
00406 msmtp.zap_program(); \
00407 return ACCESS_DENIED; \
00408 } \
00409 CHECK_STILL_RUNNING(BAD_INPUT); \
00410 }
00411
00412 outcome smtp_client::send_email()
00413 {
00414 FUNCDEF("send_email");
00415
00416 istring msmtp_path = path_configuration::application_directory() + "/msmtp";
00417 #ifdef __WIN32__
00418 msmtp_path += ".exe";
00419 #endif
00420
00421 istring text_name;
00422 istring pruned_sender = prune_address(*_msg_sender, text_name);
00423
00424
00425 isprintf smtp_parms("--protocol=smtp --host %s --port %d --from=%s",
00426 _sess_server->s(), _sess_port, pruned_sender.s());
00427
00428
00429 if (_timeout) {
00430
00431
00432 smtp_parms += isprintf(" --timeout=%d", _timeout / SECOND_ms);
00433 }
00434
00435
00436 bool authenticating = false;
00437 if (_sess_user->t()) {
00438 authenticating = true;
00439 smtp_parms += " --auth=on";
00440 smtp_parms += isprintf(" --user=%s", _sess_user->s());
00441 }
00442
00443
00444 smtp_parms += " -- ";
00445 for (int i = 0; i < _msg_recipients->length(); i++) {
00446 istring name;
00447 istring just_addr = prune_address(_msg_recipients->get(i), name);
00448 smtp_parms += just_addr;
00449 smtp_parms += " ";
00450 }
00451
00452 for (int i = 0; i < _msg_carbons->length(); i++) {
00453 istring name;
00454 istring just_addr = prune_address(_msg_carbons->get(i), name);
00455 smtp_parms += just_addr;
00456 smtp_parms += " ";
00457 }
00458
00459
00460
00461
00462 stdio_redirecter msmtp(msmtp_path, smtp_parms);
00463 if (msmtp.health() != stdio_redirecter::OKAY) {
00464
00465 LOG("failure to launch the msmtp application with redirected I/O.");
00466 return BAD_INPUT;
00467 }
00468 if (!msmtp.running()) {
00469
00470 LOG("the msmtp application has unexpectedly left the process list.");
00471 return BAD_INPUT;
00472 }
00473
00474 if (authenticating) {
00475
00476
00477
00478
00479
00480
00481 #ifndef __WIN32__
00482
00483 const int SOME_TIMEOUT = 30 * SECOND_ms;
00484
00485
00486 time_stamp done_waiting(SOME_TIMEOUT);
00487 byte_array received;
00488 bool gave_password = false;
00489 while (time_stamp() < done_waiting) {
00490 byte_array temp;
00491
00492 if (get_outputs(msmtp, temp)) {
00493 received += temp;
00494
00495 istring text_gotten(istring::UNTERMINATED, (char *)received.observe(),
00496 received.length());
00497 if (text_gotten.contains("password")) {
00498
00499
00500 int written;
00501 msmtp.write(*_sess_password + log_base::platform_ending(), written);
00502 gave_password = true;
00503
00504 break;
00505 }
00506 }
00507 portable::sleep_ms(40);
00508 }
00509 if (!gave_password) {
00510 LOG("never received password prompt for authenticated smtp.");
00511 return TIMED_OUT;
00512 }
00513 #endif
00514 }
00515
00516 CHECK_STILL_RUNNING(BAD_INPUT);
00517 byte_array junk;
00518 get_outputs(msmtp, junk);
00519 CHECK_FOR_PASSWORD_PROMPT(junk);
00520
00521 int len_written = 0;
00522
00523 #ifdef __WIN32__
00524
00525
00526 istring header_lines = "MIME-Version: 1.0";
00527 header_lines += log_base::platform_ending();
00528 header_lines += "Content-Type: text/plain; charset=\"iso-8859-1\"";
00529 header_lines += log_base::platform_ending();
00530 header_lines += "Content-Transfer-Encoding: quoted-printable";
00531 header_lines += log_base::platform_ending();
00532 msmtp.write(header_lines, len_written);
00533 #endif
00534
00535 istring subject_line = "Subject: ";
00536 subject_line += *_msg_subject;
00537 subject_line += log_base::platform_ending();
00538 msmtp.write(subject_line, len_written);
00539
00540
00541
00542
00543
00544
00545 istring from_address = "From: ";
00546 from_address += *_msg_sender;
00547 from_address += log_base::platform_ending();
00548 msmtp.write(from_address, len_written);
00549
00550
00551
00552
00553 for (int i = 0; i < _msg_recipients->length(); i++) {
00554 istring to_line = "To: ";
00555 to_line += _msg_recipients->get(i);
00556 to_line += log_base::platform_ending();
00557 msmtp.write(to_line, len_written);
00558
00559 }
00560
00561
00562 for (int i = 0; i < _msg_carbons->length(); i++) {
00563 istring to_line = "CC: ";
00564 to_line += _msg_carbons->get(i);
00565 to_line += log_base::platform_ending();
00566 msmtp.write(to_line, len_written);
00567
00568 }
00569
00570
00571 msmtp.write(log_base::platform_ending(), len_written);
00572
00573 get_outputs(msmtp, junk);
00574 CHECK_FOR_PASSWORD_PROMPT(junk);
00575
00576 int writing = _msg_body->length();
00577 int maximum_iters = MAXIMUM_WRITE_ITERATIONS;
00578
00579 while ( (writing > 0) && (maximum_iters-- > 0) ) {
00580 CHECK_STILL_RUNNING(BAD_INPUT);
00581 msmtp.write(*_msg_body, len_written);
00582 writing -= len_written;
00583
00584 if (writing)
00585 _msg_body->zap(0, len_written - 1);
00586 }
00587 msmtp.write(log_base::platform_ending(), len_written);
00588
00589
00590
00591
00592 get_outputs(msmtp, junk);
00593 CHECK_FOR_PASSWORD_PROMPT(junk);
00594
00595 msmtp.close_input();
00596 time_stamp when_to_leave(MAXIMUM_PAUSE_FOR_SENDING);
00597 while ( (time_stamp() < when_to_leave) && msmtp.running() ) {
00598 portable::sleep_ms(42);
00599 }
00600 if (time_stamp() > when_to_leave) return TIMED_OUT;
00601
00602 get_outputs(msmtp, junk);
00603
00604
00605
00606 if (!okay_exit_value(msmtp.exit_value())) return BAD_INPUT;
00607
00608 return OKAY;
00609 }
00610
00611
00612 #endif //SMTP_CLIENT_IMPLEMENTATION_FILE
00613