/* * Copyright 2007-2016, Haiku, Inc. All rights reserved. * Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved. * Copyright 2011, Clemens Zeidler * Distributed under the terms of the MIT License. */ #include "MailDaemonApplication.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "MailDaemon" static const uint32 kMsgStartAutoCheck = 'stAC'; static const uint32 kMsgAutoCheck = 'moto'; static const bigtime_t kStartAutoCheckDelay = 30000000; // Wait 30 seconds before the first auto check - this usually lets the // boot process settle down, and give the network a chance to come up. struct send_mails_info { send_mails_info() { bytes = 0; } BMessage files; off_t bytes; }; class InboundMessenger : public BMessenger { public: InboundMessenger(BInboundMailProtocol* protocol) : BMessenger(protocol) { } status_t MarkAsRead(const entry_ref& ref, read_flags flag) { BMessage message(kMsgMarkMessageAsRead); message.AddRef("ref", &ref); message.AddInt32("read", flag); return SendMessage(&message); } status_t SynchronizeMessages() { BMessage message(kMsgSyncMessages); return SendMessage(&message); } }; // #pragma mark - static void makeIndices() { const char* stringIndices[] = { B_MAIL_ATTR_CC, B_MAIL_ATTR_FROM, B_MAIL_ATTR_NAME, B_MAIL_ATTR_PRIORITY, B_MAIL_ATTR_REPLY, B_MAIL_ATTR_STATUS, B_MAIL_ATTR_SUBJECT, B_MAIL_ATTR_TO, B_MAIL_ATTR_THREAD, B_MAIL_ATTR_ACCOUNT, NULL }; // add mail indices for all devices capable of querying int32 cookie = 0; dev_t device; while ((device = next_dev(&cookie)) >= B_OK) { fs_info info; if (fs_stat_dev(device, &info) < 0 || (info.flags & B_FS_HAS_QUERY) == 0) continue; for (int32 i = 0; stringIndices[i]; i++) fs_create_index(device, stringIndices[i], B_STRING_TYPE, 0); fs_create_index(device, "MAIL:draft", B_INT32_TYPE, 0); fs_create_index(device, B_MAIL_ATTR_WHEN, B_INT32_TYPE, 0); fs_create_index(device, B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0); fs_create_index(device, B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0); fs_create_index(device, B_MAIL_ATTR_READ, B_INT32_TYPE, 0); } } static void addAttribute(BMessage& msg, const char* name, const char* publicName, int32 type = B_STRING_TYPE, bool viewable = true, bool editable = false, int32 width = 200) { msg.AddString("attr:name", name); msg.AddString("attr:public_name", publicName); msg.AddInt32("attr:type", type); msg.AddBool("attr:viewable", viewable); msg.AddBool("attr:editable", editable); msg.AddInt32("attr:width", width); msg.AddInt32("attr:alignment", B_ALIGN_LEFT); } // #pragma mark - account_protocols::account_protocols() : inboundImage(-1), inboundProtocol(NULL), outboundImage(-1), outboundProtocol(NULL) { } // #pragma mark - MailDaemonApplication::MailDaemonApplication() : BServer(B_MAIL_DAEMON_SIGNATURE, true, NULL), fAutoCheckRunner(NULL) { fErrorLogWindow = new ErrorLogWindow(BRect(200, 200, 500, 250), B_TRANSLATE("Mail daemon status log"), B_TITLED_WINDOW); // install MimeTypes, attributes, indices, and the // system beep add startup MakeMimeTypes(); makeIndices(); add_system_beep_event("New E-mail"); } MailDaemonApplication::~MailDaemonApplication() { delete fAutoCheckRunner; for (int32 i = 0; i < fQueries.CountItems(); i++) delete fQueries.ItemAt(i); while (!fAccounts.empty()) { _RemoveAccount(fAccounts.begin()->second); fAccounts.erase(fAccounts.begin()); } delete fLEDAnimation; delete fNotification; } void MailDaemonApplication::ReadyToRun() { InstallDeskbarIcon(); _InitAccounts(); // Start auto mail check with a delay BMessage startAutoCheck(kMsgStartAutoCheck); BMessageRunner::StartSending(this, &startAutoCheck, kStartAutoCheckDelay, 1); _InitNewMessagesCount(); fCentralBeep = false; fNotification = new BNotification(B_INFORMATION_NOTIFICATION); fNotification->SetGroup(B_TRANSLATE("Mail status")); fNotification->SetMessageID("daemon_status"); BPath path; if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { path.Append("Mail/New E-mail"); entry_ref ref; if (get_ref_for_path(path.Path(), &ref) == B_OK) { fNotification->SetOnClickApp("application/x-vnd.Be-TRAK"); fNotification->AddOnClickRef(&ref); } } _UpdateNewMessagesNotification(); app_info info; be_roster->GetAppInfo(B_MAIL_DAEMON_SIGNATURE, &info); BBitmap icon(BRect(0, 0, 32, 32), B_RGBA32); BNode node(&info.ref); BIconUtils::GetVectorIcon(&node, "BEOS:ICON", &icon); fNotification->SetIcon(&icon); fLEDAnimation = new LEDAnimation(); SetPulseRate(1000000); } void MailDaemonApplication::RefsReceived(BMessage* message) { entry_ref ref; for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { BNode node(&ref); if (node.InitCheck() != B_OK) continue; int32 account; if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &account, sizeof(account)) < 0) continue; BInboundMailProtocol* protocol = _InboundProtocol(account); if (protocol == NULL) continue; BMessenger target; BMessenger* replyTo = ⌖ if (message->FindMessenger("target", &target) != B_OK) replyTo = NULL; protocol->FetchBody(ref, replyTo); } } void MailDaemonApplication::MessageReceived(BMessage* msg) { switch (msg->what) { case kMsgStartAutoCheck: _UpdateAutoCheckRunner(); break; case kMsgAutoCheck: // TODO: check whether internet is up and running! // supposed to fall through case kMsgCheckAndSend: // check & send messages msg->what = kMsgSendMessages; PostMessage(msg); // supposed to fall trough case kMsgCheckMessage: // check messages GetNewMessages(msg); break; case kMsgSendMessages: // send messages SendPendingMessages(msg); break; case kMsgSettingsUpdated: fSettingsFile.Reload(); _UpdateAutoCheckRunner(); break; case kMsgAccountsChanged: _ReloadAccounts(msg); break; case kMsgMarkMessageAsRead: { int32 account = msg->FindInt32("account"); entry_ref ref; if (msg->FindRef("ref", &ref) != B_OK) break; read_flags read = (read_flags)msg->FindInt32("read"); BInboundMailProtocol* protocol = _InboundProtocol(account); if (protocol != NULL) InboundMessenger(protocol).MarkAsRead(ref, read); break; } case kMsgFetchBody: RefsReceived(msg); break; case 'stwg': // Status window gone { BMessage reply('mnuc'); reply.AddInt32("num_new_messages", fNewMessages); while ((msg = fFetchDoneRespondents.RemoveItemAt(0))) { msg->SendReply(&reply); delete msg; } if (fAlertString != B_EMPTY_STRING) { fAlertString.Truncate(fAlertString.Length() - 1); BAlert* alert = new BAlert(B_TRANSLATE("New Messages"), fAlertString.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL); alert->SetFeel(B_NORMAL_WINDOW_FEEL); alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); alert->Go(NULL); fAlertString = B_EMPTY_STRING; } if (fCentralBeep) { system_beep("New E-mail"); fCentralBeep = false; } break; } case 'mcbp': if (fNewMessages > 0) fCentralBeep = true; break; case kMsgCountNewMessages: // Number of new messages { BMessage reply('mnuc'); // Mail New message Count if (msg->FindBool("wait_for_fetch_done")) { fFetchDoneRespondents.AddItem(DetachCurrentMessage()); break; } reply.AddInt32("num_new_messages", fNewMessages); msg->SendReply(&reply); break; } case 'mblk': // Mail Blink if (fNewMessages > 0) fLEDAnimation->Start(); break; case 'enda': // End Auto Check delete fAutoCheckRunner; fAutoCheckRunner = NULL; break; case 'numg': { static BStringFormat format(B_TRANSLATE("{0, plural, " "one{# new message} other{# new messages}} for %name\n")); int32 numMessages = msg->FindInt32("num_messages"); fAlertString.Truncate(0); format.Format(fAlertString, numMessages); fAlertString.ReplaceFirst("%name", msg->FindString("name")); break; } case B_QUERY_UPDATE: { int32 previousCount = fNewMessages; int32 opcode; msg->FindInt32("opcode", &opcode); switch (opcode) { case B_ENTRY_CREATED: case B_ENTRY_REMOVED: { entry_ref ref; msg->FindInt32("device", &ref.device); msg->FindInt64("directory", &ref.directory); BEntry entry(&ref); if (!_IsEntryInTrash(entry)) { if (opcode == B_ENTRY_CREATED) fNewMessages++; else fNewMessages--; } break; } default: return; } _UpdateNewMessagesNotification(); if (fSettingsFile.ShowStatusWindow() != B_MAIL_SHOW_STATUS_WINDOW_NEVER && previousCount < fNewMessages) { fNotification->Send(); } break; } default: BApplication::MessageReceived(msg); break; } } void MailDaemonApplication::Pulse() { bigtime_t idle = idle_time(); if (fLEDAnimation->IsRunning() && idle < 100000) fLEDAnimation->Stop(); } bool MailDaemonApplication::QuitRequested() { RemoveDeskbarIcon(); return true; } void MailDaemonApplication::InstallDeskbarIcon() { BDeskbar deskbar; if (!deskbar.HasItem("mail_daemon")) { BRoster roster; entry_ref ref; status_t status = roster.FindApp(B_MAIL_DAEMON_SIGNATURE, &ref); if (status < B_OK) { fprintf(stderr, "Can't find application to tell deskbar: %s\n", strerror(status)); return; } status = deskbar.AddItem(&ref); if (status < B_OK) { fprintf(stderr, "Can't add deskbar replicant: %s\n", strerror(status)); return; } } } void MailDaemonApplication::RemoveDeskbarIcon() { BDeskbar deskbar; if (deskbar.HasItem("mail_daemon")) deskbar.RemoveItem("mail_daemon"); } void MailDaemonApplication::GetNewMessages(BMessage* msg) { int32 account = -1; if (msg->FindInt32("account", &account) == B_OK && account >= 0) { // Check the single requested account BInboundMailProtocol* protocol = _InboundProtocol(account); if (protocol != NULL) InboundMessenger(protocol).SynchronizeMessages(); return; } // Check all accounts AccountMap::const_iterator iterator = fAccounts.begin(); for (; iterator != fAccounts.end(); iterator++) { BInboundMailProtocol* protocol = iterator->second.inboundProtocol; if (protocol != NULL) InboundMessenger(protocol).SynchronizeMessages(); } } void MailDaemonApplication::SendPendingMessages(BMessage* msg) { BVolumeRoster roster; BVolume volume; std::map messages; int32 account = msg->GetInt32("account", -1); if (!msg->HasString("message_path")) { while (roster.GetNextVolume(&volume) == B_OK) { BQuery query; query.SetVolume(&volume); query.PushAttr(B_MAIL_ATTR_FLAGS); query.PushInt32(B_MAIL_PENDING); query.PushOp(B_EQ); query.PushAttr(B_MAIL_ATTR_FLAGS); query.PushInt32(B_MAIL_PENDING | B_MAIL_SAVE); query.PushOp(B_EQ); if (account >= 0) { query.PushAttr(B_MAIL_ATTR_ACCOUNT_ID); query.PushInt32(account); query.PushOp(B_EQ); query.PushOp(B_AND); } query.PushOp(B_OR); query.Fetch(); BEntry entry; while (query.GetNextEntry(&entry) == B_OK) { if (_IsEntryInTrash(entry)) continue; BNode node; while (node.SetTo(&entry) == B_BUSY) snooze(1000); if (!_IsPending(node)) continue; if (node.ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &account, sizeof(int32)) < 0) account = -1; _AddMessage(messages[account], entry, node); } } } else { // Send the requested message only const char* path; if (msg->FindString("message_path", &path) != B_OK) return; BEntry entry(path); _AddMessage(messages[account], entry, BNode(&entry)); } std::map::iterator iterator = messages.begin(); for (; iterator != messages.end(); iterator++) { BOutboundMailProtocol* protocol = _OutboundProtocol(iterator->first); if (protocol == NULL) continue; send_mails_info& info = iterator->second; if (info.bytes == 0) continue; protocol->SendMessages(info.files, info.bytes); } } void MailDaemonApplication::MakeMimeTypes(bool remakeMIMETypes) { // Add MIME database entries for the e-mail file types we handle. Either // do a full rebuild from nothing, or just add on the new attributes that // we support which the regular BeOS mail daemon didn't have. const uint8 kNTypes = 2; const char* types[kNTypes] = {"text/x-email", "text/x-partial-email"}; for (size_t i = 0; i < kNTypes; i++) { BMessage info; BMimeType mime(types[i]); if (mime.InitCheck() != B_OK) { fputs("could not init mime type.\n", stderr); return; } if (!mime.IsInstalled() || remakeMIMETypes) { // install the full mime type mime.Delete(); mime.Install(); // Set up the list of e-mail related attributes that Tracker will // let you display in columns for e-mail messages. addAttribute(info, B_MAIL_ATTR_NAME, "Name"); addAttribute(info, B_MAIL_ATTR_SUBJECT, "Subject"); addAttribute(info, B_MAIL_ATTR_TO, "To"); addAttribute(info, B_MAIL_ATTR_CC, "Cc"); addAttribute(info, B_MAIL_ATTR_FROM, "From"); addAttribute(info, B_MAIL_ATTR_REPLY, "Reply To"); addAttribute(info, B_MAIL_ATTR_STATUS, "Status"); addAttribute(info, B_MAIL_ATTR_PRIORITY, "Priority", B_STRING_TYPE, true, true, 40); addAttribute(info, B_MAIL_ATTR_WHEN, "When", B_TIME_TYPE, true, false, 150); addAttribute(info, B_MAIL_ATTR_THREAD, "Thread"); addAttribute(info, B_MAIL_ATTR_ACCOUNT, "Account", B_STRING_TYPE, true, false, 100); addAttribute(info, B_MAIL_ATTR_READ, "Read", B_INT32_TYPE, true, false, 70); mime.SetAttrInfo(&info); if (i == 0) { mime.SetShortDescription("E-mail"); mime.SetLongDescription("Electronic Mail Message"); mime.SetPreferredApp("application/x-vnd.Be-MAIL"); } else { mime.SetShortDescription("Partial E-mail"); mime.SetLongDescription("A Partially Downloaded E-mail"); mime.SetPreferredApp("application/x-vnd.Be-MAIL"); } } } } void MailDaemonApplication::_InitAccounts() { BMailAccounts accounts; for (int i = 0; i < accounts.CountAccounts(); i++) _InitAccount(*accounts.AccountAt(i)); } void MailDaemonApplication::_InitAccount(BMailAccountSettings& settings) { account_protocols account; // inbound if (settings.IsInboundEnabled()) { account.inboundProtocol = _CreateInboundProtocol(settings, account.inboundImage); } if (account.inboundProtocol != NULL) { DefaultNotifier* notifier = new DefaultNotifier(settings.Name(), true, fErrorLogWindow, fSettingsFile.ShowStatusWindow()); account.inboundProtocol->SetMailNotifier(notifier); account.inboundProtocol->Run(); } // outbound if (settings.IsOutboundEnabled()) { account.outboundProtocol = _CreateOutboundProtocol(settings, account.outboundImage); } if (account.outboundProtocol != NULL) { DefaultNotifier* notifier = new DefaultNotifier(settings.Name(), false, fErrorLogWindow, fSettingsFile.ShowStatusWindow()); account.outboundProtocol->SetMailNotifier(notifier); account.outboundProtocol->Run(); } printf("account name %s, id %i, in %p, out %p\n", settings.Name(), (int)settings.AccountID(), account.inboundProtocol, account.outboundProtocol); if (account.inboundProtocol != NULL || account.outboundProtocol != NULL) fAccounts[settings.AccountID()] = account; } void MailDaemonApplication::_ReloadAccounts(BMessage* message) { type_code typeFound; int32 countFound; message->GetInfo("account", &typeFound, &countFound); if (typeFound != B_INT32_TYPE) return; // reload accounts BMailAccounts accounts; for (int i = 0; i < countFound; i++) { int32 account = message->FindInt32("account", i); AccountMap::iterator found = fAccounts.find(account); if (found != fAccounts.end()) { _RemoveAccount(found->second); fAccounts.erase(found); } BMailAccountSettings* settings = accounts.AccountByID(account); if (settings != NULL) _InitAccount(*settings); } } void MailDaemonApplication::_RemoveAccount(const account_protocols& account) { if (account.inboundProtocol != NULL) { account.inboundProtocol->Lock(); account.inboundProtocol->Quit(); unload_add_on(account.inboundImage); } if (account.outboundProtocol != NULL) { account.outboundProtocol->Lock(); account.outboundProtocol->Quit(); unload_add_on(account.outboundImage); } } BInboundMailProtocol* MailDaemonApplication::_CreateInboundProtocol(BMailAccountSettings& settings, image_id& image) { const entry_ref& entry = settings.InboundAddOnRef(); BInboundMailProtocol* (*instantiateProtocol)(BMailAccountSettings*); BPath path(&entry); image = load_add_on(path.Path()); if (image < 0) return NULL; if (get_image_symbol(image, "instantiate_inbound_protocol", B_SYMBOL_TYPE_TEXT, (void**)&instantiateProtocol) != B_OK) { unload_add_on(image); image = -1; return NULL; } return instantiateProtocol(&settings); } BOutboundMailProtocol* MailDaemonApplication::_CreateOutboundProtocol(BMailAccountSettings& settings, image_id& image) { const entry_ref& entry = settings.OutboundAddOnRef(); BOutboundMailProtocol* (*instantiateProtocol)(BMailAccountSettings*); BPath path(&entry); image = load_add_on(path.Path()); if (image < 0) return NULL; if (get_image_symbol(image, "instantiate_outbound_protocol", B_SYMBOL_TYPE_TEXT, (void**)&instantiateProtocol) != B_OK) { unload_add_on(image); image = -1; return NULL; } return instantiateProtocol(&settings); } BInboundMailProtocol* MailDaemonApplication::_InboundProtocol(int32 account) { AccountMap::iterator found = fAccounts.find(account); if (found == fAccounts.end()) return NULL; return found->second.inboundProtocol; } BOutboundMailProtocol* MailDaemonApplication::_OutboundProtocol(int32 account) { if (account < 0) account = BMailSettings().DefaultOutboundAccount(); AccountMap::iterator found = fAccounts.find(account); if (found == fAccounts.end()) return NULL; return found->second.outboundProtocol; } void MailDaemonApplication::_InitNewMessagesCount() { BVolume volume; BVolumeRoster roster; fNewMessages = 0; while (roster.GetNextVolume(&volume) == B_OK) { BQuery* query = new BQuery; query->SetTarget(this); query->SetVolume(&volume); query->PushAttr(B_MAIL_ATTR_STATUS); query->PushString("New"); query->PushOp(B_EQ); query->PushAttr("BEOS:TYPE"); query->PushString("text/x-email"); query->PushOp(B_EQ); query->PushAttr("BEOS:TYPE"); query->PushString("text/x-partial-email"); query->PushOp(B_EQ); query->PushOp(B_OR); query->PushOp(B_AND); query->Fetch(); BEntry entry; while (query->GetNextEntry(&entry) == B_OK) { if (!_IsEntryInTrash(entry)) fNewMessages++; } fQueries.AddItem(query); } } void MailDaemonApplication::_UpdateNewMessagesNotification() { BString title; if (fNewMessages > 0) { BStringFormat format(B_TRANSLATE( "{0, plural, one{One new message} other{# new messages}}")); format.Format(title, fNewMessages); } else title = B_TRANSLATE("No new messages"); fNotification->SetTitle(title.String()); } void MailDaemonApplication::_UpdateAutoCheckRunner() { bigtime_t interval = fSettingsFile.AutoCheckInterval(); if (interval > 0) { if (fAutoCheckRunner != NULL) { fAutoCheckRunner->SetInterval(interval); fAutoCheckRunner->SetCount(-1); } else { BMessage update(kMsgAutoCheck); fAutoCheckRunner = new BMessageRunner(be_app_messenger, &update, interval); // Send one right away -- the message runner will wait until the // first interval has passed before sending a message PostMessage(&update); } } else { delete fAutoCheckRunner; fAutoCheckRunner = NULL; } } void MailDaemonApplication::_AddMessage(send_mails_info& info, const BEntry& entry, const BNode& node) { entry_ref ref; off_t size; if (node.GetSize(&size) == B_OK && entry.GetRef(&ref) == B_OK) { info.files.AddRef("ref", &ref); info.bytes += size; } } /*! Work-around for a broken index that contains out-of-date information. */ /*static*/ bool MailDaemonApplication::_IsPending(BNode& node) { int32 flags; if (node.ReadAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags, sizeof(int32)) != (ssize_t)sizeof(int32)) return false; return (flags & B_MAIL_PENDING) != 0; } /*static*/ bool MailDaemonApplication::_IsEntryInTrash(BEntry& entry) { entry_ref ref; entry.GetRef(&ref); BVolume volume(ref.device); BPath path; if (volume.InitCheck() != B_OK || find_directory(B_TRASH_DIRECTORY, &path, false, &volume) != B_OK) return false; BDirectory trash(path.Path()); return trash.Contains(&entry); }