├── src ├── LogSink.cpp ├── LogSink.h ├── ClientException.h ├── MailBox.h ├── MailLink.h ├── Options.h ├── Log.h ├── MailLink.cpp ├── Log.cpp ├── MailBox.cpp ├── MailRecord.h ├── Client.h ├── Options.cpp ├── MailDatabase.h ├── Client.cpp ├── MailRecord.cpp ├── MailDatabase.cpp └── main.cpp ├── bin ├── objects.mk ├── sources.mk ├── src │ └── subdir.mk └── makefile ├── README.md └── LICENSE /src/LogSink.cpp: -------------------------------------------------------------------------------- 1 | #include "LogSink.h" 2 | 3 | LogSink::LogSink() 4 | { 5 | } 6 | 7 | LogSink::~LogSink() 8 | { 9 | } 10 | 11 | 12 | void LogSink::set(Log& log, int priority) 13 | { 14 | this->log = &log; 15 | this->priority = priority; 16 | } -------------------------------------------------------------------------------- /bin/objects.mk: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Automatically-generated file. Do not edit! 3 | ################################################################################ 4 | 5 | USER_OBJS := 6 | 7 | LIBS := -lc-client -lboost_filesystem -lboost_program_options -------------------------------------------------------------------------------- /src/LogSink.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGSINK_H_ 2 | #define LOGSINK_H_ 3 | 4 | #include "Log.h" 5 | 6 | class LogSink 7 | { 8 | public: 9 | LogSink(); 10 | void set(Log& log, int priority); 11 | virtual ~LogSink(); 12 | 13 | template 14 | LogSink& operator<<(S const& value) { 15 | (*this->log).begin_entry(this->priority); 16 | (*this->log) << value; 17 | 18 | return *this; 19 | } 20 | 21 | private: 22 | Log* log; 23 | int priority; 24 | }; 25 | 26 | #endif /*LOGSINK_H_*/ 27 | -------------------------------------------------------------------------------- /bin/sources.mk: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Automatically-generated file. Do not edit! 3 | ################################################################################ 4 | 5 | O_SRCS := 6 | CPP_SRCS := 7 | C_UPPER_SRCS := 8 | C_SRCS := 9 | S_SRCS := 10 | OBJ_SRCS := 11 | ASM_SRCS := 12 | CXX_SRCS := 13 | C++_SRCS := 14 | CC_SRCS := 15 | OBJS := 16 | C++_DEPS := 17 | C_DEPS := 18 | CC_DEPS := 19 | CPP_DEPS := 20 | EXECUTABLES := 21 | CXX_DEPS := 22 | C_UPPER_DEPS := 23 | 24 | # Every subdirectory with source files must be described here 25 | SUBDIRS := \ 26 | src \ 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gmail Carbon Copy 2 | ================= 3 | 4 | Gmailcc makes it possible to backup your Gmail account to a Maildir format usable by any standard IMAP-server. 5 | 6 | It detects mails with multiple labels and will save only one copy of each, using a link for very duplicate. This achieves maximum space savings while still allowing an IMAP-server to find the mail with every label. Combine with Webmail software like Roundcube for optimum results. 7 | 8 | Installation & Usage 9 | -------------------- 10 | Info on how to install and use it can be found [on the project site](http://code.crowdway.com/projects/show/gmailcc "Gmail Carbon Copy - Tutorial"). 11 | -------------------------------------------------------------------------------- /src/ClientException.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #ifndef CLIENTEXCEPTION_H_ 7 | #define CLIENTEXCEPTION_H_ 8 | 9 | #include 10 | 11 | class Client; 12 | 13 | class ClientException: public std::exception 14 | { 15 | public: 16 | ClientException(Client *client): client(client) {}; 17 | private: 18 | Client *client; 19 | }; 20 | 21 | class AuthClientException: public ClientException 22 | { 23 | public: 24 | AuthClientException(Client *client): ClientException(client) {}; 25 | }; 26 | 27 | class InvalidAuthClientException: public AuthClientException 28 | { 29 | public: 30 | InvalidAuthClientException(Client *client): AuthClientException(client) {}; 31 | }; 32 | 33 | class WebAuthClientException: public AuthClientException 34 | { 35 | public: 36 | WebAuthClientException(Client *client): AuthClientException(client) {}; 37 | }; 38 | 39 | #endif /*CLIENTEXCEPTION_H_*/ 40 | -------------------------------------------------------------------------------- /src/MailBox.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #ifndef MAILBOX_H_ 7 | #define MAILBOX_H_ 8 | 9 | #include 10 | #include 11 | 12 | class MailLink; 13 | class MailDatabase; 14 | 15 | using namespace std; 16 | 17 | class MailBox 18 | { 19 | public: 20 | static bool is_primary(const string); 21 | static bool is_inbox(const string); 22 | 23 | bool inbox; 24 | bool primary; 25 | unsigned long next_uid; 26 | unsigned long uid_validity; 27 | unsigned long messagecount; 28 | string name; 29 | vector mails; 30 | 31 | bool marked; // Marked for removal 32 | 33 | string get_path(); 34 | void mark(); 35 | void dirty(); 36 | int sweep(bool check_marked = false); 37 | 38 | MailBox(MailDatabase* maildb, string name); 39 | ~MailBox(); 40 | 41 | private: 42 | static const char* primaries[]; 43 | 44 | string path; 45 | bool dirtied; // Needs sweeping to get rid of removed links 46 | MailDatabase* maildb; 47 | }; 48 | 49 | #endif /*MAILBOX_H_*/ 50 | -------------------------------------------------------------------------------- /src/MailLink.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #ifndef MAILLINK_H_ 7 | #define MAILLINK_H_ 8 | 9 | #include 10 | 11 | class MailRecord; 12 | class MailBox; 13 | 14 | using namespace std; 15 | 16 | class MailLink 17 | { 18 | public: 19 | MailRecord* mailrecord; 20 | MailBox* mailbox; 21 | unsigned long uid; 22 | 23 | bool staled; // This links is stale. It should be removed. 24 | bool marked; // Set to true when this link is found on the 25 | // server. After everything is checked, all 26 | // non-touched links are regarded as "deleted" 27 | // and are removed. 28 | 29 | void stale(); 30 | void mark(); 31 | 32 | string get_base_path(); // Without INFO-part 33 | string get_path(); // With INFO-part 34 | void set_path(string path); 35 | 36 | MailLink(); 37 | MailLink(const string box, const unsigned long uid, const string path); 38 | virtual ~MailLink(); 39 | 40 | private: 41 | string path; 42 | }; 43 | 44 | #endif /*MAILLINK_H_*/ 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2010 David Verhasselt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Options.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #ifndef OPTIONS_H_ 7 | #define OPTIONS_H_ 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace bo = boost::program_options; 14 | 15 | /* 16 | * Options: 17 | * - use symlinks or hardlinks 18 | * - backup all folders or just selected folders 19 | * - do not back up trash 20 | * - do not back up spam 21 | * - write metadata for courier-imapd, dovecot, ... 22 | */ 23 | 24 | class Options 25 | { 26 | public: 27 | Options(int argc, char* argv[]); 28 | virtual ~Options(); 29 | 30 | void show_help(); 31 | 32 | bool incomplete(); 33 | bool get_help(); 34 | bool get_version(); 35 | int get_loglevel(); 36 | std::string get_logfile(); 37 | std::string get_maildir_path(); 38 | std::string get_username(); 39 | std::string get_password(); 40 | bool silent(); 41 | 42 | private: 43 | bo::variables_map* vm; 44 | bo::options_description* generic; 45 | bo::options_description* config; 46 | bo::options_description* cmdline_options; 47 | 48 | std::string expand_path(std::string path); 49 | bool load_config_file(std::string filename); 50 | }; 51 | 52 | #endif /*OPTIONS_H_*/ 53 | -------------------------------------------------------------------------------- /bin/src/subdir.mk: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Automatically-generated file. Do not edit! 3 | ################################################################################ 4 | 5 | # Add inputs and outputs from these tool invocations to the build variables 6 | CPP_SRCS += \ 7 | ../src/Client.cpp \ 8 | ../src/Log.cpp \ 9 | ../src/LogSink.cpp \ 10 | ../src/MailBox.cpp \ 11 | ../src/MailDatabase.cpp \ 12 | ../src/MailLink.cpp \ 13 | ../src/MailRecord.cpp \ 14 | ../src/Options.cpp \ 15 | ../src/main.cpp 16 | 17 | OBJS += \ 18 | ./src/Client.o \ 19 | ./src/Log.o \ 20 | ./src/LogSink.o \ 21 | ./src/MailBox.o \ 22 | ./src/MailDatabase.o \ 23 | ./src/MailLink.o \ 24 | ./src/MailRecord.o \ 25 | ./src/Options.o \ 26 | ./src/main.o 27 | 28 | CPP_DEPS += \ 29 | ./src/Client.d \ 30 | ./src/Log.d \ 31 | ./src/LogSink.d \ 32 | ./src/MailBox.d \ 33 | ./src/MailDatabase.d \ 34 | ./src/MailLink.d \ 35 | ./src/MailRecord.d \ 36 | ./src/Options.d \ 37 | ./src/main.d 38 | 39 | 40 | # Each subdirectory must supply rules for building sources it contributes 41 | src/%.o: ../src/%.cpp 42 | @echo 'Building file: $<' 43 | @echo 'Invoking: GCC C++ Compiler' 44 | g++ -O0 -g3 -Wall -c -fmessage-length=0 -fno-operator-names -Wno-write-strings -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<" 45 | @echo 'Finished building: $<' 46 | @echo ' ' 47 | 48 | 49 | -------------------------------------------------------------------------------- /bin/makefile: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Automatically-generated file. Do not edit! 3 | ################################################################################ 4 | 5 | -include ../makefile.init 6 | 7 | RM := rm -rf 8 | 9 | # All of the sources participating in the build are defined here 10 | -include sources.mk 11 | -include subdir.mk 12 | -include src/subdir.mk 13 | -include objects.mk 14 | 15 | ifneq ($(MAKECMDGOALS),clean) 16 | ifneq ($(strip $(C++_DEPS)),) 17 | -include $(C++_DEPS) 18 | endif 19 | ifneq ($(strip $(C_DEPS)),) 20 | -include $(C_DEPS) 21 | endif 22 | ifneq ($(strip $(CC_DEPS)),) 23 | -include $(CC_DEPS) 24 | endif 25 | ifneq ($(strip $(CPP_DEPS)),) 26 | -include $(CPP_DEPS) 27 | endif 28 | ifneq ($(strip $(CXX_DEPS)),) 29 | -include $(CXX_DEPS) 30 | endif 31 | ifneq ($(strip $(C_UPPER_DEPS)),) 32 | -include $(C_UPPER_DEPS) 33 | endif 34 | endif 35 | 36 | -include ../makefile.defs 37 | 38 | # Add inputs and outputs from these tool invocations to the build variables 39 | 40 | # All Target 41 | all: gmailcc 42 | 43 | # Tool invocations 44 | gmailcc: $(OBJS) $(USER_OBJS) 45 | @echo 'Building target: $@' 46 | @echo 'Invoking: GCC C++ Linker' 47 | g++ -o"gmailcc" $(OBJS) $(USER_OBJS) $(LIBS) 48 | @echo 'Finished building target: $@' 49 | @echo ' ' 50 | 51 | # Other Targets 52 | clean: 53 | -$(RM) $(OBJS)$(C++_DEPS)$(C_DEPS)$(CC_DEPS)$(CPP_DEPS)$(EXECUTABLES)$(CXX_DEPS)$(C_UPPER_DEPS) gmailcc 54 | -@echo ' ' 55 | 56 | .PHONY: all clean dependents 57 | .SECONDARY: 58 | 59 | -include ../makefile.targets 60 | -------------------------------------------------------------------------------- /src/Log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H_ 2 | #define LOG_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * Simple Log-class 2009 david@crowdway.com 11 | * 12 | * Usage: 13 | * Log::error << "There be dragons @ " << index << Log::endl; 14 | * 15 | * For more advanced (manipulatable) logger: 16 | * http://stackoverflow.com/questions/290632/how-to-overload-operator-that-doesnt-take-or-return-ostream 17 | * http://stdcxx.apache.org/doc/stdlibug/33-2.html 18 | */ 19 | 20 | class LogSink; 21 | 22 | class Log 23 | { 24 | public: 25 | static const int priority_debug = 0; 26 | static const int priority_info = 1; 27 | static const int priority_warning = 2; 28 | static const int priority_error = 3; 29 | static const int priority_critical = 4; 30 | 31 | static char endl; 32 | static char* priorities[]; 33 | 34 | static void set_logfile(const char* path); 35 | static void set_priority(int priority); 36 | 37 | static LogSink debug; 38 | static LogSink info; 39 | static LogSink warning; 40 | static LogSink error; 41 | static LogSink critical; 42 | static Log* getInstance(); 43 | 44 | 45 | template 46 | Log& operator<<(L const& value) 47 | { 48 | if (!has_priority(current_priority)) 49 | return *this; 50 | 51 | *this->output << value; 52 | 53 | return *this; 54 | } 55 | 56 | protected: 57 | Log(); 58 | virtual ~Log(); 59 | 60 | private: 61 | int priority; 62 | std::ofstream* logfile; 63 | std::ostream* output; 64 | int current_priority; 65 | void end_line(); 66 | bool line_ended; 67 | 68 | bool has_priority(int priority); 69 | 70 | void new_line(); 71 | void begin_entry(int new_priority); 72 | 73 | 74 | friend class LogSink; 75 | 76 | //static Log* getInstance(); 77 | }; 78 | 79 | 80 | #endif /*LOGGER_H_*/ 81 | -------------------------------------------------------------------------------- /src/MailLink.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #include 7 | namespace bf = boost::filesystem; 8 | 9 | #include "LogSink.h" 10 | #include "MailLink.h" 11 | #include "MailRecord.h" 12 | #include "MailBox.h" 13 | 14 | #define MD_TAIL ":2," 15 | 16 | MailLink::MailLink() 17 | { 18 | marked = false; 19 | staled = false; 20 | } 21 | 22 | MailLink::~MailLink() 23 | { 24 | } 25 | 26 | void MailLink::mark() 27 | { 28 | marked = true; 29 | } 30 | 31 | void MailLink::stale() 32 | { 33 | staled = true; 34 | } 35 | 36 | string MailLink::get_base_path() 37 | { 38 | size_t index; 39 | string result = this->path; 40 | index = result.rfind(MD_TAIL); 41 | 42 | if (index != string::npos) 43 | result.replace(index, result.length(), ""); 44 | else 45 | Log::error << "get_base_path(): Unable to parse pathname" << Log::endl; 46 | 47 | return result; 48 | } 49 | 50 | string MailLink::get_path() 51 | { 52 | if (bf::exists(this->path)) 53 | return this->path; 54 | else 55 | { 56 | string base = this->get_base_path(); 57 | bf::path p(base); 58 | 59 | bf::directory_iterator end_itr; 60 | 61 | for (bf::directory_iterator itr(p.branch_path()); itr != end_itr; ++itr) // Using branch_path for boost 1.35 compatibility. Use parent_path for 1.39+ 62 | { 63 | if (itr->path().leaf().find(p.leaf()) != string::npos) // Using leaf() for boost 1.35 compatibility. Use filename() for 1.39+ 64 | { 65 | this->set_path(itr->path().string()); 66 | return this->path; 67 | } 68 | } 69 | 70 | } 71 | 72 | Log::error << "Could not find new filename of " << this->path << " in " << this->mailbox->name << Log::endl; 73 | return this->path; 74 | } 75 | 76 | void MailLink::set_path(string path) 77 | { 78 | this->path = path; 79 | } -------------------------------------------------------------------------------- /src/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.h" 2 | 3 | #include "LogSink.h" 4 | 5 | char Log::endl = '\n'; 6 | char* Log::priorities[] = { "debug", "info", "warning", "error", "critical" }; 7 | 8 | 9 | LogSink Log::debug; 10 | LogSink Log::info; 11 | LogSink Log::warning; 12 | LogSink Log::error; 13 | LogSink Log::critical; 14 | 15 | 16 | Log::Log() 17 | { 18 | logfile = NULL; 19 | output = &std::cout; 20 | priority = priority_warning; 21 | line_ended = true; 22 | 23 | debug.set(*this, priority_debug); 24 | info.set(*this, priority_info); 25 | warning.set(*this, priority_warning); 26 | error.set(*this, priority_error); 27 | critical.set(*this, priority_critical); 28 | } 29 | 30 | Log::~Log() 31 | { 32 | if (logfile != NULL) 33 | { 34 | output = &std::cout; 35 | logfile->close(); 36 | delete logfile; 37 | } 38 | } 39 | 40 | 41 | void Log::set_logfile(const char* path) 42 | { 43 | Log::getInstance()->logfile = new std::ofstream(path); 44 | Log::getInstance()->output = Log::getInstance()->logfile; 45 | } 46 | 47 | void Log::set_priority(int priority) 48 | { 49 | Log::getInstance()->priority = priority; 50 | } 51 | 52 | bool Log::has_priority(int priority) 53 | { 54 | return this->priority <= priority; 55 | } 56 | 57 | 58 | void Log::end_line() 59 | { 60 | line_ended = true; 61 | } 62 | 63 | void Log::new_line() 64 | { 65 | if (!line_ended) 66 | *output << std::endl; 67 | else 68 | line_ended = false; 69 | 70 | *output << "[" << priorities[current_priority] << "] "; 71 | } 72 | 73 | void Log::begin_entry(int new_priority) 74 | { 75 | int last_priority = current_priority; 76 | current_priority = new_priority; 77 | 78 | if (!has_priority(new_priority)) 79 | return; 80 | 81 | if (line_ended || (last_priority != new_priority)) // If the priority has changed, assume a new line 82 | new_line(); 83 | } 84 | 85 | 86 | template<> 87 | Log& Log::operator<<(char const& value) 88 | { 89 | if (!has_priority(current_priority)) 90 | return *this; 91 | 92 | *this->output << value; 93 | 94 | if (value == Log::endl) 95 | { 96 | (*this->output).flush(); 97 | this->end_line(); 98 | } 99 | 100 | return *this; 101 | } 102 | 103 | Log* Log::getInstance() 104 | { 105 | static Log instance; 106 | return &instance; 107 | } 108 | 109 | -------------------------------------------------------------------------------- /src/MailBox.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #include "MailBox.h" 7 | #include "MailRecord.h" 8 | #include "MailDatabase.h" 9 | #include "MailLink.h" 10 | #include "LogSink.h" 11 | 12 | const char* MailBox::primaries[] = { "[Gmail]/All Mail", "[Gmail]/Trash", "[Gmail]/Spam" }; // If you change the itemcount, change it in is_primary too 13 | 14 | bool MailBox::is_primary(const string name) 15 | { 16 | for(int i = 0; i < 3; i++) { 17 | if (strcmp(primaries[i], name.c_str()) == 0) 18 | return true; 19 | } 20 | 21 | return false; 22 | } 23 | 24 | bool MailBox::is_inbox(const string name) 25 | { 26 | return (name.compare("INBOX") == 0); 27 | } 28 | 29 | 30 | string MailBox::get_path() 31 | { 32 | return path; 33 | } 34 | 35 | void MailBox::mark() 36 | { 37 | marked = true; 38 | } 39 | 40 | void MailBox::dirty() 41 | { 42 | dirtied = true; 43 | } 44 | 45 | /* 46 | * Remove mails in this box marked as removed (i.e. touched == false or stale == true) 47 | */ 48 | int MailBox::sweep(bool check_marked) 49 | { 50 | if (!dirtied && !check_marked) 51 | return 0; 52 | 53 | int count = 0; 54 | 55 | for(vector::iterator iter = mails.begin(); iter < mails.end(); ) 56 | { 57 | if (check_marked && !(*iter)->marked) 58 | { 59 | (*iter)->mailrecord->remove_from_mailbox(this); 60 | } 61 | 62 | if ((*iter)->staled) // Stale, this is a removed link 63 | { 64 | delete *iter; 65 | iter = mails.erase(iter); 66 | count++; 67 | } 68 | 69 | else 70 | iter++; 71 | } 72 | 73 | messagecount = mails.size(); 74 | Log::info << "Removed " << count << " messages." << Log::endl; 75 | 76 | return count; 77 | } 78 | 79 | MailBox::MailBox(MailDatabase* maildb, string name): name(name), maildb(maildb) 80 | { 81 | inbox = is_inbox(name); 82 | primary = is_primary(name); 83 | 84 | if (inbox) { 85 | path = maildb->get_maildir(); 86 | } 87 | 88 | else { 89 | path = "." + name; 90 | 91 | // Generate directorypath 92 | size_t index = 0; 93 | while((index = path.find("/", index)) != string::npos) 94 | path.replace(index, 1, "."); 95 | 96 | path = maildb->get_maildir() + path + "/"; 97 | } 98 | } 99 | 100 | MailBox::~MailBox() 101 | { 102 | 103 | } -------------------------------------------------------------------------------- /src/MailRecord.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #ifndef MAILRECORD_H_ 7 | #define MAILRECORD_H_ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #undef min 19 | #undef max 20 | #include 21 | 22 | 23 | 24 | #define MD_TAIL ":2," 25 | 26 | #include "MailLink.h" 27 | class MailBox; 28 | 29 | using namespace std; 30 | 31 | /* 32 | * A mailrecord is the primary instance of an email. Per email there is only 33 | * one mailrecord, but for every Label an email has, there is one maillink. 34 | */ 35 | 36 | class MailRecord 37 | { 38 | public: 39 | string messageid; 40 | vector links; 41 | MailLink* mainlink; 42 | bool marked; 43 | void mark(); 44 | MailLink* find_link(MailBox* mailbox); // Get the MailLink corresponding to the give mailbox and this email 45 | 46 | MailRecord(); 47 | virtual ~MailRecord(); 48 | 49 | // Getters & Setters 50 | bool get_flag_draft(); 51 | void set_flag_draft(bool flag_draft); 52 | bool get_flag_flagged(); 53 | void set_flag_flagged(bool flag_flagged); 54 | bool get_flag_passed(); 55 | void set_flag_passed(bool flag_passed); 56 | bool get_flag_replied(); 57 | void set_flag_replied(bool flag_replied); 58 | bool get_flag_seen(); 59 | void set_flag_seen(bool flag_seen); 60 | bool get_flag_trashed(); 61 | void set_flag_trashed(bool flag_trashed); 62 | 63 | void sync_flags(); 64 | 65 | // Management 66 | void save_content(string body); 67 | MailLink* add_to_mailbox(MailBox* mailbox, unsigned long uid, string path = ""); 68 | void remove_from_mailbox(MailBox* mailbox); 69 | vector::iterator remove_from_mailbox(vector::iterator iter); 70 | void remove(); 71 | void load_md_info(); 72 | 73 | 74 | 75 | 76 | private: 77 | static int number_of_deliveries; 78 | 79 | bool flag_draft; 80 | bool flag_flagged; 81 | bool flag_passed; 82 | bool flag_replied; 83 | bool flag_seen; 84 | bool flag_trashed; 85 | 86 | void extract_base_path(string& path); 87 | string get_md_info(); 88 | 89 | string convert_to_path(string& mailbox); 90 | string generate_md_filename(); 91 | vector::iterator find_ilink(MailBox* mailbox); 92 | 93 | }; 94 | 95 | #endif /*MAILRECORD_H_*/ 96 | -------------------------------------------------------------------------------- /src/Client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #ifndef CLIENT_H_ 7 | #define CLIENT_H_ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | extern "C" { 16 | #include "c-client/c-client.h" 17 | } 18 | 19 | #undef T 20 | #undef min 21 | #undef max 22 | 23 | #define SERVER "{imap.gmail.com:993/ssl}" 24 | #define SERV_INBOX "{imap.gmail.com:993/ssl}INBOX" 25 | #define SERV_ALL "{imap.gmail.com:993/ssl}[Gmail]All Mail" 26 | #define MAIL_LIST_REFERENCE "{imap.gmail.com}" 27 | #define MAIL_LIST_PATTERN "*" 28 | 29 | #define MSG_SYSTEM_ERROR "System Error (Failure)" 30 | #define MSG_INVALID_CREDENTIALS "[ALERT] Invalid credentials (Failure)" 31 | #define MSG_WEB_LOGIN_REQUIRED "[ALERT] Web login required (Failure)" 32 | #define MSG_CONNECTION_BROKEN "[CLOSED] IMAP connection broken (server response)" 33 | #define MSG_CONNECTION_LOST "[CLOSED] IMAP connection lost" 34 | 35 | 36 | #define DEF_MAXLOGINTRIALS 1 37 | #define DEF_OPENTIMEOUT 5 38 | 39 | //struct MAILSTREAM; 40 | 41 | #include "ClientException.h" 42 | 43 | using namespace std; 44 | 45 | /* 46 | * Implements an IMAP client with every function we need. 47 | */ 48 | 49 | class Client 50 | { 51 | public: 52 | static Client *active; 53 | 54 | unsigned long count_recent; 55 | unsigned long count_unseen; 56 | 57 | unsigned long uid_validity; 58 | unsigned long uid_next; 59 | 60 | unsigned long msg_index; // Used when looping on msgnumber through a mailbox 61 | // Gets decremented when a mail is EXPUNGE'd that < msg_index 62 | 63 | bool invalid_credentials; 64 | bool web_login_required; 65 | bool fatal_error; 66 | MAILSTREAM* stream; 67 | 68 | vector mailboxlist; // List with mailboxes, with {...} removed already 69 | 70 | void connect(string username, string password) throw(ClientException); 71 | void disconnect(); 72 | void get_mailboxen(); 73 | void open_mailbox(string mailbox) throw(ClientException); // Set the current mailbox. Also calls refresh_mailbox() 74 | void close_mailbox(); 75 | void refresh_mailbox(string mailbox); // Retrieve the messagecount, UID-next and UID-validity 76 | unsigned long get_mailcount(); 77 | /*unsigned long get_mailcount(string mailbox); 78 | unsigned long get_cachecount();*/ 79 | 80 | string remote(string mailbox); 81 | 82 | Client(); 83 | virtual ~Client(); 84 | 85 | /* Callback Functions */ 86 | void mm_login (NETMBX *mb,char *user,char *pwd,long trial); 87 | void mm_list (MAILSTREAM *stream, char delim, char *name, long attrib); 88 | void mm_status (MAILSTREAM *stream, char *mailbox, MAILSTATUS *status); 89 | void mm_expunged (MAILSTREAM *stream,unsigned long number); 90 | 91 | 92 | 93 | private: 94 | string username; 95 | string password; 96 | 97 | void open_stream(string mailbox, long options = OP_READONLY) throw(ClientException); // Wrapper for mail_open 98 | //string current_mailbox; 99 | //void change_mailbox(string new_mailbox); 100 | 101 | }; 102 | 103 | 104 | 105 | #endif /*CLIENT_H_*/ 106 | -------------------------------------------------------------------------------- /src/Options.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #include "Options.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | namespace bf = boost::filesystem; 14 | 15 | #include "LogSink.h" 16 | 17 | 18 | Options::Options(int argc, char* argv[]) 19 | { 20 | generic = new bo::options_description("Generic options"); 21 | generic->add_options() 22 | ("help,?,h", "produce help message") 23 | ("version,v", "show version information") 24 | ("config,c", "set config file") 25 | ("interactive,i", "set interactive mode (no config-files are read)") 26 | ; 27 | 28 | config = new bo::options_description("Configuration"); 29 | config->add_options() 30 | ("loglevel", bo::value()->default_value(2), "set loglevel") 31 | ("logfile", bo::value(), "file to log to") 32 | ("username,u", bo::value(), "username to be used to log in. If you're using Gmail, you can omit @gmail.com. If you're using Google Apps, please add your domain, e.g. john@smith.com") 33 | ("password,p", bo::value(), "password to log in with") 34 | ("maildir,d", bo::value(), "folder to backup your mails to") 35 | ; 36 | 37 | cmdline_options = new bo::options_description; 38 | cmdline_options->add(*generic); 39 | cmdline_options->add(*config); 40 | 41 | vm = new bo::variables_map(); 42 | bo::store(bo::parse_command_line(argc, argv, *cmdline_options), *vm); 43 | bo::notify(*vm); 44 | 45 | if (!vm->count("interactive")) 46 | { 47 | if (vm->count("config")) 48 | { 49 | if (!load_config_file((*vm)["config"].as())) 50 | { 51 | Log::error << "Config-file " << (*vm)["config"].as() << "doesn't exist." << Log::endl; 52 | } 53 | } else { 54 | std::string paths[2] = {"~/.gmailcc.conf", "/etc/gmailcc.conf"}; 55 | 56 | for(int i = 0; i < 2; i++) 57 | { 58 | if (load_config_file(paths[i].c_str())) 59 | break; 60 | } 61 | } 62 | } 63 | } 64 | 65 | std::string Options::expand_path(std::string path) 66 | { 67 | wordexp_t exp_result; 68 | wordexp(path.c_str(), &exp_result, 0); 69 | 70 | return exp_result.we_wordv[0]; 71 | } 72 | 73 | bool Options::load_config_file(std::string filename) 74 | { 75 | std::string path = expand_path(filename); 76 | if (!bf::exists(path)) return false; 77 | 78 | std::ifstream ifs(path.c_str()); 79 | bo::store(bo::parse_config_file(ifs, *config), *vm); 80 | bo::notify(*vm); 81 | 82 | return true; 83 | } 84 | 85 | Options::~Options() 86 | { 87 | delete vm; 88 | delete cmdline_options; 89 | delete config; 90 | delete generic; 91 | } 92 | 93 | 94 | void Options::show_help() 95 | { 96 | std::cout << *cmdline_options << "\n"; 97 | } 98 | 99 | 100 | bool Options::incomplete() 101 | { 102 | return (!(vm->count("username") && vm->count("password") && vm->count("maildir"))); 103 | } 104 | 105 | bool Options::get_help() 106 | { 107 | return vm->count("help"); 108 | } 109 | 110 | bool Options::get_version() 111 | { 112 | return vm->count("version"); 113 | } 114 | 115 | int Options::get_loglevel() 116 | { 117 | return (*vm)["loglevel"].as(); 118 | } 119 | 120 | std::string Options::get_logfile() 121 | { 122 | if (vm->count("logfile")) 123 | return expand_path((*vm)["logfile"].as()); 124 | else 125 | return ""; 126 | } 127 | 128 | std::string Options::get_maildir_path() 129 | { 130 | if (vm->count("maildir")) 131 | return expand_path((*vm)["maildir"].as()); 132 | else 133 | return ""; 134 | } 135 | 136 | std::string Options::get_username() 137 | { 138 | if (vm->count("username")) 139 | return (*vm)["username"].as(); 140 | else 141 | return ""; 142 | } 143 | 144 | std::string Options::get_password() 145 | { 146 | if (vm->count("password")) 147 | return (*vm)["password"].as(); 148 | else 149 | return ""; 150 | } 151 | -------------------------------------------------------------------------------- /src/MailDatabase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #ifndef MAILDATABASE_H_ 7 | #define MAILDATABASE_H_ 8 | 9 | #include 10 | #include 11 | 12 | 13 | #include "MailBox.h" 14 | class MailRecord; 15 | 16 | /* 17 | * IMAP Maildir++ Format: 18 | * ~/Maildir/cur -> These mails are "seen" or "read" 19 | * ~/Maildir/tmp -> new mails are stored here. Upon completion, they are moved to new 20 | * ~/Maildir/new -> These mails are not "seen" or "read" 21 | * ~/Maildir/.subfolder -> A subfolder 22 | * ~/Maildir/.subfolder/cur ... -> Each folder and subfolder needs cur/tmp/new to be recognized 23 | * 24 | * Possible FAQ: My Labels aren't showing up in my webmail client. Please make sure you are 25 | * subscribed to these folders. Possibly no way to enforce this using Maildir 26 | * 27 | * Tested on Courier-imap-ssl & roundcube: 28 | * Hard links work 29 | * Symbolic links work if absolute to root or userdir (~) 30 | * Example: ln -s /home/david/maildir/cur/bla ./ works 31 | * ln -s ~/maildir/cur/bla ./ works 32 | * ln -s ../../cur/bla ./ does not work 33 | * 34 | * Oddness: 35 | * When using the info section to mark a message as non-read (as in, removing the S), the "new message" counter 36 | * next to the label is increased, yet when opening the folder the message's read status looks "cached" and set as "read". 37 | * 38 | * When moving the original to which the link points, the non-read counter next to the label counts the dead-links as well, 39 | * but when opening the label the dead-links mail are not shown. 40 | * 41 | * Maildir++: 42 | * http://www.courier-mta.org/imap/README.maildirquota.html 43 | * 44 | * Dovecot has some specifics on migration, including UID/UIDVALIDITY integrity, message flags, and 45 | * mailbox subscription list: http://wiki.dovecot.org/Migration 46 | * 47 | * Courier Specific Maildir Extensions 48 | * ----------------------------------- 49 | * 50 | * ~/Maildir/courierimapsubscribed A file containing on each line a folder to which the user is subscribed 51 | * Example: INBOX.Label1 52 | * 53 | * Keywords: http://www.courier-mta.org/imap/README.imapkeywords.html 54 | * 55 | * Dovecot 56 | * ------- 57 | * 58 | * Courier's courierimapsubscribed file is compatible with Dovecot's subscriptions file, 59 | * but you need to remove the "INBOX." prefixes from the mailboxes. 60 | * ~/Maildir/.subscriptions (of ~/mail/.subscriptions) 61 | * 62 | * UW-IMAP 63 | * ------- 64 | * 65 | * Subscription list: ~/.mailboxlist 66 | * 67 | */ 68 | 69 | class MailDatabase 70 | { 71 | public: 72 | vector mailboxes; 73 | vector messages; 74 | 75 | static MailDatabase* load(string path); 76 | static MailDatabase* create(string path); 77 | void save(); 78 | 79 | MailBox* add_mailbox(string mailbox); 80 | void remove_mailbox(string mailbox); 81 | 82 | bool mail_exists(unsigned long uid); 83 | bool mail_exists(string mailbox, unsigned long uid); 84 | 85 | MailRecord* get_mail(MailBox* mailbox, unsigned long uid); 86 | MailRecord* get_mail(unsigned long uid); 87 | 88 | vector::iterator get_imail(string messageid); 89 | MailRecord* get_mail(string messageid); 90 | 91 | MailRecord* add_mail(string messageid, MailBox* mailbox, unsigned long uid, string path); // Use it when loading database 92 | 93 | MailRecord* new_mail(string messageid, MailBox* mailbox, unsigned long uid); // Add new mail to secondary mailbox, no text necessary 94 | MailRecord* new_mail(string messageid, unsigned long uid, string body); 95 | MailRecord* new_mail(string messageid, unsigned long uid, string header, string content); // Add new mail to All Messages 96 | 97 | 98 | void remove_mail(MailRecord* mr, MailBox* mailbox = NULL); 99 | void remove_mail(string messageid, MailBox* mailbox = NULL); 100 | 101 | void sweep(); 102 | 103 | string get_maildir(); 104 | 105 | MailDatabase(); 106 | virtual ~MailDatabase(); 107 | 108 | private: 109 | string maildir; 110 | 111 | bool is_primary(string mailbox); 112 | MailBox* get_mailbox(string mailbox); 113 | 114 | static void create_maildir(string path); 115 | 116 | }; 117 | 118 | #endif /*MAILDATABASE_H_*/ 119 | -------------------------------------------------------------------------------- /src/Client.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #include 7 | 8 | #include "Client.h" 9 | #include "LogSink.h" 10 | 11 | Client* Client::active; 12 | 13 | void Client::open_stream(string mailbox, long options) throw(ClientException) 14 | { 15 | invalid_credentials = false; 16 | web_login_required = false; 17 | char* mb = cpystr(remote(mailbox).c_str()); 18 | 19 | //close_mailbox(); 20 | stream = mail_open(stream, mb, options | OP_READONLY); 21 | 22 | free(mb); 23 | 24 | if (stream == NULL) { 25 | if (invalid_credentials) 26 | throw InvalidAuthClientException(this); 27 | else if (web_login_required) 28 | throw WebAuthClientException(this); 29 | else 30 | throw ClientException(this); 31 | } 32 | } 33 | 34 | void Client::connect(string username, string password) throw(ClientException) 35 | { 36 | this->username = username; 37 | this->password = password; 38 | 39 | open_stream(SERV_INBOX); 40 | 41 | // Get the mailboxes as initialization 42 | get_mailboxen(); 43 | } 44 | 45 | void Client::open_mailbox(string mailbox) throw(ClientException) 46 | { 47 | open_stream(mailbox); 48 | refresh_mailbox(mailbox); 49 | } 50 | 51 | void Client::close_mailbox() 52 | { 53 | if (stream) 54 | stream = mail_close(stream); 55 | } 56 | 57 | void Client::refresh_mailbox(string mailbox) 58 | { 59 | char* mb = cpystr(remote(mailbox).c_str()); 60 | 61 | mail_status(stream, mb, SA_MESSAGES | SA_UIDNEXT | SA_UIDVALIDITY); 62 | 63 | free(mb); 64 | } 65 | 66 | void Client::get_mailboxen() 67 | { 68 | mailboxlist.clear(); 69 | mail_list(stream, MAIL_LIST_REFERENCE, MAIL_LIST_PATTERN); 70 | } 71 | 72 | unsigned long Client::get_mailcount() 73 | { 74 | return stream->nmsgs; 75 | } 76 | /* 77 | * These two functions are deprecated since we found out Gimap (Gmail's IMAP server) has a bug 78 | * where stream->nmsgs is smaller than the count returned by mail_status. stream->nmsgs is the 79 | * correct number. 80 | */ 81 | /* 82 | unsigned long Client::get_mailcount() 83 | { 84 | mail_status(stream, stream->mailbox, SA_MESSAGES); 85 | 86 | return count_messages; 87 | 88 | } 89 | 90 | unsigned long Client::get_mailcount(string mailbox) 91 | { 92 | char* mb = cpystr(remote(mailbox).c_str()); 93 | 94 | mail_status(stream, mb, SA_MESSAGES); 95 | 96 | free(mb); 97 | 98 | return count_messages; 99 | } 100 | */ 101 | 102 | void Client::disconnect() 103 | { 104 | } 105 | 106 | string Client::remote(string mailbox) 107 | { 108 | if (mailbox[0] != '{') 109 | return mailbox.insert(0, SERVER); 110 | else 111 | return mailbox; 112 | } 113 | 114 | Client::Client() 115 | { 116 | #include "c-client/linkage.c" 117 | 118 | Client::active = this; 119 | stream = NULL; 120 | 121 | invalid_credentials = false; 122 | web_login_required = false; 123 | fatal_error = false; 124 | 125 | /* Set operational parameters */ 126 | mail_parameters(NIL, SET_MAXLOGINTRIALS, (void *) DEF_MAXLOGINTRIALS); 127 | mail_parameters(NIL, SET_OPENTIMEOUT, (void * ) DEF_OPENTIMEOUT); 128 | } 129 | 130 | Client::~Client() 131 | { 132 | } 133 | 134 | /* Callback Functions */ 135 | void Client::mm_login (NETMBX *mb,char *user,char *pwd,long trial) 136 | { 137 | Log::info << "[c-client] Trying to log in (take " << trial << ")" << Log::endl; 138 | 139 | strcpy(user, username.c_str()); 140 | strcpy(pwd, password.c_str()); 141 | } 142 | 143 | void Client::mm_list(MAILSTREAM *stream, char delim, char *name, long attrib) 144 | { 145 | string mailbox = name; 146 | 147 | mailboxlist.push_back(mailbox.substr(mailbox.find("}")+1, string::npos)); 148 | } 149 | 150 | void Client::mm_status (MAILSTREAM *stream, char *mailbox, MAILSTATUS *status) 151 | { 152 | //if (status->flags & SA_MESSAGES) this->count_messages = status->messages; 153 | if (status->flags & SA_RECENT) this->count_recent = status->recent; 154 | if (status->flags & SA_UNSEEN) this->count_unseen = status->unseen; 155 | if (status->flags & SA_UIDVALIDITY) this->uid_validity = status->uidvalidity; 156 | if (status->flags & SA_UIDNEXT) this->uid_next = status->uidnext; 157 | } 158 | 159 | void Client::mm_expunged (MAILSTREAM *stream,unsigned long number) 160 | { 161 | // "number" is removed from the mailbox, so every subsequent message's number has been decremented 162 | 163 | //count_messages--; 164 | 165 | // However, this shouldn't affect our FETCH operations since according to the rfc's 166 | // the server MUST NOT send an EXPUNGE in response to a FETCH, to "maintain sync 167 | // with client". So the following lines may just be useless. 168 | if (msg_index > number) 169 | { 170 | msg_index--; 171 | } 172 | } 173 | 174 | 175 | /* Interfaces to C-client */ 176 | 177 | 178 | void mm_searched (MAILSTREAM *stream,unsigned long number) 179 | { 180 | } 181 | 182 | 183 | void mm_exists (MAILSTREAM *stream,unsigned long number) 184 | { 185 | } 186 | 187 | 188 | void mm_expunged (MAILSTREAM *stream,unsigned long number) 189 | { 190 | // This function is apparently not called when a mail 191 | // gets expunged by another client connected at the 192 | // same time. 193 | 194 | // However, when a mail gets expunged we do get this 195 | // output: 196 | // mm_log: %Unknown message data: 1800 EXPUNGE 197 | // After (!) a call to the server which is not a fetch 198 | // call: 199 | 200 | // 3856 Message index: 1918, Count: 1918 201 | // 3857 Checking mailbox Business... 202 | // 3858 mm_log: %Unknown message data: 1800 EXPUNGE 203 | // 3859 mm_log: %Unknown message data: 1882 EXPUNGE 204 | // 3860 mm_log: %Unknown message data: 1884 EXPUNGE 205 | // 3861 mm_log: %Unknown message data: 1886 EXPUNGE 206 | // 3862 mm_log: %Unknown message data: 1893 EXPUNGE 207 | 208 | printf("Expunged %lu\n", number); 209 | Client::active->mm_expunged(stream, number); 210 | } 211 | 212 | 213 | void mm_flags (MAILSTREAM *stream,unsigned long number) 214 | { 215 | // This function is called repeatedly for every message 216 | // after a mail_fetchfast() call. 217 | 218 | // printf("Flags!\n"); 219 | } 220 | 221 | 222 | void mm_notify (MAILSTREAM *stream,char *string,long errflg) 223 | { 224 | mm_log (string,errflg); 225 | } 226 | 227 | 228 | void mm_list (MAILSTREAM *stream,int delimiter,char *mailbox,long attributes) 229 | { 230 | Client::active->mm_list(stream, delimiter, mailbox, attributes); 231 | } 232 | 233 | 234 | void mm_lsub (MAILSTREAM *stream,int delimiter,char *mailbox,long attributes) 235 | { 236 | putchar (' '); 237 | if (delimiter) putchar (delimiter); 238 | else fputs ("NIL",stdout); 239 | putchar (' '); 240 | fputs (mailbox,stdout); 241 | if (attributes & LATT_NOINFERIORS) fputs (", no inferiors",stdout); 242 | if (attributes & LATT_NOSELECT) fputs (", no select",stdout); 243 | if (attributes & LATT_MARKED) fputs (", marked",stdout); 244 | if (attributes & LATT_UNMARKED) fputs (", unmarked",stdout); 245 | putchar ('\n'); 246 | } 247 | 248 | 249 | void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status) 250 | { 251 | Client::active->mm_status(stream, mailbox, status); 252 | } 253 | 254 | 255 | /* Here come all the error message through. Some samples: 256 | * Wrong credentials: 257 | * long WARN or PARSE, message: [ALERT] Invalid credentials (Failure) 258 | * No internet connection: 259 | * long ERROR, message: No such host as imap.gmail.com 260 | * 261 | * Wrong IP/DNS: 262 | * it just waits endlessly after NIL - Trying IP address [xx.xx.xx.xx] 263 | * after some minutes it gives: 264 | * long ERROR, message: Can't connect to xxxxxxxxx: Connection timed out 265 | */ 266 | 267 | void mm_log (char *msg,long errflg) 268 | { 269 | switch ((short) errflg) { 270 | case NIL: 271 | Log::info << "[c-client] [warning] " << msg << Log::endl; 272 | break; 273 | case PARSE: 274 | case WARN: 275 | Log::info << "[c-client] [warning] " << msg << Log::endl; 276 | 277 | if (strcmp(msg, MSG_INVALID_CREDENTIALS) == 0) 278 | Client::active->invalid_credentials = true; 279 | 280 | if (strcmp(msg, MSG_WEB_LOGIN_REQUIRED) == 0) 281 | Client::active->web_login_required = true; 282 | 283 | break; 284 | case ERROR: 285 | Log::info << "[c-client] [error] " << msg << Log::endl; 286 | 287 | if ((strcmp(msg, MSG_CONNECTION_LOST) == 0) || 288 | (strcmp(msg, MSG_CONNECTION_BROKEN) == 0) || 289 | (strcmp(msg, MSG_SYSTEM_ERROR) == 0)) 290 | { 291 | Log::error << "[c-client] Fatal error detected" << Log::endl; 292 | Client::active->fatal_error = true; 293 | } 294 | 295 | break; 296 | default: 297 | Log::info << "[c-client] " << msg << Log::endl; 298 | break; 299 | } 300 | } 301 | 302 | 303 | void mm_dlog (char *string) 304 | { 305 | Log::info << (string) << Log::endl; 306 | } 307 | 308 | 309 | void mm_login (NETMBX *mb,char *user,char *pwd,long trial) 310 | { 311 | Client::active->mm_login(mb, user, pwd, trial); 312 | } 313 | 314 | 315 | void mm_critical (MAILSTREAM *stream) 316 | { 317 | printf("mm_critical\n"); 318 | } 319 | 320 | 321 | void mm_nocritical (MAILSTREAM *stream) 322 | { 323 | printf("mm_nocritical\n"); 324 | } 325 | 326 | 327 | long mm_diskerror (MAILSTREAM *stream,long errcode,long serious) 328 | { 329 | 330 | #if UNIXLIKE 331 | kill (getpid (),SIGSTOP); 332 | #else 333 | abort (); 334 | #endif 335 | return NIL; 336 | 337 | } 338 | 339 | 340 | void mm_fatal (char *string) 341 | { 342 | printf ("?%s\n",string); 343 | } 344 | -------------------------------------------------------------------------------- /src/MailRecord.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #include "MailRecord.h" 7 | 8 | #include 9 | namespace bf = boost::filesystem; 10 | 11 | #include "LogSink.h" 12 | #include "MailLink.h" 13 | #include "MailBox.h" 14 | 15 | int MailRecord::number_of_deliveries = 0; 16 | 17 | MailRecord::MailRecord() 18 | { 19 | marked = false; 20 | 21 | flag_draft = false; 22 | flag_flagged = false; 23 | flag_passed = false; 24 | flag_replied = false; 25 | flag_seen = false; 26 | flag_trashed = false; 27 | } 28 | 29 | MailRecord::~MailRecord() 30 | { 31 | for (vector::iterator iter = this->links.begin(); iter < links.end(); iter++) 32 | { 33 | delete(*iter); 34 | } 35 | } 36 | 37 | void MailRecord::mark() 38 | { 39 | marked = true; 40 | } 41 | 42 | bool MailRecord::get_flag_draft() 43 | { 44 | return flag_draft; 45 | } 46 | 47 | void MailRecord::set_flag_draft(bool flag_draft) 48 | { 49 | if (this->flag_draft == flag_draft) 50 | return; 51 | 52 | this->flag_draft = flag_draft; 53 | } 54 | 55 | bool MailRecord::get_flag_flagged() 56 | { 57 | return flag_flagged; 58 | } 59 | 60 | void MailRecord::set_flag_flagged(bool flag_flagged) 61 | { 62 | if (this->flag_flagged == flag_flagged) 63 | return; 64 | 65 | this->flag_flagged = flag_flagged; 66 | } 67 | 68 | bool MailRecord::get_flag_passed() 69 | { 70 | return flag_passed; 71 | } 72 | 73 | void MailRecord::set_flag_passed(bool flag_passed) 74 | { 75 | if (this->flag_passed == flag_passed) 76 | return; 77 | 78 | this->flag_passed = flag_passed; 79 | } 80 | 81 | bool MailRecord::get_flag_replied() 82 | { 83 | return flag_replied; 84 | } 85 | 86 | void MailRecord::set_flag_replied(bool flag_replied) 87 | { 88 | if (this->flag_replied == flag_replied) 89 | return; 90 | 91 | this->flag_replied = flag_replied; 92 | } 93 | 94 | bool MailRecord::get_flag_seen() 95 | { 96 | return flag_seen; 97 | } 98 | 99 | void MailRecord::set_flag_seen(bool flag_seen) 100 | { 101 | if (this->flag_seen == flag_seen) 102 | return; 103 | 104 | this->flag_seen = flag_seen; 105 | } 106 | 107 | bool MailRecord::get_flag_trashed() 108 | { 109 | return flag_trashed; 110 | } 111 | 112 | void MailRecord::set_flag_trashed(bool flag_trashed) 113 | { 114 | if (this->flag_trashed == flag_trashed) 115 | return; 116 | 117 | this->flag_trashed = flag_trashed; 118 | } 119 | 120 | void MailRecord::save_content(string body) 121 | { 122 | ofstream mfile; 123 | 124 | mfile.open(mainlink->get_path().c_str()); 125 | 126 | if (mfile.is_open()) 127 | { 128 | mfile << body; 129 | mfile.close(); 130 | } 131 | else Log::error << "Unable to open file " << mainlink->get_path() << Log::endl; 132 | } 133 | 134 | MailLink* MailRecord::add_to_mailbox(MailBox* mailbox, unsigned long uid, string path) 135 | { 136 | MailLink* maillink = find_link(mailbox); 137 | 138 | if (maillink != NULL) // Existing mail: just update UID 139 | { 140 | maillink->uid = uid; 141 | 142 | if (!path.empty()) 143 | { 144 | if (!rename(maillink->get_path().c_str(), path.c_str())) 145 | perror("MailRecord::add_to_mailbox: unable to move link"); 146 | else 147 | maillink->set_path(path); 148 | } 149 | } 150 | else 151 | { 152 | maillink = new MailLink; 153 | 154 | if (mailbox->primary) 155 | mainlink = maillink; 156 | 157 | bool exists = false; 158 | if (path.empty()) 159 | path = mailbox->get_path() + "cur/" + generate_md_filename() + get_md_info(); 160 | else 161 | exists = true; 162 | 163 | 164 | 165 | maillink->mailrecord = this; 166 | maillink->mailbox = mailbox; 167 | maillink->set_path(path); 168 | maillink->uid = uid; 169 | 170 | this->links.push_back(maillink); 171 | mailbox->mails.push_back(maillink); 172 | 173 | if (!exists) 174 | { 175 | if (this->links.size() > 1) // Create a link to the original 176 | { 177 | string sourcepath = this->mainlink->get_path().c_str(); 178 | //sourcepath.insert(0, "../"); 179 | 180 | bf::create_hard_link(sourcepath, path); 181 | // perror("MailRecord::add_to_mailbox: creating symlink failed"); 182 | } 183 | else // "Touch" file 184 | { 185 | ofstream mfile; 186 | mfile.open(path.c_str()); 187 | mfile << ""; 188 | mfile.close(); 189 | } 190 | } 191 | else 192 | { 193 | load_md_info(); 194 | } 195 | } 196 | 197 | 198 | return maillink; 199 | } 200 | 201 | void MailRecord::remove_from_mailbox(MailBox* mailbox) 202 | { 203 | vector::iterator iter = find_ilink(mailbox); 204 | 205 | if (iter < this->links.end()) 206 | { 207 | remove_from_mailbox(iter); 208 | return; 209 | } 210 | else 211 | { 212 | cout << "Whoops, something went wrong here" << endl; 213 | } 214 | } 215 | 216 | vector::iterator MailRecord::remove_from_mailbox(vector::iterator iter) 217 | { 218 | if (unlink((*iter)->get_path().c_str()) != 0) 219 | perror("MailRecord::remove_from_mailbox: unable to unlink file"); 220 | 221 | (*iter)->mailbox->dirty(); 222 | (*iter)->stale(); 223 | 224 | return this->links.erase(iter); 225 | } 226 | 227 | 228 | void MailRecord::remove() 229 | { 230 | for (vector::iterator iter = this->links.begin(); iter < this->links.end(); ) 231 | { 232 | iter = this->remove_from_mailbox(iter); 233 | } 234 | } 235 | 236 | MailLink* MailRecord::find_link(MailBox* mailbox) 237 | { 238 | vector::iterator result = find_ilink(mailbox); 239 | 240 | if (result == this->links.end()) 241 | return NULL; 242 | else 243 | return *result; 244 | } 245 | 246 | vector::iterator MailRecord::find_ilink(MailBox* mailbox) 247 | { 248 | for (vector::iterator iter = this->links.begin(); iter < this->links.end(); iter++) 249 | { 250 | if ((*iter)->mailbox == mailbox) 251 | { 252 | return iter; 253 | } 254 | } 255 | 256 | return this->links.end(); 257 | } 258 | 259 | 260 | 261 | /** 262 | * Regenerates the paths of every mail, moves the files 263 | * of the mails, also of every link. This is useful if one of the 264 | * files has a different info part because another client changed 265 | * the flags. 266 | */ 267 | void MailRecord::sync_flags() 268 | { 269 | for (vector::iterator iter = this->links.begin(); iter < links.end(); iter++) 270 | { 271 | string new_path = (*iter)->get_base_path(); 272 | 273 | 274 | new_path.insert(new_path.size(), this->get_md_info()); 275 | 276 | if (new_path.compare((*iter)->get_path()) != 0) 277 | { 278 | if (rename((*iter)->get_path().c_str(), new_path.c_str()) != 0) 279 | perror("MailRecord::flags_changed: Unable to rename file"); 280 | else 281 | (*iter)->set_path(new_path); 282 | } 283 | } 284 | } 285 | 286 | 287 | string MailRecord::generate_md_filename() 288 | { 289 | char seconds[20]; sprintf(seconds, "%lu", (unsigned long) time(NULL)); 290 | 291 | char hostname[99] = "hostnamex"; 292 | string shostname; 293 | pid_t pid = getpid(); 294 | 295 | if (pid < 0) 296 | perror("MailRecord::generate_md_filename: unable to get pid"); 297 | 298 | char spid[20]; sprintf(spid, "%d", pid); 299 | 300 | if (gethostname(hostname, 99) != 0) 301 | { 302 | perror("MailRecord::generate_md_filename: unable to gethostname()"); 303 | perror(hostname); 304 | } 305 | 306 | shostname = hostname; 307 | 308 | // Replace "/" with "\057" 309 | size_t index; 310 | while((index = shostname.find("/")) != string::npos) 311 | { 312 | shostname.replace(index, 1, "\057"); 313 | } 314 | 315 | // Replace ":" with "\072" 316 | while((index = shostname.find(":")) != string::npos) 317 | { 318 | shostname.replace(index, 1, "\072"); 319 | } 320 | 321 | char delnum[20]; sprintf(delnum, "%d", MailRecord::number_of_deliveries++); 322 | 323 | string result; 324 | 325 | result += seconds; 326 | result += "."; 327 | result += "P"; 328 | result += spid; 329 | result += "Q"; 330 | result += delnum; 331 | result += "."; 332 | result += shostname; 333 | 334 | return result; 335 | } 336 | 337 | /** 338 | * Generate the "info" part of the maildir specification 339 | * see: http://cr.yp.to/proto/maildir.html 340 | */ 341 | string MailRecord::get_md_info() 342 | { 343 | string result = MD_TAIL; 344 | 345 | if (this->flag_draft) result += "D"; 346 | if (this->flag_flagged) result += "F"; 347 | if (this->flag_passed) result += "P"; 348 | if (this->flag_replied) result += "R"; 349 | if (this->flag_seen) result += "S"; 350 | if (this->flag_trashed) result += "T"; 351 | 352 | return result; 353 | } 354 | 355 | /** 356 | * Extract the "info" part from the path and update lfags 357 | */ 358 | void MailRecord::load_md_info() 359 | { 360 | this->flag_draft = false; 361 | this->flag_flagged = false; 362 | this->flag_passed = false; 363 | this->flag_replied = false; 364 | this->flag_seen = false; 365 | this->flag_trashed = false; 366 | 367 | string path = mainlink->get_path(); 368 | for(size_t index = path.rfind(MD_TAIL) + strlen(MD_TAIL); index < path.size(); index++) 369 | { 370 | switch(path[index]) 371 | { 372 | case 'D': 373 | this->flag_draft = true; break; 374 | case 'F': 375 | this->flag_flagged = true; break; 376 | case 'P': 377 | this->flag_passed = true; break; 378 | case 'R': 379 | this->flag_replied = true; break; 380 | case 'S': 381 | this->flag_seen = true; break; 382 | case 'T': 383 | this->flag_trashed = true; break; 384 | } 385 | } 386 | } -------------------------------------------------------------------------------- /src/MailDatabase.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #include "MailDatabase.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | namespace bf = boost::filesystem; 17 | 18 | #include "LogSink.h" 19 | #include "MailLink.h" 20 | #include "MailBox.h" 21 | #include "MailRecord.h" 22 | 23 | 24 | MailBox* MailDatabase::add_mailbox(string mailbox) 25 | { 26 | MailBox* box = get_mailbox(mailbox); 27 | 28 | if (box != NULL) 29 | return box; 30 | 31 | box = new MailBox(this, mailbox); 32 | 33 | // Make sure the directory structure is there 34 | create_maildir(box->get_path()); 35 | 36 | mailboxes.push_back(box); 37 | 38 | return box; 39 | } 40 | 41 | bool MailDatabase::mail_exists(unsigned long uid) 42 | { 43 | return mail_exists("{imap.gmail.com}[Gmail]/All Mail", uid); 44 | 45 | } 46 | 47 | bool MailDatabase::mail_exists(string mailbox, unsigned long uid) 48 | { 49 | MailBox* mb = get_mailbox(mailbox); 50 | 51 | if (mb != NULL) 52 | { 53 | for (vector::iterator iter = mb->mails.begin(); iter < mb->mails.end(); iter++) 54 | { 55 | if ((*iter)->uid == uid) 56 | return true; 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | 63 | /* 64 | * Find the iterator in the messages list corresponding the mail with given messageid 65 | */ 66 | vector::iterator MailDatabase::get_imail(string messageid) // TODO: This function gets called unnecessary when a new mail is added (maildb->new_mail()). Remove it. 67 | { 68 | for (vector::iterator iter = messages.begin(); iter < messages.end(); iter++) 69 | { 70 | if (messageid.compare((*iter)->messageid) == 0) 71 | { 72 | return iter; 73 | } 74 | } 75 | 76 | return messages.end(); 77 | } 78 | 79 | MailRecord* MailDatabase::get_mail(MailBox* mailbox, unsigned long uid) 80 | { 81 | for (vector::iterator iter = mailbox->mails.begin(); iter < mailbox->mails.end(); iter++) 82 | { 83 | if ((*iter)->uid == uid) 84 | return (*iter)->mailrecord; 85 | } 86 | 87 | return NULL; 88 | } 89 | 90 | MailRecord* MailDatabase::get_mail(unsigned long uid) 91 | { 92 | return get_mail(get_mailbox("[Gmail]/All Mail"), uid); 93 | } 94 | 95 | MailRecord* MailDatabase::get_mail(string messageid) 96 | { 97 | vector::iterator iter = get_imail(messageid); 98 | 99 | if (iter == messages.end()) 100 | return NULL; 101 | else 102 | return *iter; 103 | } 104 | 105 | MailRecord* MailDatabase::add_mail(string messageid, MailBox* mailbox, unsigned long uid, string path) 106 | { 107 | MailRecord* mr = get_mail(messageid); 108 | 109 | if (mr == NULL) 110 | { 111 | mr = new MailRecord; 112 | 113 | mr->messageid = messageid; 114 | messages.push_back(mr); 115 | } 116 | 117 | MailLink* ml = mr->add_to_mailbox(mailbox, uid, path); 118 | 119 | if (mailbox->primary) 120 | { 121 | mr->mainlink = ml; 122 | mr->load_md_info(); 123 | } 124 | 125 | return mr; 126 | } 127 | 128 | 129 | MailRecord* MailDatabase::new_mail(string messageid, MailBox* mailbox, unsigned long uid) 130 | { 131 | MailRecord* mr = get_mail(messageid); 132 | 133 | if (mr == NULL) 134 | return NULL; 135 | 136 | mr->add_to_mailbox(mailbox, uid); 137 | 138 | return mr; 139 | } 140 | 141 | 142 | MailRecord* MailDatabase::new_mail(string messageid, unsigned long uid, string header, string content) 143 | { 144 | return new_mail(messageid, uid, header + '\n' + content); 145 | 146 | } 147 | 148 | MailRecord* MailDatabase::new_mail(string messageid, unsigned long uid, string body) 149 | { 150 | MailRecord* mr = get_mail(messageid); 151 | 152 | if (mr != NULL) 153 | return mr; 154 | else 155 | mr = new MailRecord; 156 | 157 | mr->messageid = messageid; 158 | mr->add_to_mailbox(get_mailbox("[Gmail]/All Mail"), uid); 159 | mr->save_content(body); 160 | 161 | messages.push_back(mr); 162 | 163 | return mr; 164 | } 165 | 166 | void MailDatabase::remove_mail(MailRecord* mr, MailBox* mailbox) 167 | { 168 | if (mr == NULL) 169 | return; 170 | 171 | if (mailbox != NULL) 172 | mr->remove_from_mailbox(mailbox); 173 | else 174 | mr->remove(); 175 | 176 | if (mr->links.size() <= 0) 177 | { 178 | vector::iterator mr_iter = get_imail(mr->messageid); 179 | 180 | if (mr_iter < messages.end()) 181 | { 182 | messages.erase(mr_iter); 183 | } 184 | else 185 | Log::error << "Could not find mail with message-id '" << mr->messageid << "'." << Log::endl; 186 | } 187 | } 188 | 189 | 190 | void MailDatabase::remove_mail(string messageid, MailBox* mailbox) 191 | { 192 | remove_mail(get_mail(messageid), mailbox); 193 | } 194 | 195 | 196 | MailDatabase* MailDatabase::create(string path) 197 | { 198 | return NULL; 199 | } 200 | 201 | 202 | MailBox* MailDatabase::get_mailbox(string mailbox) 203 | { 204 | for (vector::iterator it = mailboxes.begin(); it < mailboxes.end(); it++) 205 | { 206 | if ((*it)->name.compare(mailbox) == 0) 207 | { 208 | return *it; 209 | } 210 | } 211 | 212 | return NULL; 213 | 214 | } 215 | 216 | /** 217 | * mailbox-name 218 | * mailbox-uid_validity 219 | * mailbox-next_uid 220 | * mailbox-msgcount 221 | * maillink 222 | * maillink 223 | * maillink 224 | * /n 225 | */ 226 | 227 | void MailDatabase::save() 228 | { 229 | 230 | Log::info << "Saving database." << Log::endl; 231 | 232 | ofstream dbfile; 233 | 234 | dbfile.open((maildir + "database").c_str()); 235 | 236 | if (dbfile.is_open()) 237 | { 238 | // Header 239 | dbfile << "Generated by Gmail Carbon Copy -- http://code.crowdway.com/projects/gmailcc" << endl; 240 | dbfile << endl; // Signals end of comments 241 | dbfile << "1" << endl; // Format version 242 | 243 | for (vector::iterator mb = mailboxes.begin(); mb < mailboxes.end(); mb++) 244 | { 245 | dbfile << (*mb)->name << endl; // mailbox-name 246 | dbfile << (*mb)->uid_validity << endl; // mailbox-uid_validity 247 | dbfile << (*mb)->next_uid << endl; // mailbox-next_uid 248 | dbfile << (*mb)->messagecount << endl; // mailbox-msgcount 249 | 250 | for (vector::iterator mail = (*mb)->mails.begin(); mail < (*mb)->mails.end(); mail++) 251 | { 252 | dbfile << (*mail)->uid << endl; // uid 253 | dbfile << (*mail)->mailrecord->messageid << endl; // messageid 254 | dbfile << (*mail)->get_path() << endl; // path 255 | } 256 | 257 | dbfile << endl; 258 | } 259 | 260 | dbfile.close(); 261 | 262 | } 263 | else 264 | { 265 | Log::error << "Unable to open database for writing (" << maildir + "database). Could not save database." << Log::endl; 266 | } 267 | } 268 | 269 | MailDatabase* MailDatabase::load(string path) 270 | { 271 | Log::info << "Loading MailDatabase @ " << path << Log::endl; 272 | 273 | // Make sure it ends with "/" 274 | if (path[path.size()-1] != '/') 275 | path += '/'; 276 | 277 | // Make sure we have a Maildir-format type directory structure 278 | create_maildir(path); 279 | 280 | MailDatabase* mdb = new MailDatabase(); 281 | mdb->maildir = path; 282 | 283 | // Load databasefile 284 | ifstream dbfile; 285 | 286 | Log::info << "Opening database-file" << Log::endl; 287 | dbfile.open((path + "database").c_str()); 288 | 289 | if (dbfile.is_open()) 290 | { 291 | string line; 292 | 293 | MailBox* currentmb; 294 | 295 | // Header 296 | getline(dbfile, line); // First line of comment 297 | while(!line.empty()) 298 | { 299 | getline(dbfile, line); // Read next line of comment 300 | } 301 | getline(dbfile, line); // version, "1" 302 | 303 | getline(dbfile, line); 304 | while(!dbfile.eof()) 305 | { 306 | currentmb = mdb->add_mailbox(line); 307 | getline(dbfile, line); currentmb->uid_validity = atoi(line.c_str()); 308 | getline(dbfile, line); currentmb->next_uid = atoi(line.c_str()); 309 | getline(dbfile, line); currentmb->messagecount = atoi(line.c_str()); 310 | 311 | unsigned long uid; 312 | string path; 313 | string messageid; 314 | 315 | while(!dbfile.eof()) 316 | { 317 | getline(dbfile, line); 318 | 319 | if (line.empty()) 320 | { 321 | break; // Next mailbox 322 | } 323 | 324 | uid = atoi(line.c_str()); 325 | getline(dbfile, messageid); 326 | getline(dbfile, path); 327 | 328 | mdb->add_mail(messageid, currentmb, uid, path); 329 | } 330 | 331 | getline(dbfile, line); 332 | } 333 | 334 | dbfile.close(); 335 | } 336 | 337 | else 338 | { 339 | Log::info << "Couldn't open database file ( " << path << "database" << " ). Starting from scratch." << Log::endl; 340 | } 341 | 342 | return mdb; 343 | } 344 | 345 | void MailDatabase::sweep() 346 | { 347 | for (vector::iterator iter = messages.begin(); iter < messages.end(); ) 348 | { 349 | if (!(*iter)->marked) 350 | { 351 | (*iter)->remove(); 352 | delete *iter; 353 | 354 | iter = messages.erase(iter); 355 | } 356 | else 357 | iter++; 358 | } 359 | 360 | for (vector::iterator iter = mailboxes.begin(); iter < mailboxes.end(); iter++) 361 | { 362 | (*iter)->sweep(); 363 | } 364 | } 365 | 366 | string MailDatabase::get_maildir() 367 | { 368 | return maildir; 369 | } 370 | 371 | /* 372 | * Creates a directory structure fit for Maildir format. 373 | * Example: 374 | * .dir_path/new 375 | * .dir_path/cur 376 | * .dir_path/tmp 377 | */ 378 | void MailDatabase::create_maildir(string dir_path) 379 | { 380 | bf::path path(dir_path); 381 | bf::path subpath; 382 | 383 | bf::path paths[] = {path, path / "cur", path / "new", path / "tmp"}; 384 | 385 | for (uint i = 0; i < 4; i++) 386 | { 387 | if (!bf::exists(paths[i])) { 388 | Log::info << "Creating directory " << paths[i] << Log::endl; 389 | bf::create_directory(paths[i]); 390 | } 391 | else if (!bf::is_directory(paths[i])) { 392 | Log::error << "Directory at " << paths[i] << " already exists and is not a directory." << Log::endl; 393 | throw exception(); 394 | } 395 | } 396 | } 397 | 398 | 399 | 400 | MailDatabase::MailDatabase() 401 | { 402 | } 403 | 404 | MailDatabase::~MailDatabase() 405 | { 406 | for (vector::iterator iter = mailboxes.begin(); iter < mailboxes.end(); iter++) 407 | { 408 | delete (*iter); 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 David Verhasselt 3 | * Licensed under The MIT License - http://gmailcc.crowdway.com/license.txt 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "Client.h" 13 | #include "MailDatabase.h" 14 | #include "MailRecord.h" 15 | #include "LogSink.h" 16 | #include "Options.h" 17 | 18 | 19 | /* 20 | * TODO: A mail could transfer between All Mail and Trash, two different primary boxes. Our app would remove it from the first and then redownload for the second -> improve? 21 | * -> first check spam & trash 22 | * TODO: A lot of error checking, e.g. if you can open the database-file, have write permissions, etc... 23 | * TODO: Check what happens when an external client (i.e. webmail) marks a mail(link) as "read". Is it still found? Is the mark removed when updating? 24 | * TODO: GMail accepts 10 simultaneous connections (MAILSTREAM), try to use them to check multiple boxes at the same time 25 | * -> create a simultaneous connection by calling mail_open() without an opened stream to recycle (i.e. mail_open(NULL, mb, options); ) 26 | * TODO: Add error handling for create_hard_link 27 | * TODO: When implementing the feature to sync back to gmail, check all links of a mail for one that contains "S" for read. If one contains it 28 | change all links to read (add "S") to emulate GMail's Label implementation. One could create a daemon that continually checks 29 | all mails for changes like this to then rename all links. 30 | 31 | Errors: 32 | Logging in...(0 times) 33 | mm_log: %[ALERT] Web login required (Failure) 34 | Logging in...(1 times) 35 | mm_log: %[ALERT] Web login required (Failure) 36 | Logging in...(2 times) 37 | mm_log: %[ALERT] Web login required (Failure) 38 | mm_log: ?Too many login failures 39 | */ 40 | 41 | /* Sinds we gelijk ni veel krijgen als er een error gebeurd, hier een lijstje 42 | * met errors: 43 | * 44 | * mail_uid: returned 0 als stroom dood is 45 | * mail_fetch_envelope NIL als er fout gebeurd is. (PS: fetched "fast" informatie ook) 46 | * mail_fetch_header: "" 47 | * mail_fetch_text: "" 48 | * 49 | * 50 | */ 51 | 52 | void show_version() 53 | { 54 | cout << "Gmail Carbon Copy v0.2.x - released July 2009" << endl; 55 | cout << "http://code.crowdway.com/gmailcc" << endl; 56 | cout << "Copyright (c) 2009 David Verhasselt (david@crowdway.com)" << endl; 57 | cout << "PCB-free" << endl; 58 | cout << endl; 59 | cout << "Compiled at " << __TIMESTAMP__ << " using g++ " << __VERSION__ << endl; 60 | } 61 | 62 | 63 | void finalize(Client& client, MailDatabase& maildb, char* error = NULL) 64 | { 65 | if (error != NULL) 66 | Log::error << "Finalizing with error: " << error << Log::endl; 67 | 68 | client.disconnect(); 69 | maildb.save(); 70 | 71 | if (error != NULL) 72 | abort(); 73 | } 74 | 75 | 76 | int main(int argc, char* argv[]) 77 | { 78 | // Start logger 79 | Log::set_priority(0); 80 | 81 | 82 | // Check configuration 83 | Options options(argc, argv); 84 | 85 | string username; 86 | string password; 87 | string maildir; 88 | 89 | if (options.get_version()) { 90 | show_version(); return 1; 91 | } 92 | 93 | if (options.get_help()) { 94 | options.show_help(); return 1; 95 | } 96 | 97 | username = options.get_username(); 98 | password = options.get_password(); 99 | maildir = options.get_maildir_path(); 100 | 101 | if (options.incomplete()) { 102 | if (isatty(fileno(stdin)) && isatty(fileno(stdout))) // If input and output is a terminal, we can still ask the user for values 103 | { 104 | if (username.empty()) { 105 | cout << "Gmail username: "; 106 | cin >> username; 107 | } 108 | 109 | if (password.empty()) { 110 | char* pass = getpass("Gmail password: "); // Deprecated, find another way 111 | password = pass; 112 | memset(pass, '*', strlen(pass)); // Clear memory 113 | delete pass; 114 | } 115 | 116 | if (maildir.empty()) { 117 | cout << "Target maildir: "; 118 | cin >> maildir; 119 | } 120 | 121 | cout << endl; 122 | } 123 | else 124 | { 125 | options.show_help(); 126 | return 1; 127 | } 128 | } 129 | 130 | // Configure logger 131 | Log::set_priority(options.get_loglevel()); 132 | Log::set_logfile(options.get_logfile().c_str()); 133 | 134 | // Start timer 135 | time_t start_time = time(NULL); 136 | Log::info << "Started @ " << ctime(&start_time) << Log::endl; 137 | 138 | // Variables 139 | Client client; 140 | unsigned long cacheindex = 0; 141 | int cacheinterval = 1000; 142 | char* cachestring = new char[50]; 143 | MailBox* mb; 144 | MailRecord* mr; 145 | ENVELOPE* envelope; 146 | MESSAGECACHE* msgcache; 147 | 148 | // Load local database 149 | MailDatabase* maildb = MailDatabase::load(maildir); 150 | 151 | // Connect to IMAP server 152 | try { 153 | client.connect(username, password); 154 | } catch (InvalidAuthClientException &e) { 155 | Log::critical << "Unable to log-in to server because of invalid credentials. Please check your username and password." << Log::endl; 156 | finalize(client, *maildb); 157 | } catch (WebAuthClientException &e) { 158 | Log::critical << "Unable to log-in to server because Gmail requires a one-time web login. Please use your browser to login once." << Log::endl; 159 | finalize(client, *maildb); 160 | } catch(ClientException &e) { 161 | Log::critical << "Unable to log-in to server because of an unknown reason. Please check your internet connection." << Log::endl; 162 | finalize(client, *maildb); 163 | } 164 | 165 | Log::info << "Connection succesfully established. Log-in succesful." << Log::endl; 166 | 167 | 168 | // Load primary mailboxes 169 | client.open_mailbox("[Gmail]/All Mail"); 170 | mb = maildb->add_mailbox("[Gmail]/All Mail"); 171 | 172 | 173 | bool new_mail; 174 | bool deleted_mail; 175 | bool refresh_uids = client.uid_validity != mb->uid_validity; 176 | 177 | unsigned long uid; 178 | char* body_data; string body; 179 | long unsigned int body_length; 180 | 181 | mb->uid_validity = client.uid_validity; 182 | mb->next_uid = client.uid_next; 183 | mb->messagecount = client.get_mailcount(); 184 | 185 | Log::info << "Mailbox has " << client.get_mailcount() << " messages." << Log::endl; 186 | 187 | for(client.msg_index = 1; client.msg_index <= client.get_mailcount(); client.msg_index++) { 188 | // Check if we need to cache them 189 | if (cacheindex < client.msg_index) 190 | { 191 | sprintf(cachestring, "%lu:%lu", cacheindex+1, cacheindex+cacheinterval); 192 | Log::info << "Caching status of mails " << cacheindex+1 << " to " << min(cacheindex+cacheinterval, client.get_mailcount()) << "." << Log::endl; 193 | 194 | cacheindex = min(cacheindex+cacheinterval, client.get_mailcount()); 195 | 196 | mail_gc (client.stream, GC_ELT); // Garbage collect the old ones 197 | mail_fetchfast(client.stream, cachestring); // We'll need to check the flags either way 198 | } 199 | 200 | Log::info << "Mail " << client.msg_index << " of " << client.get_mailcount(); 201 | 202 | mr = NULL; 203 | 204 | uid = mail_uid(client.stream, client.msg_index); 205 | if (uid == 0) finalize(client, *maildb, "(new mail) mail_uid is 0"); // Connection broken 206 | 207 | if (!refresh_uids) { 208 | 209 | mr = maildb->get_mail(uid); // Try to load the email from our local database using the UID (long) 210 | } 211 | 212 | if (mr == NULL) { // If UID is invalid or it's a new mail 213 | Log::info << ", uid unknown"; 214 | envelope = mail_fetchenvelope(client.stream, client.msg_index); // First fetch the mail metadata 215 | 216 | if (envelope == NIL || client.fatal_error) finalize(client, *maildb, "Envelope is NIL"); // Connection broken 217 | 218 | if ((envelope->message_id == NULL) || (envelope->message_id[0] == '\0')) { 219 | Log::error << ", mail has empty message-id. Ignore." << Log::endl; 220 | continue; 221 | } 222 | 223 | mr = maildb->get_mail(envelope->message_id); // Again, try to load the email from our local database, this time using the message ID (string) 224 | 225 | if (mr == NULL) // New mail 226 | { 227 | Log::info << ", message id unknown. Download." << Log::endl; 228 | 229 | body.clear(); 230 | body_data = mail_fetchbody_full(client.stream, client.msg_index, "", &body_length, FT_PEEK | FT_INTERNAL); 231 | body.append(body_data, body_length); // Convert to string using the content_length because of possible 232 | // binary data inside (and thus also /0 characters. 233 | if (client.fatal_error) finalize(client, *maildb, "Fatal Error while downloading body."); 234 | if (!body.size()) Log::info << " -- An empty mail, how quaint." << Log::endl; 235 | 236 | mr = maildb->new_mail(envelope->message_id, uid, body); 237 | } 238 | else // Existing mail 239 | { 240 | Log::info << ", message id known. "; 241 | 242 | if (refresh_uids) 243 | { 244 | Log::info << "Set uid." << Log::endl; 245 | mr = maildb->new_mail(envelope->message_id, mb, uid); // Just update the UID 246 | } 247 | else 248 | { 249 | Log::info << "Duplicate: ignore." << Log::endl; 250 | } 251 | 252 | } 253 | } 254 | else 255 | { 256 | Log::info << ", uid known." << Log::endl; 257 | } 258 | 259 | 260 | mail_gc (client.stream, GC_TEXTS); // TODO: Maybe move this to the gc envelopes statement to also 261 | // execute periodically? 262 | 263 | 264 | // Garbage collect envelopes 265 | if (!(client.msg_index % 20)) { 266 | // Log::info << "Collect garbage (envelopes)" << Log::endl; 267 | mail_gc (client.stream, GC_ENV); } 268 | 269 | msgcache = mail_elt(client.stream, client.msg_index); 270 | 271 | if (msgcache == NIL || client.fatal_error) finalize(client, *maildb, "mail_elt returned NIL"); // Connection broken 272 | 273 | mr->set_flag_draft(msgcache->draft == 1); 274 | mr->set_flag_flagged(msgcache->flagged == 1); 275 | mr->set_flag_passed(msgcache->recent == 1); // Not sure of this mapping here 276 | mr->set_flag_replied(msgcache->answered == 1); 277 | mr->set_flag_seen(msgcache->seen == 1); 278 | mr->set_flag_trashed(msgcache->deleted == 1); 279 | 280 | mr->sync_flags(); 281 | 282 | // Set touched to true to signify this mail shouldn't be deleted 283 | mr->mark(); 284 | } 285 | 286 | mail_gc (client.stream, GC_ELT); 287 | 288 | // Also check trash & spam here 289 | 290 | maildb->sweep(); 291 | 292 | 293 | // Load secondary mails 294 | client.get_mailboxen(); 295 | for (vector::iterator it = client.mailboxlist.begin(); it < client.mailboxlist.end(); it++) 296 | { 297 | Log::info << "Checking mailbox " << *it << "..." << Log::endl; 298 | 299 | if (it->compare("[Gmail]/All Mail") == 0) 300 | continue; 301 | 302 | if (it->compare("[Gmail]/Trash") == 0) 303 | continue; 304 | 305 | mb = maildb->add_mailbox(*it); 306 | client.open_mailbox(mb->name); 307 | 308 | new_mail = client.uid_next != mb->next_uid; 309 | deleted_mail = new_mail || (client.get_mailcount() < mb->messagecount); // If there's new mail, there's no way of knowing presearch if there are any deleted messages. 310 | refresh_uids = client.uid_validity != mb->uid_validity; 311 | 312 | Log::info << "This mailbox has possibly "; 313 | if (!new_mail) Log::info << "no "; Log::info << "new mail, "; 314 | if (!deleted_mail) Log::info << "no "; Log::info << "deleted mail, and "; 315 | if (!refresh_uids) Log::info << "no "; Log::info << "refreshed UID's" << Log::endl; 316 | 317 | mb->uid_validity = client.uid_validity; 318 | mb->next_uid = client.uid_next; 319 | mb->messagecount = client.get_mailcount(); 320 | 321 | Log::info << "Checking " << client.get_mailcount() << " messages." << Log::endl; 322 | 323 | 324 | if (new_mail || deleted_mail || refresh_uids) 325 | { 326 | for(client.msg_index = 1; client.msg_index <= client.get_mailcount(); client.msg_index++) 327 | { 328 | mr = NULL; 329 | 330 | if (!refresh_uids) 331 | { 332 | mr = maildb->get_mail(mb, mail_uid(client.stream, client.msg_index)); 333 | } 334 | 335 | if (mr == NULL) // If UID is invalid or it's a new mail 336 | { 337 | envelope = mail_fetchenvelope(client.stream, client.msg_index); 338 | mr = maildb->get_mail(envelope->message_id); 339 | if (mr != NULL) // mr should never be NULL, except if something happened between primary and secondary update 340 | mr = maildb->new_mail(envelope->message_id, mb, mail_uid(client.stream, client.msg_index)); // Just updates UID 341 | else 342 | continue; 343 | } 344 | 345 | // Set touched to true to signify this link shouldn't be deleted 346 | if (mr && deleted_mail) 347 | mr->find_link(mb)->mark(); 348 | } 349 | 350 | if (deleted_mail) 351 | { 352 | mb->sweep(true); 353 | } 354 | 355 | mail_gc (client.stream,GC_ELT | GC_ENV | GC_TEXTS); 356 | 357 | } 358 | 359 | else 360 | Log::info << "Nothing to be done." << Log::endl; 361 | } 362 | 363 | Log::info << "End of message checking." << Log::endl; 364 | Log::info << "Succesfully completed in " << (time(NULL) - start_time) << " seconds." << Log::endl; 365 | 366 | finalize(client, *maildb); 367 | 368 | } 369 | --------------------------------------------------------------------------------