diff --git a/include/common/LOAuth2.h b/include/common/LOAuth2.h --- a/include/common/LOAuth2.h +++ b/include/common/LOAuth2.h @@ -1,64 +1,64 @@ #ifndef _LOAUTH2_H_ #define _LOAUTH2_H_ ////////////////////////////////////////////////////////////////// -/* Do this elsewhere? +/* Do this somewhere? GAutoString ErrorMsg; StartSSL(ErrorMsg, NULL); */ class LOAuth2 { struct LOAuth2Priv *d; public: struct Params { enum ServiceProvider { None, OAuthGoogle, OAuthMicrosoft, } Provider; GString ClientID; GString ClientSecret; GString RedirURIs; GString AuthUri; GString ApiUri; // GString RevokeUri; GString Scope; GUri Proxy; // GString AccessToken; // GString RefreshToken; // int ExpiresIn; Params() { Provider = None; // ExpiresIn = 0; } bool IsValid() { return Provider != None && ClientID && ClientSecret && RedirURIs && AuthUri && // RevokeUri && Scope && ApiUri; } }; - LOAuth2(Params ¶ms, const char *account, GStream *log = NULL); + LOAuth2(Params ¶ms, const char *account, GDom *store, GStream *log = NULL); virtual ~LOAuth2(); GString GetAccessToken(); }; #endif \ No newline at end of file diff --git a/include/common/Mail.h b/include/common/Mail.h --- a/include/common/Mail.h +++ b/include/common/Mail.h @@ -1,947 +1,951 @@ /** \file \author Matthew Allen */ #ifndef __MAIL_H #define __MAIL_H #include "INet.h" #include "Base64.h" #include "Progress.h" #include "GVariant.h" #include "LOAuth2.h" #ifndef GPL_COMPATIBLE #define GPL_COMPATIBLE 0 #endif // Defines #define MAX_LINE_SIZE 1024 #define MAX_NAME_SIZE 64 #define EMAIL_LINE_SIZE 76 // #define IsDigit(c) ((c) >= '0' AND (c) <= '9') // MIME content types #define CONTENT_NONE 0 #define CONTENT_BASE64 1 #define CONTENT_QUOTED_PRINTABLE 2 #define CONTENT_OCTET_STREAM 3 // Mail logging defines #define MAIL_SEND_COLOUR Rgb24(0, 0, 0xff) #define MAIL_RECEIVE_COLOUR Rgb24(0, 0x8f, 0) #define MAIL_ERROR_COLOUR Rgb24(0xff, 0, 0) #define MAIL_WARNING_COLOUR Rgb24(0xff, 0x7f, 0) #define MAIL_INFO_COLOUR Rgb24(0, 0, 0) // Helper functions extern void TokeniseStrList(char *Str, List &Output, const char *Delim); extern char ConvHexToBin(char c); #define ConvBinToHex(i) (((i)<10)?'0'+(i):'A'+(i)-10) extern void DecodeAddrName(const char *Start, GAutoString &Name, GAutoString &Addr, const char *DefaultDomain); extern char *DecodeRfc2047(char *Str); extern char *EncodeRfc2047(char *Str, const char *CodePage, List *CharsetPrefs, ssize_t LineLength = 0); extern char *DecodeBase64Str(char *Str, int Len = -1); extern char *DecodeQuotedPrintableStr(char *Str, ssize_t Len = -1); extern bool Is8Bit(char *Text); extern int MaxLineLen(char *Text); extern char *EncodeImapString(const char *s); extern char *DecodeImapString(const char *s); +extern bool UnBase64Str(GString &s); +extern bool Base64Str(GString &s); extern const char *sTextPlain; extern const char *sTextHtml; extern const char *sTextXml; extern const char *sApplicationInternetExplorer; extern const char sMultipartMixed[]; extern const char sMultipartEncrypted[]; extern const char sMultipartSigned[]; extern const char sMultipartAlternative[]; extern const char sMultipartRelated[]; extern const char sAppOctetStream[]; // Classes class MailProtocol; struct MailProtocolError { int Code; GString ErrMsg; MailProtocolError() { Code = 0; } }; class MailProtocolProgress { public: uint64 Start; int Value; ssize_t Range; MailProtocolProgress() { Empty(); } void Empty() { Start = 0; Value = 0; Range = 0; } void StartTransfer(ssize_t Size) { Start = LgiCurrentTime(); Value = 0; Range = Size; } }; class LogEntry { GColour c; public: GArray Txt; LogEntry(GColour col); GColour GetColour() { return c; } bool Add(const char *t, ssize_t len = -1); }; /// Attachment descriptor class FileDescriptor : public GBase { protected: // Global int64 Size; char *MimeType; char *ContentId; // Read from file GFile File; GStreamI *Embeded; bool OwnEmbeded; int64 Offset; LMutex *Lock; // Write to memory uchar *Data; GAutoPtr DataStream; public: FileDescriptor(GStreamI *embed, int64 Offset, int64 Size, char *Name); FileDescriptor(char *name); FileDescriptor(char *data, int64 len); FileDescriptor(); ~FileDescriptor(); void SetLock(LMutex *l); LMutex *GetLock(); void SetOwnEmbeded(bool i); // Access functions GStreamI *GotoObject(); // Get data to read uchar *GetData(); // Get data from write int Sizeof(); char *GetMimeType() { return MimeType; } void SetMimeType(char *s) { DeleteArray(MimeType); MimeType = NewStr(s); } char *GetContentId() { return ContentId; } void SetContentId(char *s) { DeleteArray(ContentId); ContentId = NewStr(s); } // Decode MIME data to memory bool Decode(char *ContentType, char *ContentTransferEncoding, char *MimeData, int MimeDataLength); }; /// Address dscriptor class AddressDescriptor : public GBase { public: uint8_t Status; uchar CC; // MAIL_ADDR_?? char *Name; char *Addr; void *Data; // application defined AddressDescriptor(AddressDescriptor *Copy = 0); ~AddressDescriptor(); void _Delete(); void Print(char *Str, int Len); ssize_t Sizeof() { return SizeofStr(Name) + SizeofStr(Addr); } bool Serialize(GFile &f, bool Write) { bool Status = true; if (Write) { WriteStr(f, Name); WriteStr(f, Addr); } else { DeleteArray(Name); Name = ReadStr(f PassDebugArgs); DeleteArray(Addr); Addr = ReadStr(f PassDebugArgs); } return Status; } }; /* class MailMessage : public GStream { char* Text; char* TextCharset; char* Html; char* HtmlCharset; public: List To; AddressDescriptor *From; AddressDescriptor *Reply; GAutoString Subject; GAutoString MessageID; GAutoString FwdMsgId; GAutoString BounceMsgId; List FileDesc; char* InternetHeader; char Priority; int MarkColour; uint8 DispositionNotificationTo : 1; // read receipt uint8 EncryptedMsg : 1; GAutoString References; // Protocol specific GAutoString UserData; // Class MailMessage(); virtual ~MailMessage(); void Empty(); virtual char *GetBody(); virtual bool SetBody(const char *Txt, int Bytes = -1, bool Copy = true, const char *Cs = 0); virtual char *GetBodyCharset(); virtual bool SetBodyCharset(const char *Cs); virtual char *GetHtml(); virtual bool SetHtml(const char *Txt, int Bytes = -1, bool Copy = true, const char *Cs = 0); virtual char *GetHtmlCharset(); virtual bool SetHtmlCharset(const char *Cs); // Logging GStream *Log; int Write(const void *Ptr, int Size, int Flags = 0); // Conversion to/from MIME GStringPipe *Raw; // High level encoding functions bool Encode (GStreamI &Out, GStream *HeadersSink, MailProtocol *Protocol, bool Mime = true); bool EncodeHeaders (GStreamI &Out, MailProtocol *Protocol, bool Mime = true); bool EncodeBody (GStreamI &Out, MailProtocol *Protocol, bool Mime = true); // Encoding mime segment data int EncodeText (GStreamI &Out, GStreamI &In); int EncodeQuotedPrintable (GStreamI &Out, GStreamI &In); int EncodeBase64 (GStreamI &Out, GStreamI &In); }; */ /// Base class for mail protocol implementations class MailProtocol { protected: char Buffer[4<<10]; LMutex SocketLock; GAutoPtr Socket; LOAuth2::Params OAuth2; + GDom *SettingStore; bool Error(const char *file, int line, const char *msg, ...); bool Read(); bool Write(const char *Buf = NULL, bool Log = false); virtual void OnUserMessage(char *Str) {} public: // Logging GStreamI *Logger; void Log(const char *Str, GSocketI::SocketMsgType type); // Task Progress MailProtocolProgress *Items; MailProtocolProgress *Transfer; // Settings int ErrMsgId; /// \sa #L_ERROR_ESMTP_NO_AUTHS, #L_ERROR_ESMTP_UNSUPPORTED_AUTHS GString ErrMsgFmt; /// The format for the printf GString ErrMsgParam; /// The arguments for the printf GString ProgramName; GString ExtraOutgoingHeaders; List CharsetPrefs; // Object MailProtocol(); virtual ~MailProtocol(); // Methods void SetOAuthParams(LOAuth2::Params &p) { OAuth2 = p; } + void SetSettingStore(GDom *store) { SettingStore = store; } /// Thread safe hard close (quit now) bool CloseSocket() { LMutex::Auto l(&SocketLock, _FL); if (Socket != NULL) return Socket->Close() != 0; return false; } void SetError(int ResourceId, const char *Fmt, const char *Param = NULL) { ErrMsgId = ResourceId; ErrMsgFmt = Fmt; ErrMsgParam = Param; } }; ///////////////////////////////////////////////////////////////////// // Mail IO parent classes /// Enable STARTTLS support (requires an SSL capable socket) #define MAIL_USE_STARTTLS 0x01 /// Use authentication #define MAIL_USE_AUTH 0x02 /// Force the use of PLAIN type authentication #define MAIL_USE_PLAIN 0x04 /// Force the use of LOGIN type authentication #define MAIL_USE_LOGIN 0x08 /// Force the use of NTLM type authentication #define MAIL_USE_NTLM 0x10 /// Secure auth #define MAIL_SECURE_AUTH 0x20 /// Use SSL #define MAIL_SSL 0x40 /// OAUTH2 #define MAIL_USE_OAUTH2 0x80 /// CRAM-MD5 #define MAIL_USE_CRAM_MD5 0x100 /// Mail sending protocol class MailSink : public MailProtocol { public: /// Connection setup/shutdown virtual bool Open ( /// The transport layer to use GSocketI *S, /// The host to connect to const char *RemoteHost, /// The local domain const char *LocalDomain, /// The sink username (or NULL) const char *UserName, /// The sink password (or NULL) const char *Password, /// The port to connect with or 0 for default. int Port, /// Options: Use any of #MAIL_SSL, #MAIL_USE_STARTTLS, #MAIL_SECURE_AUTH, #MAIL_USE_PLAIN, #MAIL_USE_LOGIN etc or'd together. int Flags ) = 0; /// Close the connection virtual bool Close() = 0; // Commands available while connected /// Write the email's contents into the GStringPipe returned from /// SendStart and then call SendEnd to finish the transaction virtual GStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0) = 0; /// Finishes the mail send virtual bool SendEnd(GStringPipe *Sink) = 0; }; struct ImapMailFlags { union { struct { uint8_t ImapAnswered : 1; uint8_t ImapDeleted : 1; uint8_t ImapDraft : 1; uint8_t ImapFlagged : 1; uint8_t ImapRecent : 1; uint8_t ImapSeen : 1; uint8_t ImapExpunged :1; }; uint16 All; }; ImapMailFlags(char *init = 0) { ImapAnswered = 0; ImapDeleted = 0; ImapDraft = 0; ImapFlagged = 0; ImapRecent = 0; ImapSeen = 0; ImapExpunged = 0; if (init) Set(init); } char *Get() { char s[256] = ""; int ch = 0; if (ImapAnswered) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\answered "); if (ImapDeleted) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\deleted "); if (ImapDraft) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\draft "); if (ImapFlagged) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\flagged "); if (ImapRecent) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\recent "); if (ImapSeen) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\seen "); if (ch == 0) return NULL; LgiAssert(ch < sizeof(s)); s[--ch] = 0; return NewStr(s); } void Set(const char *s) { All = 0; if (!s) s = ""; while (*s) { if (*s == '/' || *s == '\\') { while (*s == '/' || *s == '\\') s++; const char *e = s; while (*e && isalpha(*e)) e++; if (!_strnicmp(s, "answered", e-s)) ImapAnswered = true; else if (!_strnicmp(s, "deleted", e-s)) ImapDeleted = true; else if (!_strnicmp(s, "draft", e-s)) ImapDraft = true; else if (!_strnicmp(s, "flagged", e-s)) ImapFlagged = true; else if (!_strnicmp(s, "recent", e-s)) ImapRecent = true; else if (!_strnicmp(s, "seen", e-s)) ImapSeen = true; s = e; } else s++; } } bool operator ==(ImapMailFlags &f) { return ImapAnswered == f.ImapAnswered && ImapDeleted == f.ImapDeleted && ImapDraft == f.ImapDraft && ImapFlagged == f.ImapFlagged && ImapRecent == f.ImapRecent && ImapSeen == f.ImapSeen && ImapExpunged == f.ImapExpunged; } bool operator !=(ImapMailFlags &f) { return !(ImapAnswered == f.ImapAnswered && ImapDeleted == f.ImapDeleted && ImapDraft == f.ImapDraft && ImapFlagged == f.ImapFlagged && ImapRecent == f.ImapRecent && ImapSeen == f.ImapSeen && ImapExpunged == f.ImapExpunged); } }; /// A bulk mail handling class class MailTransaction { public: /// The index of the mail in the folder int Index; /// \sa #MAIL_POSTED_TO_GUI, #MAIL_EXPLICIT int Flags; // bool Delete; bool Status; bool Oversize; /// The mail protocol handler writes the email to this stream GStreamI *Stream; /// Flags used on the IMAP protocolf ImapMailFlags Imap; /// The user app can use this for whatever void *UserData; MailTransaction(); ~MailTransaction(); }; /// Return code from MailSrcCallback enum MailSrcStatus { /// Download the whole email DownloadAll, /// Download just the top part DownloadTop, /// Skip this email DownloadNone, /// About the whole receive DownloadAbort }; /// The callback function used by MailSource::Receive typedef MailSrcStatus (*MailSrcCallback) ( /// The currently executing transaction MailTransaction *Trans, /// The size of the email about to be downloaded uint64 Size, /// If DownloadTop is returned, you can set the number of lines to retreive here int *LinesToDownload, /// The data cookie passed into MailSource::Receive void *Data ); /// The callback function used by MailSource::Receive typedef bool (*MailReceivedCallback) ( /// The currently executing transaction MailTransaction *Trans, /// The data cookie passed into MailSource::Receive void *Data ); /// Collection of callbacks called during mail receive. You should zero this /// entire object before using it. Because if someone adds new callbacks after /// you write the calling code you wouldn't want to leave some callbacks un- /// initialized. A NULL callback is ignored. struct MailCallbacks { /// The callback data void *CallbackData; /// Called before receiving mail MailSrcCallback OnSrc; /// Called after mail received MailReceivedCallback OnReceive; }; /* /// Enable STARTTLS support (requires an SSL capable socket) #define MAIL_SOURCE_STARTTLS 0x01 /// Use authentication #define MAIL_SOURCE_AUTH 0x02 /// Force the use of PLAIN type authentication #define MAIL_SOURCE_USE_PLAIN 0x04 /// Force the use of LOGIN type authentication #define MAIL_SOURCE_USE_LOGIN 0x08 */ /// A generic mail source object class MailSource : public MailProtocol { public: /// Opens a connection to the server virtual bool Open ( /// The transport socket GSocketI *S, /// The hostname or IP of the server const char *RemoteHost, /// The port on the host to connect to int Port, /// The username for authentication const char *User, /// The password for authentication const char *Password, /// [Optional] Persistant storage of settings GDom *SettingStore, /// [Optional] Flags: #MAIL_SOURCE_STARTTLS, #MAIL_SOURCE_AUTH, #MAIL_SOURCE_USE_PLAIN, #MAIL_SOURCE_USE_LOGIN int Flags = 0) = 0; /// Closes the connection virtual bool Close() = 0; /// Returns the number of messages available on the server virtual int GetMessages() = 0; /// Receives a list of messages from the server. virtual bool Receive ( /// An array of messages to receive. The MailTransaction objects contains the index of the message to receive /// and various status values returned after the operation. GArray &Trans, /// An optional set of callback functions. MailCallbacks *Callbacks = 0 ) = 0; /// Deletes a message on the server virtual bool Delete(int Message) = 0; /// Gets the size of the message on the server virtual int Sizeof(int Message) = 0; /// Gets the size of all the messages on the server virtual bool GetSizes(GArray &Sizes) { return false; } /// Gets the unique identifier of the message virtual bool GetUid(int Message, char *Id, int IdLen) = 0; /// Gets the unique identifiers of a list of messages virtual bool GetUidList(List &Id) = 0; /// Gets the headers associated with a given message virtual char *GetHeaders(int Message) = 0; /// Sets the proxy server. e.g. HTTP mail. virtual void SetProxy(char *Server, int Port) {} }; ///////////////////////////////////////////////////////////////////// // Mail IO implementations /// SMTP implementation class MailSmtp : public MailSink { protected: bool ReadReply(const char *Str, GStringPipe *Pipe = 0, MailProtocolError *Err = 0); bool WriteText(const char *Str); public: MailSmtp(); ~MailSmtp(); bool Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0); bool Close(); bool SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); GStringPipe *SendData(MailProtocolError *Err = 0); GStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); bool SendEnd(GStringPipe *Sink); // bool Send(MailMessage *Msg, bool Mime = false); }; class MailSendFolder : public MailSink { class MailPostFolderPrivate *d; public: MailSendFolder(char *Path); ~MailSendFolder(); bool Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0); bool Close(); GStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); bool SendEnd(GStringPipe *Sink); }; class MailPop3 : public MailSource { protected: bool ReadReply(); bool ReadMultiLineReply(char *&Str); int GetInt(); bool MailIsEnd(char *Ptr, ssize_t Len); bool ListCmd(const char *Cmd, LHashTbl, bool> &Results); const char *End; const char *Marker; int Messages; public: MailPop3(); ~MailPop3(); // Connection bool Open(GSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *SettingStore, int Flags = 0); bool Close(); // Commands available while connected int GetMessages(); bool Receive(GArray &Trans, MailCallbacks *Callbacks = 0); bool Delete(int Message); int Sizeof(int Message); bool GetSizes(GArray &Sizes); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(List &Id); char *GetHeaders(int Message); }; class MailReceiveFolder : public MailSource { protected: class MailReceiveFolderPrivate *d; public: MailReceiveFolder(char *Path); ~MailReceiveFolder(); // Connection bool Open(GSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *SettingStore, int Flags = 0); bool Close(); // Commands available while connected int GetMessages(); bool Receive(GArray &Trans, MailCallbacks *Callbacks = 0); bool Delete(int Message); int Sizeof(int Message); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(List &Id); char *GetHeaders(int Message); }; class MailPhp : public MailSource { protected: class MailPhpPrivate *d; bool Get(GSocketI *S, char *Uri, GStream &Out, bool ChopDot); public: MailPhp(); ~MailPhp(); // Connection bool Open(GSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *SettingStore, int Flags = 0); bool Close(); // Commands available while connected int GetMessages(); bool Receive(GArray &Trans, MailCallbacks *Callbacks = 0); bool Delete(int Message); int Sizeof(int Message); bool GetSizes(GArray &Sizes); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(List &Id); char *GetHeaders(int Message); void SetProxy(char *Server, int Port); }; class MailImapFolder { friend class MailIMap; friend struct ImapThreadPrivate; char Sep; char *Path; public: bool NoSelect; bool NoInferiors; bool Marked; int Exists; int Recent; int Deleted; // int UnseenIndex; MailImapFolder(); virtual ~MailImapFolder(); char *GetPath(); void SetPath(const char *s); char *GetName(); void SetName(const char *s); char GetSep() { return Sep; } void operator =(LHashTbl,int> &v); }; class MailIMap : public MailSource { protected: class MailIMapPrivate *d; char Buf[2048]; List Uid; GStringPipe ReadBuf; List Dialog; void ClearDialog(); void ClearUid(); bool FillUidList(); bool WriteBuf(bool ObsurePass = false, const char *Buffer = 0, bool Continuation = false); bool ReadResponse(int Cmd = -1, bool Plus = false); bool Read(GStreamI *Out = 0, int Timeout = -1); bool ReadLine(); bool IsResponse(const char *Buf, int Cmd, bool &Ok); void CommandFinished(); public: typedef LHashTbl,GString> StrMap; struct StrRange { ssize_t Start, End; void Set(ssize_t s, ssize_t e) { Start = s; End = e; } ssize_t Len() { return End - Start; } }; // Typedefs struct Untagged { GString Cmd; GString Param; int Id; }; /// This callback is used to notify the application using this object of IMAP fetch responses. /// \returns true if the application wants to continue reading and has taken ownership of the strings in "Parts". typedef bool (*FetchCallback) ( /// The IMAP object class MailIMap *Imap, /// The message sequence number char *Msg, /// The fetch parts (which the callee needs to own if returning true) StrMap &Parts, /// The user data passed to the Fetch function void *UserData ); // Object MailIMap(); ~MailIMap(); // Mutex bool Lock(const char *file, int line); bool LockWithTimeout(int Timeout, const char *file, int line); void Unlock(); // General char GetFolderSep(); char *EncodePath(const char *Path); char *GetCurrentPath(); bool GetExpungeOnExit(); void SetExpungeOnExit(bool b); bool ServerOption(char *Opt); bool IsOnline(); const char *GetWebLoginUri(); void SetParentWindow(GViewI *wnd); void SetCancel(LCancel *Cancel); ssize_t ParseImapResponse(char *Buffer, ssize_t BufferLen, GArray &Ranges, int Names); // Connection bool Open(GSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *SettingStore, int Flags = 0); bool Close(); // Non-threadsafe soft close (normal operation) bool GetCapabilities(GArray &s); // Commands available while connected bool Receive(GArray &Trans, MailCallbacks *Callbacks = 0); int GetMessages(); bool Delete(int Message); bool Delete(bool ByUid, const char *Seq); int Sizeof(int Message); bool GetSizes(GArray &Sizes); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(List &Id); char *GetHeaders(int Message); char *SequenceToString(GArray *Seq); // Imap specific commands /// This method wraps the imap FETCH command /// \returns the number of records accepted by the callback fn int Fetch ( /// True if 'Seq' is a UID, otherwise it's a sequence bool ByUid, /// The sequence number or UID const char *Seq, /// The parts to retrieve const char *Parts, /// Data is returned to the caller via this callback function FetchCallback Callback, /// A user defined param to pass back to the 'Callback' function. void *UserData, /// [Optional] The raw data received will be written to this stream if provided, else NULL. GStreamI *RawCopy = 0, /// [Optional] The rough size of the fetch... used to pre-allocate a buffer to receive data. int64 SizeHint = -1 ); /// Appends a message to the specified folder bool Append ( /// The folder to write to const char *Folder, /// [Optional] Flags for the message ImapMailFlags *Flags, /// The rfc822 body of the message const char *Msg, /// [Out] The UID of the message appended (if known, can be empty if not known) GString &NewUid ); bool GetFolders(GArray &Folders); bool SelectFolder(const char *Path, StrMap *Values = 0); char *GetSelectedFolder(); int GetMessages(const char *Path); bool CreateFolder(MailImapFolder *f); bool DeleteFolder(const char *Path); bool RenameFolder(const char *From, const char *To); bool SetFolderFlags(MailImapFolder *f); /// Expunges (final delete) any deleted messages the current folder. bool ExpungeFolder(); // Uid methods bool CopyByUid(GArray &InUids, const char *DestFolder); bool SetFlagsByUid(GArray &Uids, const char *Flags); /// Idle processing... /// \returns true if something happened bool StartIdle(); bool OnIdle(int Timeout, GArray &Resp); bool FinishIdle(); bool Poll(int *Recent = 0, GArray *New = 0); bool Status(char *Path, int *Recent); bool Search(bool Uids, GArray &SeqNumbers, const char *Filter); // Utility static bool Http(GSocketI *S, GAutoString *OutHeaders, GAutoString *OutBody, int *StatusCode, const char *InMethod, const char *InUri, const char *InHeaders, const char *InBody); }; #endif diff --git a/src/common/INet/LOAuth2.cpp b/src/common/INet/LOAuth2.cpp --- a/src/common/INet/LOAuth2.cpp +++ b/src/common/INet/LOAuth2.cpp @@ -1,398 +1,429 @@ #include "Lgi.h" #include "resdefs.h" #include "GTextLog.h" #include "OpenSSLSocket.h" #include "Base64.h" #include "INetTools.h" #include "LOAuth2.h" #include "LJSon.h" ////////////////////////////////////////////////////////////////// #define LOCALHOST_PORT 54900 - -/* -const char *ClientID = ""; -const char *ClientSecret = ""; -const char *Api = "https://apidata.googleusercontent.com/caldav/v2/%s/user"; -const char *Scope = "https://www.googleapis.com/auth/calendar"; -const char *AuthServer = "https://accounts.google.com/o/oauth2/v2/auth"; -*/ +#define OPT_AccessToken "AccessToken" +#define OPT_RefreshToken "RefreshToken" static GString GetHeaders(GSocketI *s) { char Buf[256]; ssize_t Rd; GString p; while ((Rd = s->Read(Buf, sizeof(Buf))) > 0) { p += GString(Buf, Rd); if (p.Find("\r\n\r\n") >= 0) return p; } s->Close(); return NULL; } ssize_t ChunkSize(ssize_t &Pos, GString &Buf, GString &Body) { static GString Eol("\r\n"); auto End = Buf.Find(Eol, Pos); if (End > Pos) { auto Sz = Buf(Pos, End).Int(16); if (Sz >= 0) { End += Eol.Length(); auto Bytes = End + Sz + Eol.Length(); if (Buf.Length() >= Bytes) { Body += Buf(End, End + Sz); Pos = End + Sz + Eol.Length(); return Sz; } } } return -1; } static bool GetHttp(GSocketI *s, GString &Hdrs, GString &Body, bool IsResponse) { GString Resp = GetHeaders(s); char Buf[512]; ssize_t Rd; auto BodyPos = Resp.Find("\r\n\r\n"); GAutoString Len(InetGetHeaderField(Resp, "Content-Length", BodyPos)); if (Len) { int Bytes = atoi(Len); size_t Total = BodyPos + 4 + Bytes; while (Resp.Length() < Total) { Rd = s->Read(Buf, sizeof(Buf)); if (Rd > 0) { Resp += GString(Buf, Rd); } } } else if (s->IsOpen() && IsResponse) { GAutoString Te(InetGetHeaderField(Resp, "Transfer-Encoding", BodyPos)); bool Chunked = Te && !_stricmp(Te, "chunked"); if (Chunked) { ssize_t Pos = 0; Hdrs = Resp(0, BodyPos); GString Raw = Resp(BodyPos + 4, -1); Body.Empty(); while (s->IsOpen()) { auto Sz = ChunkSize(Pos, Raw, Body); if (Sz == 0) break; if (Sz < 0) { Rd = s->Read(Buf, sizeof(Buf)); if (Rd > 0) Raw += GString(Buf, Rd); else break; } } return true; } else { while ((Rd = s->Read(Buf, sizeof(Buf))) > 0) Resp += GString(Buf, Rd); } } Hdrs = Resp(0, BodyPos); Body = Resp(BodyPos + 4, -1); return true; } static GString UrlFromHeaders(GString Hdrs) { auto Lines = Hdrs.Split("\r\n", 1); auto p = Lines[0].SplitDelimit(); if (p.Length() < 3) { return NULL; } return p[1]; } static bool Write(GSocketI *s, GString b) { for (size_t i = 0; i < b.Length(); ) { auto Wr = s->Write(b.Get() + i, b.Length() - i); if (Wr <= 0) return false; i += Wr; } return true; } static GString FormEncode(const char *s, bool InValue = true) { GStringPipe p; for (auto c = s; *c; c++) { if (isalpha(*c) || isdigit(*c) || *c == '_' || *c == '.' || (!InValue && *c == '+') || *c == '-' || *c == '%') { p.Write(c, 1); } else if (*c == ' ') { p.Write((char*)"+", 1); } else { p.Print("%%%02.2X", *c); } } return p.NewGStr(); } struct LOAuth2Priv : public LCancel { LOAuth2::Params Params; GString Id; GStream *Log; GString Token; GString CodeVerifier; GStringPipe LocalLog; + GDom *Store; GString AccessToken, RefreshToken; int64 ExpiresIn; struct Server : public GSocket { GSocket Listen; LOAuth2Priv *d; GSocket s; public: LHashTbl,GString> Params; GString Body; Server(LOAuth2Priv *cd) : d(cd) { while (!Listen.Listen(LOCALHOST_PORT)) { if (d->IsCancelled()) break; d->Log->Print("Error: Can't listen on %i...\n", LOCALHOST_PORT); LgiSleep(1000); } } bool GetReq() { while (!d->IsCancelled()) { if (Listen.IsReadable(100)) { if (Listen.Accept(&s)) { // Read access code out of response GString Hdrs; if (GetHttp(&s, Hdrs, Body, false)) { auto Url = UrlFromHeaders(Hdrs); auto Vars = Url.Split("?", 1); if (Vars.Length() != 2) { return false; } Vars = Vars[1].Split("&"); for (auto v : Vars) { auto p = v.Split("=", 1); if (p.Length() != 2) continue; Params.Add(p[0], p[1]); } return true; } } } } return false; } bool Response(const char *Txt) { GString Msg; Msg.Printf("HTTP/1.0 200 OK\r\n" "\r\n" "\n" "%s\n" "", Txt); return ::Write(&s, Msg); } }; GString Base64(GString s) { GString b; b.Length(BufferLen_BinTo64(s.Length())); auto ch = ConvertBinaryToBase64(b.Get(), b.Length(), (uchar*)s.Get(), s.Length()); + b.Get()[b.Length()] = 0; return b; } GString ToText(GString Bin) { GArray t; for (char i='0'; i<='9'; i++) t.Add(i); for (char i='a'; i<='z'; i++) t.Add(i); for (char i='A'; i<='Z'; i++) t.Add(i); t.Add('-'); t.Add('.'); t.Add('_'); t.Add('~'); GString Txt; Txt.Length(Bin.Length()); int Pos = 0; for (int i=0; iPrint("Error: Can't connect to '%s:%i'\n", u.Host, HTTPS_PORT); - return NULL; - } + GStringPipe p(1024); + GUri u(Params.Scope); + SslSocket sock(NULL, NULL, true); + if (!sock.Open(u.Host, HTTPS_PORT)) + { + Log->Print("Error: Can't connect to '%s:%i'\n", u.Host, HTTPS_PORT); + return NULL; + } - GString Body, Http; - Body.Printf("code=%s&" - "client_id=%s&" - "client_secret=%s&" - "redirect_uri=http://localhost:%i&" - "code_verifier=%s&" - "grant_type=authorization_code", - FormEncode(Token).Get(), - Params.ClientID.Get(), - Params.ClientSecret.Get(), - LOCALHOST_PORT, - FormEncode(CodeVerifier).Get()); + GString Body, Http; + Body.Printf("code=%s&" + "client_id=%s&" + "client_secret=%s&" + "redirect_uri=http://localhost:%i&" + "code_verifier=%s&" + "grant_type=authorization_code", + FormEncode(Token).Get(), + Params.ClientID.Get(), + Params.ClientSecret.Get(), + LOCALHOST_PORT, + FormEncode(CodeVerifier).Get()); - Http.Printf("POST /oauth2/v4/token HTTP/1.1\r\n" - "Host: www.googleapis.com\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "Content-length: " LPrintfSizeT "\r\n" - "\r\n" - "%s", - Body.Length(), - Body.Get()); - if (!Write(&sock, Http)) - { - Log->Print("%s:%i - Error writing to socket.\n", _FL); - return false; + Http.Printf("POST /oauth2/v4/token HTTP/1.1\r\n" + "Host: www.googleapis.com\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-length: " LPrintfSizeT "\r\n" + "\r\n" + "%s", + Body.Length(), + Body.Get()); + if (!Write(&sock, Http)) + { + Log->Print("%s:%i - Error writing to socket.\n", _FL); + return false; + } + + GString Hdrs; + if (!GetHttp(&sock, Hdrs, Body, true)) + { + return false; + } + + // Log->Print("Body=%s\n", Body.Get()); + LJson j(Body); + + AccessToken = j.Get("access_token"); + RefreshToken = j.Get("refresh_token"); + ExpiresIn = j.Get("expires_in").Int(); } - GString Hdrs; - if (!GetHttp(&sock, Hdrs, Body, true)) - { + return AccessToken.Get() != NULL; + } + + LOAuth2Priv(LOAuth2::Params ¶ms, const char *account, GDom *store, GStream *log) + { + Params = params; + Id = account; + Store = store; + Log = log ? log : &LocalLog; + } + + bool Serialize(bool Write) + { + if (!Store) return false; - } - Log->Print("Body=%s\n", Body.Get()); - - LJson j(Body); + GVariant v; + GString Key, kAccTok; + Key.Printf("%s.%s", Params.Scope.Get(), Id.Get()); + auto KeyB64 = Base64(Key); + kAccTok.Printf("OAuth2-%s-%s", OPT_AccessToken, KeyB64.Get()); + kAccTok = kAccTok.RStrip("="); - AccessToken = j.Get("access_token"); - RefreshToken = j.Get("refresh_token"); - ExpiresIn = j.Get("expires_in").Int(); + if (Write) + { + Store->SetValue(kAccTok, v = AccessToken.Get()); + } + else + { + if (Store->GetValue(kAccTok, v)) + AccessToken = v.Str(); + else + return false; + } return true; } - - LOAuth2Priv(GStream *log, const char *id) - { - Log = log ? log : &LocalLog; - Id = id; - } }; -LOAuth2::LOAuth2(LOAuth2::Params ¶ms, const char *Account, GStream *log) +LOAuth2::LOAuth2(LOAuth2::Params ¶ms, const char *account, GDom *store, GStream *log) { - d = new LOAuth2Priv(log, Account); - d->Params = params; + d = new LOAuth2Priv(params, account, store, log); + d->Serialize(false); } LOAuth2::~LOAuth2() { + d->Serialize(true); delete d; } GString LOAuth2::GetAccessToken() { + if (d->AccessToken) + return d->AccessToken; + if (d->GetToken()) { d->Log->Print("Got token.\n"); if (d->GetAccess()) { return d->AccessToken; } } else d->Log->Print("No token.\n"); return GString(); } diff --git a/src/common/INet/Mail.cpp b/src/common/INet/Mail.cpp --- a/src/common/INet/Mail.cpp +++ b/src/common/INet/Mail.cpp @@ -1,4017 +1,4049 @@ /*hdr ** FILE: Mail.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Mail app ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "Lgi.h" #include "Mail.h" #include "GToken.h" #include "Base64.h" #include "INetTools.h" #include "LDateTime.h" #include "GDocView.h" #include "Store3Defs.h" #include "LgiRes.h" #include "../Hash/md5/md5.h" const char *sTextPlain = "text/plain"; const char *sTextHtml = "text/html"; const char *sTextXml = "text/xml"; const char *sApplicationInternetExplorer = "application/internet-explorer"; const char sMultipartMixed[] = "multipart/mixed"; const char sMultipartEncrypted[] = "multipart/encrypted"; const char sMultipartSigned[] = "multipart/signed"; const char sMultipartAlternative[] = "multipart/alternative"; const char sMultipartRelated[] = "multipart/related"; const char sAppOctetStream[] = "application/octet-stream"; ////////////////////////////////////////////////////////////////////////////////////////////////// LogEntry::LogEntry(GColour col) { c = col; } bool LogEntry::Add(const char *t, ssize_t len) { if (!t) return false; if (len < 0) len = strlen(t); /* // Strip off any whitespace on the end of the line. while (len > 0 && strchr(" \t\r\n", t[len-1])) len--; */ GAutoWString w(Utf8ToWide(t, len)); if (!w) return false; size_t ch = StrlenW(w); return Txt.Add(w, ch); } +bool Base64Str(GString &s) +{ + GString b64; + ssize_t Base64Len = BufferLen_BinTo64(s.Length()); + if (!b64.Set(NULL, Base64Len)) + return false; + + ssize_t Ch = ConvertBinaryToBase64(b64.Get(), b64.Length(), (uchar*)s.Get(), s.Length()); + LgiAssert(Ch == b64.Length()); + s = b64; + return true; +} + +bool UnBase64Str(GString &s) +{ + GString Bin; + ssize_t BinLen = BufferLen_64ToBin(s.Length()); + if (!Bin.Set(NULL, BinLen)) + return false; + + ssize_t Ch = ConvertBase64ToBinary((uchar*)Bin.Get(), Bin.Length(), s.Get(), s.Length()); + LgiAssert(Ch <= (int)Bin.Length()); + s = Bin; + s.Get()[Ch] = 0; + return true; +} + ////////////////////////////////////////////////////////////////////////////////////////////////// // return true if there are any characters with the 0x80 bit set bool Is8Bit(char *Text) { if (!Text) return false; while (*Text) { if (*Text & 0x80) return true; Text++; } return false; } // returns the maximum length of the lines contained in the string int MaxLineLen(char *Text) { if (!Text) return false; int Max = 0; int i = 0; for (char *c = Text; *c; c++) { if (*c == '\r') { // return } else if (*c == '\n') { // eol Max = MAX(i, Max); i = 0; } else { // normal char i++; } } return Max; } bool IsDotLined(char *Text) { if (Text) { for (char *l = Text; l && *l; ) { if (l[0] == '.') { if (l[1] == '\n' || l[1] == 0) { return true; } } l = strchr(l, '\n'); if (l) l++; } } return false; } char ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } // Is s a valid non-whitespace string? bool ValidNonWSStr(const char *s) { if (s && *s) { while (*s && strchr(" \r\t\n", *s)) { s++; } if (*s) { return true; } } return false; } void TokeniseStrList(char *Str, List &Output, const char *Delim) { if (Str && Delim) { char *s = Str; while (*s) { while (*s && strchr(WhiteSpace, *s)) s++; char *e = s; for (; *e; e++) { if (strchr("\'\"", *e)) { // handle string constant char delim = *e++; e = strchr(e, delim); } else if (*e == '<') { e = strchr(e, '>'); } else { while (*e && *e != '<' && !IsWhiteSpace(*e) && !strchr(Delim, *e)) e++; } if (!e || !*e || strchr(Delim, *e)) { break; } } ssize_t Len = e ? e - s : strlen(s); if (Len > 0) { char *Temp = new char[Len+1]; if (Temp) { memcpy(Temp, s, Len); Temp[Len] = 0; Output.Insert(Temp); } } if (e) { s = e; for (; *s && strchr(Delim, *s); s++); } else break; } } } //////////////////////////////////////////////////////////////////////////////// char *DecodeBase64Str(char *Str, int Len) { if (Str) { ssize_t B64Len = (Len < 0) ? strlen(Str) : Len; ssize_t BinLen = BufferLen_64ToBin(B64Len); char *s = new char[BinLen+1]; if (s) { ssize_t Converted = ConvertBase64ToBinary((uchar*)s, BinLen, Str, B64Len); s[Converted] = 0; DeleteArray(Str); Str = s; } } return Str; } char *DecodeQuotedPrintableStr(char *Str, ssize_t Len) { if (Str) { if (Len < 0) Len = strlen(Str); uchar *s = new uchar[Len+1]; if (s) { char *Out = (char*) s; char *Text = Str; for (int i=0; i='a'&&(c)<='z')?(c)-'a'+'A':(c) ) #endif char *DecodeRfc2047(char *Str) { if (!Str) return NULL; GStringPipe p(256); for (char *s = Str; *s; ) { char *e = s; bool Decode = 0, Descape = 0; while (*e) { if ( (Decode = (e[0] == '=' && e[1] == '?')) || (Descape = (e[0] == '\\')) ) { // Emit characters between 's' and 'e' if (e > s) p.Write(s, e - s); break; } e++; } if (Decode) { // is there a word remaining bool Encoded = false; char *Start = e + 2; char *First = strchr(Start, '?'); char *Second = First ? strchr(First + 1, '?') : NULL; char *End = Second ? strstr(Second + 1, "?=") : NULL; if (End) { GString Cp(Start, First - Start); int Type = CONTENT_NONE; bool StripUnderscores = false; if (ToUpper(First[1]) == 'B') { // Base64 encoding Type = CONTENT_BASE64; } else if (ToUpper(First[1]) == 'Q') { // Quoted printable Type = CONTENT_QUOTED_PRINTABLE; StripUnderscores = true; } if (Type != CONTENT_NONE) { Second++; char *Block = NewStr(Second, End-Second); if (Block) { switch (Type) { case CONTENT_BASE64: Block = DecodeBase64Str(Block); break; case CONTENT_QUOTED_PRINTABLE: Block = DecodeQuotedPrintableStr(Block); break; } size_t Len = strlen(Block); if (StripUnderscores) { for (char *i=Block; *i; i++) { if (*i == '_') *i = ' '; } } if (Cp && !_stricmp(Cp, "utf-8")) { p.Write((uchar*)Block, Len); } else { GAutoString Utf8((char*)LgiNewConvertCp("utf-8", Block, Cp, Len)); if (Utf8) { if (LgiIsUtf8(Utf8)) p.Write((uchar*)Utf8.Get(), strlen(Utf8)); } else { p.Write((uchar*)Block, Len); } } DeleteArray(Block); } s = End + 2; if (*s == '\n') { s++; while (*s && strchr(WhiteSpace, *s)) s++; } Encoded = true; } } if (!Encoded) { // Encoding error, just emit the raw string and exit. size_t Len = strlen(s); p.Write((uchar*) s, Len); break; } } else if (Descape) { // Un-escape the string... e++; if (*e) p.Write(e, 1); else break; s = e + 1; } else { // Last segment of string... LgiAssert(*e == 0); if (e > s) p.Write(s, e - s); break; } } DeleteArray(Str); return p.NewStr(); } #define MIME_MAX_LINE 76 char *EncodeRfc2047(char *Str, const char *CodePage, List *CharsetPrefs, ssize_t LineLength) { if (!CodePage) { CodePage = "utf-8"; } GStringPipe p(256); if (!Str) return NULL; if (Is8Bit(Str)) { // pick an encoding bool Base64 = false; const char *DestCp = "utf-8"; size_t Len = strlen(Str);; if (_stricmp(CodePage, "utf-8") == 0) { DestCp = LgiDetectCharset(Str, Len, CharsetPrefs); } int Chars = 0; for (unsigned i=0; i 0 && ((double)Chars/Len) > 0.4 ) ) { Base64 = true; } char *Buf = (char*)LgiNewConvertCp(DestCp, Str, CodePage, Len); if (Buf) { // encode the word char Prefix[64]; int Ch = sprintf_s(Prefix, sizeof(Prefix), "=?%s?%c?", DestCp, Base64 ? 'B' : 'Q'); p.Write(Prefix, Ch); LineLength += Ch; if (Base64) { // Base64 size_t InLen = strlen(Buf); // int EstBytes = BufferLen_BinTo64(InLen); char Temp[512]; ssize_t Bytes = ConvertBinaryToBase64(Temp, sizeof(Temp), (uchar*)Buf, InLen); p.Push(Temp, Bytes); } else { // Quoted printable for (char *w = Buf; *w; w++) { if (*w == ' ') { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write((char*)"_", 1); LineLength++; } else if (*w & 0x80 || *w == '_' || *w == '?' || *w == '=') { if (LineLength > MIME_MAX_LINE - 5) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } char Temp[16]; Ch = sprintf_s(Temp, sizeof(Temp), "=%2.2X", (uchar)*w); p.Write(Temp, Ch); LineLength += Ch; } else { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write(w, 1); LineLength++; } } } p.Push("?="); DeleteArray(Buf); } DeleteArray(Str); Str = p.NewStr(); } else { bool RecodeNewLines = false; for (char *s = Str; *s; s++) { if (*s == '\n' && (s == Str || s[-1] != '\r')) { RecodeNewLines = true; break; } } if (RecodeNewLines) { for (char *s = Str; *s; s++) { if (*s == '\r') ; else if (*s == '\n') p.Write("\r\n", 2); else p.Write(s, 1); } DeleteArray(Str); Str = p.NewStr(); } } return Str; } ////////////////////////////////////////////////////////////////////////////// void DeNullText(char *in, ssize_t &len) { char *out = in; char *end = in + len; while (in < end) { if (*in) { *out++ = *in; } else { len--; } in++; } } ////////////////////////////////////////////////////////////////////////////// typedef char CharPair[2]; static CharPair Pairs[] = { {'<', '>'}, {'(', ')'}, {'\'', '\''}, {'\"', '\"'}, {0, 0}, }; struct MailAddrPart { GAutoString Part; bool Brackets; bool ValidEmail; GAutoString RemovePairs(char *Str, ssize_t Len, CharPair *Pairs) { char *s = Str; if (Len < 0) Len = strlen(s); while (*s && strchr(WhiteSpace, *s)) { s++; Len--; } if (!*s) return GAutoString(); // Get the end of the string... char *e = s; if (Len < 0) e += strlen(s); else e += Len; // Seek back over any trailing whitespace while (e > s && strchr(WhiteSpace, e[-1])) e--; for (CharPair *p = Pairs; (*p)[0]; p++) { if ((*p)[0] == *s && (*p)[1] == e[-1]) { s++; e--; if (s < e) { // reset search p = Pairs - 1; } else break; } } Len = e - s; if (Len < 0) return GAutoString(); return GAutoString(NewStr(s, Len)); } MailAddrPart(char *s, ssize_t len) { ValidEmail = false; Brackets = false; if (s) { if (len < 0) len = strlen(s); while (strchr(WhiteSpace, *s) && len > 0) { s++; len--; } Brackets = *s == '<'; Part = RemovePairs(s, len, Pairs); // ValidEmail = IsValidEmail(Part); } } int Score() { if (!Part) return 0; return (ValidEmail ? 1 : 0) + (Brackets ? 1 : 0); } }; int PartCmp(GAutoPtr *a, GAutoPtr *b) { return (*b)->Score() - (*a)->Score(); } void DecodeAddrName(const char *Str, GAutoString &Name, GAutoString &Addr, const char *DefaultDomain) { /* Testing code char *Input[] = { "\"Sound&Secure@speedytechnical.com\" ", "\"@MM-Social Mailman List\" ", "'Matthew Allen (fret)' ", "Matthew Allen (fret) ", "\"'Matthew Allen'\" ", "Matthew Allen", "fret@memecode.com", "\"\" ", " (fret@memecode.com)", "Matthew Allen ", "\"Matthew, Allen\" (fret@memecode.com)", "Matt'hew Allen ", "john.omalley ", "Bankers' Association (ABA)", "'Amy's Mum' ", "\"Philip Doggett (JIRA)\" ", 0 }; GAutoString Name, Addr; for (char **i = Input; *i; i++) { Name.Reset(); Addr.Reset(); DecodeAddrName(*i, Name, Addr, "name.com"); LgiTrace("N=%-#32s A=%-32s\n", Name, Addr); } */ if (!Str) return; GArray< GAutoPtr > Parts; GString s = Str; GString non; GString email; GString::Array a = s.SplitDelimit("<>"); for (unsigned i=0; i 0) { const char *ChSet = " \t\r\n\'\"<>"; do { non = non.Strip(ChSet); } while (non.Length() > 0 && strchr(ChSet, non(0))); } Name.Reset(NewStr(non)); Addr.Reset(NewStr(email.Strip())); } void StrCopyToEOL(char *d, char *s) { if (d && s) { while (*s && *s != '\r' && *s != '\n') { *d++ = *s++; } *d = 0; } } ////////////////////////////////////////////////////////////////////////////////////////////////// MailTransaction::MailTransaction() { Index = -1; Flags = 0; Status = false; Oversize = false; Stream = 0; UserData = 0; } MailTransaction::~MailTransaction() { } ////////////////////////////////////////////////////////////////////////////////////////////////// FileDescriptor::FileDescriptor() { Embeded = 0; Offset = 0; Size = 0; Data = 0; MimeType = 0; ContentId = 0; Lock = 0; OwnEmbeded = false; } FileDescriptor::FileDescriptor(GStreamI *embed, int64 offset, int64 size, char *name) { Embeded = embed; Offset = offset; Size = size; Data = 0; MimeType = 0; Lock = 0; ContentId = 0; OwnEmbeded = false; if (name) { Name(name); } } FileDescriptor::FileDescriptor(char *name) { Embeded = 0; Offset = 0; Size = 0; Data = 0; MimeType = 0; Lock = 0; ContentId = 0; OwnEmbeded = false; if (name) { Name(name); if (File.Open(name, O_READ)) { Size = File.GetSize(); File.Close(); } } } FileDescriptor::FileDescriptor(char *data, int64 len) { Embeded = 0; Offset = 0; MimeType = 0; Lock = 0; ContentId = 0; Size = len; OwnEmbeded = false; Data = data ? new uchar[(size_t)Size] : 0; if (Data) { memcpy(Data, data, (size_t)Size); } } FileDescriptor::~FileDescriptor() { if (OwnEmbeded) { DeleteObj(Embeded); } DeleteArray(MimeType); DeleteArray(ContentId); DeleteArray(Data); } void FileDescriptor::SetOwnEmbeded(bool i) { OwnEmbeded = i; } void FileDescriptor::SetLock(LMutex *l) { Lock = l; } LMutex *FileDescriptor::GetLock() { return Lock; } GStreamI *FileDescriptor::GotoObject() { if (Embeded) { Embeded->SetPos(Offset); return Embeded; } else if (Name() && File.Open(Name(), O_READ)) { return &File; } else if (Data && Size > 0) { DataStream.Reset(new GMemStream(Data, Size, false)); return DataStream; } return 0; } int FileDescriptor::Sizeof() { return (int)Size; } uchar *FileDescriptor::GetData() { return Data; } bool FileDescriptor::Decode(char *ContentType, char *ContentTransferEncoding, char *MimeData, int MimeDataLen) { bool Status = false; int Content = CONTENT_NONE; if (ContentType && ContentTransferEncoding) { // Content-Type: application/octet-stream; name="Scribe.opt" Content = CONTENT_OCTET_STREAM; if (strnistr(ContentTransferEncoding, "base64", 1000)) { Content = CONTENT_BASE64; } if (strnistr(ContentTransferEncoding, "quoted-printable", 1000)) { Content = CONTENT_QUOTED_PRINTABLE; } if (Content != CONTENT_NONE) { const char *NameKey = "name"; char *n = strnistr(ContentType, NameKey, 1000); if (n) { char *Equal = strchr(n, '='); if (Equal) { Equal++; while (*Equal && *Equal == '\"') { Equal++; } char *End = strchr(Equal, '\"'); if (End) { *End = 0; } Name(Equal); Status = true; } } } } if (Status && MimeData && MimeDataLen > 0 && Content != CONTENT_NONE) { Status = false; char *Base64 = new char[MimeDataLen]; switch (Content) { case CONTENT_OCTET_STREAM: { Size = 0; DeleteObj(Data); Data = new uchar[MimeDataLen]; if (Data) { Size = MimeDataLen; memcpy(Data, MimeData, (size_t)Size); Status = true; } break; } case CONTENT_QUOTED_PRINTABLE: { Size = 0; DeleteObj(Data); Data = new uchar[MimeDataLen+1]; if (Data) { char *Out = (char*) Data; for (int i=0; i= Size - 3; if (Status) { Size = Converted; } else { DeleteArray(Data); Size = 0; } } break; } } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// AddressDescriptor::AddressDescriptor(AddressDescriptor *Copy) { Data = 0; Status = Copy ? Copy->Status : false; CC = Copy ? Copy->CC : false; Addr = Copy ? NewStr(Copy->Addr) : 0; Name = Copy ? NewStr(Copy->Name) : 0; } AddressDescriptor::~AddressDescriptor() { _Delete(); } void AddressDescriptor::_Delete() { Data = 0; Status = false; CC = 0; DeleteArray(Name); DeleteArray(Addr); } void AddressDescriptor::Print(char *Str, int Len) { if (!Str) { LgiAssert(0); return; } if (Addr && Name) { sprintf_s(Str, Len, "%s (%s)", Addr, Name); } else if (Addr) { strcpy_s(Str, Len, Addr); } else if (Name) { sprintf_s(Str, Len, "(%s)", Name); } } ////////////////////////////////////////////////////////////////////////////////////////////////// MailProtocol::MailProtocol() { Buffer[0] = 0; Logger = 0; ErrMsgId = 0; + SettingStore = NULL; Items = 0; Transfer = 0; } MailProtocol::~MailProtocol() { CharsetPrefs.DeleteArrays(); } void MailProtocol::Log(const char *Str, GSocketI::SocketMsgType type) { if (Logger && Str) { char s[1024]; char *e = s + sizeof(s) - 2; const char *i = Str; char *o = s; while (*i && o < e) { *o++ = *i++; } while (o > s && (o[-1] == '\r' || o[-1] == '\n')) o--; *o++ = '\n'; *o = 0; Logger->Write(s, o - s, type); } } bool MailProtocol::Error(const char *file, int line, const char *msg, ...) { char s[1024]; va_list a; va_start(a, msg); vsprintf_s(s, sizeof(s), msg, a); va_end(a); Log(s, GSocketI::SocketMsgError); LgiTrace("%s:%i - Error: %s", file, line, s); return false; } bool MailProtocol::Read() { bool Status = false; if (Socket) { Status = Socket->Read(Buffer, sizeof(Buffer), 0) > 0; } return Status; } bool MailProtocol::Write(const char *Buf, bool LogWrite) { bool Status = false; if (Socket) { const char *p = Buf ? Buf : Buffer; Status = Socket->Write(p, strlen(p), 0) > 0; if (LogWrite) { Log(p, GSocketI::SocketMsgSend); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// #define VERIFY_RET_VAL(Arg) \ { \ if (!Arg) \ { \ LMutex::Auto Lck(&SocketLock, _FL); \ Socket.Reset(0); \ return NULL; \ } \ } #define VERIFY_ONERR(Arg) \ { \ if (!Arg) \ { \ LMutex::Auto Lck(&SocketLock, _FL); \ Socket.Reset(0); \ goto CleanUp; \ } \ } void Reorder(GArray &a, const char *s) { for (unsigned i=0; i 0) { a.DeleteAt(i, true); a.AddAt(0, s); break; } } } MailSmtp::MailSmtp() { } MailSmtp::~MailSmtp() { } bool MailSmtp::Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { char Str[256] = ""; bool Status = false; if (!RemoteHost) Error(_FL, "No remote SMTP host.\n"); else { strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (Port == 0) { if (Flags & MAIL_SSL) Port = SMTP_SSL_PORT; else Port = SMTP_PORT; } GAutoString Server(TrimStr(Str)); if (Server) { if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } Socket->SetTimeout(30 * 1000); char Msg[256]; sprintf_s(Msg, sizeof(Msg), "Connecting to %s:%i...", Server.Get(), Port); Log(Msg, GSocketI::SocketMsgInfo); if (!Socket->Open(Server, Port)) Error(_FL, "Failed to connect socket to %s:%i\n", Server.Get(), Port); else { GStringPipe Str; // receive signon message VERIFY_RET_VAL(ReadReply("220")); // Rfc 2554 ESMTP authentication SmtpHello: sprintf_s(Buffer, sizeof(Buffer), "EHLO %s\r\n", (ValidNonWSStr(LocalDomain)) ? LocalDomain : "default"); VERIFY_RET_VAL(Write(0, true)); /*bool HasSmtpExtensions =*/ ReadReply("250", &Str); bool Authed = false; bool NoAuthTypes = false; bool SupportsStartTLS = false; GArray AuthTypes; // Look through the response for the auth line char *Response = Str.NewStr(); if (Response) { GToken Lines(Response, "\n"); for (uint32_t i=0; iSetValue(GSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; goto SmtpHello; } else { // SSL init failed... what to do here? return false; } } if (ValidStr(UserName) && ValidStr(Password)) { if (AuthTypes.Length() == 0) { // No auth types? huh? if (TestFlag(Flags, MAIL_USE_AUTH)) { if (TestFlag(Flags, MAIL_USE_PLAIN)) // Force plain type AuthTypes.Add("PLAIN"); else if (TestFlag(Flags, MAIL_USE_LOGIN)) // Force login type AuthTypes.Add("LOGIN"); else if (TestFlag(Flags, MAIL_USE_CRAM_MD5)) // Force CRAM MD5 type AuthTypes.Add("CRAM-MD5"); else if (TestFlag(Flags, MAIL_USE_OAUTH2)) // Force OAUTH2 type AuthTypes.Add("XOAUTH2"); else { // Try all AuthTypes.Add("PLAIN"); AuthTypes.Add("LOGIN"); AuthTypes.Add("CRAM-MD5"); AuthTypes.Add("XOAUTH2"); } } else { NoAuthTypes = true; } } else { if (TestFlag(Flags, MAIL_USE_AUTH)) { // Force user preference if (TestFlag(Flags, MAIL_USE_PLAIN)) Reorder(AuthTypes, "PLAIN"); else if (TestFlag(Flags, MAIL_USE_LOGIN)) Reorder(AuthTypes, "LOGIN"); else if (TestFlag(Flags, MAIL_USE_CRAM_MD5)) Reorder(AuthTypes, "CRAM-MD5"); else if (TestFlag(Flags, MAIL_USE_OAUTH2)) Reorder(AuthTypes, "XOAUTH2"); } } for (auto Auth : AuthTypes) { // Try all their auth types against our internally support types if (Auth.Equals("LOGIN")) { VERIFY_RET_VAL(Write("AUTH LOGIN\r\n", true)); VERIFY_RET_VAL(ReadReply("334")); ZeroObj(Buffer); ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)UserName, strlen(UserName)); strcat(Buffer, "\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("334")) { ZeroObj(Buffer); ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)Password, strlen(Password)); strcat(Buffer, "\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) { Authed = true; } } } else if (Auth.Equals("PLAIN")) { char Tmp[256]; ZeroObj(Tmp); int ch = 1; ch += sprintf_s(Tmp+ch, sizeof(Tmp)-ch, "%s", UserName) + 1; ch += sprintf_s(Tmp+ch, sizeof(Tmp)-ch, "%s", Password) + 1; char B64[256]; ZeroObj(B64); ConvertBinaryToBase64(B64, sizeof(B64), (uint8_t*)Tmp, ch); sprintf_s(Buffer, sizeof(Buffer), "AUTH PLAIN %s\r\n", B64); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) { Authed = true; } } else if (Auth.Equals("CRAM-MD5")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH CRAM-MD5\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("334")) { auto Sp = strchr(Buffer, ' '); if (Sp) { Sp++; // Decode the server response: uint8_t Txt[128]; auto InLen = strlen(Sp); ssize_t TxtLen = ConvertBase64ToBinary(Txt, sizeof(Txt), Sp, InLen); // Calc the hash: // https://tools.ietf.org/html/rfc2104 char Key[64] = {0}; memcpy(Key, Password, MIN(strlen(Password), sizeof(Key))); uint8_t iKey[256]; char oKey[256]; for (unsigned i=0; i<64; i++) { iKey[i] = Key[i] ^ 0x36; oKey[i] = Key[i] ^ 0x5c; } memcpy(iKey+64, Txt, TxtLen); md5_state_t md5; md5_init(&md5); md5_append(&md5, iKey, 64 + TxtLen); md5_finish(&md5, oKey + 64); md5_init(&md5); md5_append(&md5, (uint8_t*)oKey, 64 + 16); char digest[16]; md5_finish(&md5, digest); char r[256]; int ch = sprintf_s(r, sizeof(r), "%s ", UserName); for (unsigned i=0; i<16; i++) ch += sprintf_s(r+ch, sizeof(r)-ch, "%02x", (uint8_t)digest[i]); // Base64 encode ssize_t Len = ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uint8_t*)r, ch); Buffer[Len++] = '\r'; Buffer[Len++] = '\n'; Buffer[Len++] = 0; VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) Authed = true; } } } else if (Auth.Equals("XOAUTH2")) { - LOAuth2 Auth(OAuth2, UserName); - auto Tok = Auth.GetAccessToken(); + LOAuth2 OAuth2(OAuth2, UserName, SettingStore); + auto Tok = OAuth2.GetAccessToken(); if (Tok) { - } - else - { + GString s; + s.Printf("user=%s\001auth=Bearer %s\001\001\0", UserName, Tok.Get()); + Base64Str(s); + + sprintf_s(Buffer, sizeof(Buffer), "AUTH %s %s\r\n", Auth.Get(), s.Get()); + VERIFY_RET_VAL(Write(0, true)); + Authed = ReadReply("235"); } } else { LgiTrace("%s:%i - Unsupported auth type '%s'\n", _FL, Auth.Get()); } if (Authed) break; } if (!Authed) { if (NoAuthTypes) SetError(L_ERROR_ESMTP_NO_AUTHS, "The server didn't return the authentication methods it supports."); else { GString p; for (auto i : AuthTypes) { if (p.Get()) p += ", "; p += i; } SetError(L_ERROR_UNSUPPORTED_AUTH, "Authentication failed, types available:\n\t%s", p); } } Status = Authed; } else { Status = true; } } } } return Status; } bool MailSmtp::WriteText(const char *Str) { // we have to convert all strings to CRLF in here bool Status = false; if (Str) { GMemQueue Temp; const char *Start = Str; while (*Str) { if (*Str == '\n') { // send a string ssize_t Size = Str-Start; if (Str[-1] == '\r') Size--; Temp.Write((uchar*) Start, Size); Temp.Write((uchar*) "\r\n", 2); Start = Str + 1; } Str++; } // send the final string ssize_t Size = Str-Start; if (Str[-1] == '\r') Size--; Temp.Write((uchar*) Start, (int)Size); Size = (int)Temp.GetSize(); char *Data = new char[(size_t)Size]; if (Data) { Temp.Read((uchar*) Data, Size); Status = Socket->Write(Data, (int)Size, 0) == Size; DeleteArray(Data); } } return Status; } char *StripChars(char *Str, const char *Chars = "\r\n") { if (Str) { char *i = Str; char *o = Str; while (*i) { if (strchr(Chars, *i)) i++; else *o++ = *i++; } *o++ = 0; } return Str; } char *CreateAddressTag(List &l, int Type, List *CharsetPrefs) { char *Result = 0; List Addr; AddressDescriptor *a; for (a = l.First(); a; a = l.Next()) { if (a->CC == Type) { Addr.Insert(a); } } if (Addr.Length() > 0) { GStringPipe StrBuf; StrBuf.Push((Type == 0) ? (char*)"To: " : (char*)"Cc: "); for (a = Addr.First(); a; ) { AddressDescriptor *NextA = Addr.Next(); char Buffer[256] = ""; StripChars(a->Name); StripChars(a->Addr); if (a->Addr && strchr(a->Addr, ',')) { // Multiple address format GToken t(a->Addr, ","); for (uint32_t i=0; i", t[i]); if (i < t.Length()-1) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); Buffer[0] = 0; } } else if (a->Name) { // Name and addr char *Mem = 0; char *Name = a->Name; if (Is8Bit(Name)) { Name = Mem = EncodeRfc2047(NewStr(Name), 0, CharsetPrefs); } if (strchr(Name, '\"')) sprintf_s(Buffer, sizeof(Buffer), "'%s' <%s>", Name, a->Addr); else sprintf_s(Buffer, sizeof(Buffer), "\"%s\" <%s>", Name, a->Addr); DeleteArray(Mem); } else if (a->Addr) { // Just addr sprintf_s(Buffer, sizeof(Buffer), "<%s>", a->Addr); } if (NextA) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); a = NextA; } StrBuf.Push("\r\n"); Result = StrBuf.NewStr(); } return Result; } // This class implements a pipe that writes to a socket class SocketPipe : public GStringPipe { GSocketI *s; MailProtocolProgress *p; public: bool Status; SocketPipe(GSocketI *socket, MailProtocolProgress *progress) { s = socket; p = progress; Status = true; } ssize_t Read(void *Ptr, ssize_t Size, int Flags) { return false; } int64 SetSize(int64 Size) { if (p) { p->Start = LgiCurrentTime(); p->Range = (int)Size; return Size; } return -1; } ssize_t Write(const void *InPtr, ssize_t Size, int Flags) { char *Ptr = (char*)InPtr; char *e = Ptr + Size; while (Ptr < e) { ssize_t w = s->Write(Ptr, e - Ptr, 0); if (w > 0) { Ptr += w; if (p && p->Range && w > 0) p->Value += w; } else break; } return Ptr - (char*)InPtr; } }; bool MailSmtp::SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err) { bool AddrOk = false; if (To.Length() == 0) { ErrMsgId = L_ERROR_ESMTP_NO_RECIPIENT; ErrMsgFmt = "No recipients to send to."; ErrMsgParam.Empty(); return false; } // send MAIL message if (From && ValidStr(From->Addr)) { sprintf_s(Buffer, sizeof(Buffer), "MAIL FROM: <%s>\r\n", From->Addr); } else { ErrMsgId = L_ERROR_ESMTP_NO_FROM; ErrMsgFmt = "No 'from' address in email."; ErrMsgParam.Empty(); return false; } VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("250", 0, Err)); // send RCPT message AddrOk = true; List::I Recip = To.begin(); for (AddressDescriptor *a = *Recip; a; a = *++Recip) { char *Addr = ValidStr(a->Addr) ? a->Addr : a->Name; if (ValidStr(Addr)) { GToken Parts(Addr, ","); for (unsigned p=0; p\r\n", Parts[p]); VERIFY_RET_VAL(Write(0, true)); a->Status = ReadReply("25", 0, Err); AddrOk |= a->Status != 0; // at least one address is ok } } else if (Err) { ErrMsgId = L_ERROR_ESMTP_BAD_RECIPIENT; ErrMsgFmt = "Invalid recipient '%s'."; ErrMsgParam = Addr; } } return AddrOk; } GStringPipe *MailSmtp::SendData(MailProtocolError *Err) { // send DATA message sprintf_s(Buffer, sizeof(Buffer), "DATA\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("354", 0, Err)); return new SocketPipe(Socket, Transfer); } GStringPipe *MailSmtp::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return SendToFrom(To, From, Err) ? SendData(Err) : NULL; } bool MailSmtp::SendEnd(GStringPipe *m) { bool Status = false; SocketPipe *Msg = dynamic_cast(m); if (Msg) { // send message terminator and receive reply if (Msg->Status && Msg->Write((void*)"\r\n.\r\n", 5, 0)) { Status = ReadReply("250"); } // else // just close the connection on them // so nothing gets sent } DeleteObj(m); return Status; } /* bool MailSmtp::Send(MailMessage *Msg, bool Mime) { bool Status = false; if (Socket && Msg) { GStringPipe *Sink = SendStart(Msg->To, Msg->From); if (Sink) { // setup a gui progress meter to send the email, // the length is just a guesstimate as we won't know the exact // size until we encode it all, and I don't want it hanging around // in memory at once, so we encode and send on the fly. int Length = 1024 + (Msg->GetBody() ? strlen(Msg->GetBody()) : 0); for (FileDescriptor *f=Msg->FileDesc.First(); f; f=Msg->FileDesc.Next()) { Length += f->Sizeof() * 4 / 3; } // encode and send message for transport Msg->Encode(*Sink, 0, this); Status = SendEnd(Sink); } } return Status; } */ bool MailSmtp::Close() { if (Socket) { // send QUIT message sprintf_s(Buffer, sizeof(Buffer), "QUIT\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("221")); LMutex::Auto Lock(&SocketLock, _FL); Socket.Reset(0); return true; } return false; } bool MailSmtp::ReadReply(const char *Str, GStringPipe *Pipe, MailProtocolError *Err) { bool Status = false; if (Socket && Str) { int Pos = 0; char *Start = Buffer; ZeroObj(Buffer); while (Pos < sizeof(Buffer)) { ssize_t Len = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Len > 0) { char *Eol = strstr(Start, "\r\n"); while (Eol) { // wipe EOL chars *Eol++ = 0; *Eol++ = 0; // process if (Pipe) { if (Pipe->GetSize()) Pipe->Push("\n"); Pipe->Push(Start); } if (Start[3] == ' ') { // end of response if (!strncmp(Start, Str, strlen(Str))) { Status = true; } if (Err) { Err->Code = atoi(Start); char *Sp = strchr(Start, ' '); Err->ErrMsg = Sp ? Sp + 1 : Start; } // Log Log(Start, atoi(Start) >= 400 ? GSocketI::SocketMsgError : GSocketI::SocketMsgReceive); // exit loop Pos = sizeof(Buffer); break; } else { Log(Start, GSocketI::SocketMsgReceive); // more lines follow Start = Eol; Eol = strstr(Start, "\r\n"); } } Pos += Len; } else break; } if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// class Mail2Folder : public GStringPipe { char File[256]; GFile F; public: Mail2Folder(char *Path, List &To) { do { char n[32]; sprintf_s(n, sizeof(n), "%u.mail", LgiRand()); LgiMakePath(File, sizeof(File), Path, n); } while (FileExists(File)); if (F.Open(File, O_WRITE)) { F.Print("Forward-Path: "); int i = 0; for (AddressDescriptor *a=To.First(); a; a=To.Next()) { a->Status = true; GToken Addrs(a->Addr, ","); for (unsigned n=0; n", Addrs[n]); } } F.Print("\r\n"); } } ~Mail2Folder() { F.Close(); } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return F.Read(Buffer, Size, Flags); } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { return F.Write(Buffer, Size, Flags); } }; class MailPostFolderPrivate { public: char *Path; MailPostFolderPrivate() { Path = 0; } ~MailPostFolderPrivate() { DeleteArray(Path); } }; MailSendFolder::MailSendFolder(char *Path) { d = new MailPostFolderPrivate; d->Path = NewStr(Path); } MailSendFolder::~MailSendFolder() { DeleteObj(d); } bool MailSendFolder::Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { return DirExists(d->Path); } bool MailSendFolder::Close() { return true; } GStringPipe *MailSendFolder::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return new Mail2Folder(d->Path, To); } bool MailSendFolder::SendEnd(GStringPipe *Sink) { DeleteObj(Sink); return true; } ////////////////////////////////////////////////////////////////////////////////////////////////// class MailItem { public: char *File; bool Delete; MailItem(char *f) { File = NewStr(f); Delete = false; } ~MailItem() { DeleteArray(File); } }; class MailReceiveFolderPrivate { public: char *Path; List Mail; MailReceiveFolderPrivate() { Path = 0; } ~MailReceiveFolderPrivate() { DeleteArray(Path); Mail.DeleteObjects(); } void Empty() { for (MailItem *m = Mail.First(); m; m = Mail.Next()) { if (m->Delete) { FileDev->Delete(m->File, false); } } Mail.DeleteObjects(); } }; MailReceiveFolder::MailReceiveFolder(char *Path) { d = new MailReceiveFolderPrivate; d->Path = NewStr(Path); } MailReceiveFolder::~MailReceiveFolder() { DeleteObj(d); } bool MailReceiveFolder::Open(GSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *SettingStore, int Flags) { // We don't use the socket so just free it here... DeleteObj(S); // Argument check if (!DirExists(d->Path)) return false; GDirectory Dir; // Loop through files, looking for email for (int b = Dir.First(d->Path, LGI_ALL_FILES); b; b = Dir.Next()) { if (!Dir.IsDir()) { if (MatchStr("*.eml", Dir.GetName()) || MatchStr("*.mail", Dir.GetName())) { char p[300]; Dir.Path(p, sizeof(p)); d->Mail.Insert(new MailItem(p)); } } } return true; } bool MailReceiveFolder::Close() { d->Empty(); return true; } int MailReceiveFolder::GetMessages() { return (int)d->Mail.Length(); } bool MailReceiveFolder::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; for (unsigned i=0; iStream) { t->Status = false; MailItem *m = d->Mail[t->Index]; if (m) { GFile i; if (i.Open(m->File, O_READ)) { GCopyStreamer c; if (c.Copy(&i, t->Stream)) { Status = t->Status = true; if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(t, Callbacks->CallbackData); } } } } } } return Status; } bool MailReceiveFolder::Delete(int Message) { MailItem *m = d->Mail[Message]; if (m) { m->Delete = true; return false; } return false; } int MailReceiveFolder::Sizeof(int Message) { MailItem *m = d->Mail[Message]; if (m) { return (int)LgiFileSize(m->File); } return 0; } bool MailReceiveFolder::GetUid(int Message, char *Id, int IdLen) { if (Id) { MailItem *m = d->Mail[Message]; if (m) { char *s = strrchr(m->File, DIR_CHAR); if (s++) { char *e = strchr(s, '.'); if (!e) e = s + strlen(s); ssize_t Len = e - s; memcpy(Id, s, Len); Id[Len] = 0; return true; } } } return false; } bool MailReceiveFolder::GetUidList(List &Id) { bool Status = false; for (int i=0; iMail.Length(); i++) { char Uid[256]; if (GetUid(i, Uid, sizeof(Uid))) { Status = true; Id.Insert(NewStr(Uid)); } else { Id.DeleteArrays(); Status = false; break; } } return Status; } char *MailReceiveFolder::GetHeaders(int Message) { MailItem *m = d->Mail[Message]; if (m) { GFile i; if (i.Open(m->File, O_READ)) { GStringPipe o; GCopyStreamer c; GLinePrefix e("", false); if (c.Copy(&i, &o, &e)) { return o.NewStr(); } } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////////////// MailPop3::MailPop3() { End = "\r\n.\r\n"; Marker = End; Messages = -1; } MailPop3::~MailPop3() { } int MailPop3::GetMessages() { if (Messages < 0) { if (Socket && Socket->IsOpen()) { // see how many messages there are VERIFY_ONERR(Write("STAT\r\n", true)); VERIFY_ONERR(ReadReply()); Messages = GetInt(); } else LgiAssert(!"No socket to get message count."); } CleanUp: return Messages; } int MailPop3::GetInt() { char Buf[32]; char *Start = strchr(Buffer, ' '); if (Start) { Start++; char *End = strchr(Start, ' '); if (End) { int Len = (int) (End - Start); memcpy(Buf, Start, Len); Buf[Len] = 0; return atoi(Buf); } } return 0; } bool MailPop3::ReadReply() { bool Status = false; if (Socket) { int Pos = 0; ZeroObj(Buffer); do { ssize_t Result = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Result <= 0) // an error? { // Leave the loop... break; } Pos += Result; } while ( !strstr(Buffer, "\r\n") && sizeof(Buffer)-Pos > 0); Status = (Buffer[0] == '+') && strstr(Buffer, "\r\n"); char *Cr = strchr(Buffer, '\r'); if (Cr) *Cr = 0; if (ValidStr(Buffer)) Log(Buffer, (Status) ? GSocketI::SocketMsgReceive : GSocketI::SocketMsgError); if (Cr) *Cr = '\r'; if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } bool MailPop3::ListCmd(const char *Cmd, LHashTbl, bool> &Results) { sprintf_s(Buffer, sizeof(Buffer), "%s\r\n", Cmd); if (!Write(0, true)) return false; char *b = Buffer; ssize_t r; while ((r = Socket->Read(b, sizeof(Buffer)-(b-Buffer))) > 0) { b += r; if (strnstr(Buffer, "\r\n.\r\n", b-Buffer)) break; } if (r <= 0) return false; GToken t(Buffer, "\r\n"); for (unsigned i=1; iGetValue("IsSSL", IsSsl) && IsSsl.CastInt32()) Port = POP3_SSL_PORT; else Port = POP3_PORT; } strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (S && User && Password && (Server = TrimStr(Str))) { S->SetTimeout(30 * 1000); ReStartConnection: if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } if (Socket && Socket->Open(Server, Port) && ReadReply()) { GVariant NoAPOP = false; if (SettingStore) SettingStore->GetValue(OPT_Pop3NoApop, NoAPOP); if (!NoAPOP.CastInt32()) { char *s = strchr(Buffer + 3, '<'); if (s) { char *e = strchr(s + 1, '>'); if (e) { Apop = NewStr(s, e - s + 1); } } } // login bool Authed = false; char *user = (char*) LgiNewConvertCp("iso-8859-1", User, "utf-8"); char *pass = (char*) LgiNewConvertCp("iso-8859-1", Password, "utf-8"); if (user && (pass || SecureAuth)) { bool SecurityError = false; if (TestFlag(Flags, MAIL_USE_STARTTLS)) { strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); GVariant v; if (Socket->SetValue(GSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; } else { SecurityError = true; } } if (!SecurityError && Apop) // GotKey, not implemented { // using encrypted password unsigned char Digest[16]; char HexDigest[33]; // append password char Key[256]; sprintf_s(Key, sizeof(Key), "%s%s", Apop, pass); ZeroObj(Digest); MDStringToDigest(Digest, Key); for (int i = 0; i < 16; i++) sprintf_s(HexDigest + (i*2), 3, "%2.2x", Digest[i]); HexDigest[32] = 0; sprintf_s(Buffer, sizeof(Buffer), "APOP %s %s\r\n", user, HexDigest); VERIFY_ONERR(Write(0, true)); Authed = ReadReply(); if (!Authed) { DeleteArray(Apop); GVariant NoAPOP = true; if (SettingStore) SettingStore->SetValue(OPT_Pop3NoApop, NoAPOP); S->Close(); goto ReStartConnection; } } if (!SecurityError && SecureAuth) { LHashTbl, bool> AuthTypes, Capabilities; if (ListCmd("AUTH", AuthTypes) && ListCmd("CAPA", Capabilities)) { if (AuthTypes.Find("GSSAPI")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH GSSAPI\r\n"); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); // http://www.faqs.org/rfcs/rfc2743.html } } } else if (!SecurityError && !Authed) { // have to use non-key method sprintf_s(Buffer, sizeof(Buffer), "USER %s\r\n", user); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); sprintf_s(Buffer, sizeof(Buffer), "PASS %s\r\n", pass); VERIFY_ONERR(Write(0, false)); Log("PASS *******", GSocketI::SocketMsgSend); Authed = ReadReply(); } DeleteArray(user); DeleteArray(pass); } if (Authed) { Status = true; } else { if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } LgiTrace("%s:%i - Failed auth.\n", _FL); } } else Error(_FL, "Failed to open socket to %s:%i and read reply.\n", Server, Port); } else Error(_FL, "No user/pass.\n"); } CleanUp: DeleteArray(Apop); DeleteArray(Server); return Status; } bool MailPop3::MailIsEnd(char *Ptr, ssize_t Len) { for (char *c = Ptr; Len-- > 0; c++) { if (*c != *Marker) { Marker = End; } if (*c == *Marker) { Marker++; if (!*Marker) { return true; } } } return false; } bool MailPop3::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Trans.Length() > 0 && Socket) { for (unsigned n = 0; nIndex; GStreamI *Msg = Trans[n]->Stream; if (Msg) { int Size = 0; // Transfer is not null when the caller wants info on the bytes comming in if (Transfer || Callbacks) { // get message size sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } MailSrcStatus Action = DownloadAll; int TopLines = 100; if (Callbacks && Callbacks->OnSrc) { Action = Callbacks->OnSrc(Trans[n], Size, &TopLines, Callbacks->CallbackData); } if (Action == DownloadAbort) { break; } if (Action == DownloadAll || Action == DownloadTop) { if (Action == DownloadAll) { sprintf_s(Buffer, sizeof(Buffer), "RETR %i\r\n", Message + 1); } else { sprintf_s(Buffer, sizeof(Buffer), "TOP %i %i\r\n", Message + 1, TopLines); } VERIFY_RET_VAL(Write(0, true)); GLinePrefix End(".\r\n"); if (Transfer) { Transfer->Value = 0; Transfer->Range = Size; Transfer->Start = LgiCurrentTime(); } // Read status line ZeroObj(Buffer); int Used = 0; bool Ok = false; bool Finished = false; int64 DataPos = 0; while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer+Used, sizeof(Buffer)-Used-1, 0); if (r > 0) { DeNullText(Buffer + Used, r); if (Transfer) { Transfer->Value += r; } char *Eol = strchr(Buffer, '\n'); if (Eol) { Eol++; Ok = Buffer[0] == '+'; if (Ok) { // Log(Buffer, GSocketI::SocketMsgReceive); // The Buffer was zero'd at the beginning garrenteeing // NULL termination size_t Len = strlen(Eol); ssize_t EndPos = End.IsEnd(Eol, Len); if (EndPos >= 0) { Msg->Write(Eol, EndPos - 3); Status = Trans[n]->Status = true; Finished = true; } else { Msg->Write(Eol, Len); DataPos += Len; } } else { Log(Buffer, GSocketI::SocketMsgError); Finished = true; } break; } Used += r; } else break; } if (!Finished) { if (Ok) { // Read rest of message while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer, sizeof(Buffer), 0); if (r > 0) { DeNullText(Buffer, r); if (Transfer) { Transfer->Value += r; } ssize_t EndPos = End.IsEnd(Buffer, r); if (EndPos >= 0) { ssize_t Actual = EndPos - DataPos - 3; if (Actual > 0) { ssize_t w = Msg->Write(Buffer, Actual); LgiAssert(w == Actual); } // else the end point was in the last buffer Status = Trans[n]->Status = true; break; } else { ssize_t w = Msg->Write(Buffer, r); LgiAssert(w == r); DataPos += r; } } else { break; } } if (!Status) { LgiTrace("%s:%i - Didn't get end-of-mail marker.\n", _FL); } } else { LgiTrace("%s:%i - Didn't get Ok.\n", _FL); break; } } if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(Trans[n], Callbacks->CallbackData); } if (Transfer) { Transfer->Empty(); } } else { Trans[n]->Oversize = Status = true; } if (Items) { Items->Value++; } } else { LgiTrace("%s:%i - No stream.\n", _FL); } } } else { LgiTrace("%s:%i - Arg check failed, len=%p, sock=%p.\n", _FL, Trans.Length(), Socket.Get()); } return Status; } bool MailPop3::GetSizes(GArray &Sizes) { if (Socket) { strcpy_s(Buffer, sizeof(Buffer), (char*)"LIST\r\n"); VERIFY_RET_VAL(Write(0, true)); char *s = 0; if (ReadMultiLineReply(s)) { GToken l(s, "\r\n"); DeleteArray(s); for (unsigned i=0; i 0; } int MailPop3::Sizeof(int Message) { int Size = 0; if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } return Size; } bool MailPop3::Delete(int Message) { if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "DELE %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); return true; } return false; } bool MailPop3::GetUid(int Index, char *Id, int IdLen) { if (Socket && Id) { sprintf_s(Buffer, sizeof(Buffer), "UIDL %i\r\n", Index + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *Space = strchr(Buffer, ' '); if (Space) { Space = strchr(Space+1, ' '); if (Space) { for (char *s = Space+1; *s; s++) { if (*s == '\r' || *s == '\n') { *s = 0; break; } } strcpy_s(Id, IdLen, Space+1); return true; } } } return false; } bool MailPop3::GetUidList(List &Id) { bool Status = false; if (Socket) { char *Str = 0; sprintf_s(Buffer, sizeof(Buffer), "UIDL\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadMultiLineReply(Str)); if (Str) { Status = true; GToken T(Str, "\r\n"); for (unsigned i=0; iRead(Buffer, sizeof(Buffer), 0); if (ReadLen > 0 && Buffer[0] == '+') { // positive response char *Eol = strchr(Buffer, '\n'); if (Eol) { char *Ptr = Eol + 1; ReadLen -= Ptr-Buffer; memmove(Buffer, Ptr, ReadLen); Temp.Write((uchar*) Buffer, ReadLen); while (!MailIsEnd(Buffer, ReadLen)) { ReadLen = Socket->Read(Buffer, sizeof(Buffer), 0); if (ReadLen > 0) { Temp.Write((uchar*) Buffer, ReadLen); } else break; } int Len = (int)Temp.GetSize(); Str = new char[Len+1]; if (Str) { Temp.Read((uchar*)Str, Len); Str[Len] = 0; Status = true; } } } } return Status; } bool MailPop3::Close() { if (Socket) { // logout VERIFY_RET_VAL(Write("QUIT\r\n", true)); // 2 sec timeout, we don't really care about the server's response Socket->SetTimeout(2000); ReadReply(); if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } Messages = 0; return true; } return false; } ////////////////////////////////////////////////////////////////////////////////////////////////// /* MailMessage::MailMessage() { Log = this; From = 0; Reply = 0; InternetHeader = 0; Priority = MAIL_PRIORITY_NORMAL; Text = 0; TextCharset = 0; Html = 0; HtmlCharset = 0; MarkColour = -1; DispositionNotificationTo = false; Raw = 0; } MailMessage::~MailMessage() { Empty(); DeleteObj(From); DeleteObj(Reply); DeleteObj(Raw); } int MailMessage::Write(const void *Ptr, int Size, int Flags) { LgiTrace("%.*s", Size, Ptr); return Size; } void MailMessage::Empty() { Subject.Reset(); MessageID.Reset(); References.Reset(); FwdMsgId.Reset(); BounceMsgId.Reset(); DeleteArray(InternetHeader); DeleteArray(Text); DeleteArray(TextCharset); DeleteArray(Html); DeleteArray(HtmlCharset); To.DeleteObjects(); FileDesc.DeleteObjects(); } char *MailMessage::GetBody() { return Text; } char *MailMessage::GetBodyCharset() { return TextCharset; } bool MailMessage::SetBodyCharset(const char *Cs) { DeleteArray(TextCharset); TextCharset = NewStr(Cs); return true; } bool MailMessage::SetBody(const char *Txt, int Bytes, bool Copy, const char *Cs) { if (Txt != Text) { DeleteArray(Text); Text = Copy ? NewStr(Txt, Bytes) : (char*)Txt; if (Txt && !Text) return false; } if (Cs != TextCharset) { DeleteArray(TextCharset); if (!(TextCharset = NewStr(Cs))) return false; } return true; } char *MailMessage::GetHtml() { return Html; } char *MailMessage::GetHtmlCharset() { return HtmlCharset; } bool MailMessage::SetHtmlCharset(const char *Cs) { DeleteArray(HtmlCharset); HtmlCharset = NewStr(Cs); return true; } bool MailMessage::SetHtml(const char *Txt, int Bytes, bool Copy, const char *Cs) { if (Txt != Html) { DeleteArray(Html); Html = Copy ? NewStr(Txt, Bytes) : (char*)Txt; if (Txt && !Html) return false; } if (Cs != HtmlCharset) { DeleteArray(HtmlCharset); if (!(HtmlCharset = NewStr(Cs))) return false; } return true; } int MailMessage::EncodeBase64(GStreamI &Out, GStreamI &In) { int64 Start = LgiCurrentTime(); int Status = 0; int BufSize = 4 << 10; char *InBuf = new char[BufSize]; char *OutBuf = new char[BufSize]; if (InBuf && OutBuf) { int InLen = (int)In.GetSize(); // file remaining to read int InUsed = 0; int InDone = 0; int OutUsed = 0; do { if (InUsed - InDone < 256 && InLen > 0) { // Move any bit left over down to the start memmove(InBuf, InBuf + InDone, InUsed - InDone); InUsed -= InDone; InDone = 0; // Read in as much data as we can int Max = min(BufSize-InUsed, InLen); int r = In.Read(InBuf + InUsed, Max); if (r <= 0) break; // FilePos += r; InUsed += r; InLen -= r; } if (OutUsed > BufSize - 256) { int w = Out.Write(OutBuf, OutUsed); if (w > 0) { OutUsed = 0; Status += w; } else { break; } } int OutLen = ConvertBinaryToBase64( OutBuf + OutUsed, 76, (uchar*)InBuf + InDone, InUsed - InDone); int In = OutLen * 3 / 4; InDone += In; OutUsed += OutLen; OutBuf[OutUsed++] = '\r'; OutBuf[OutUsed++] = '\n'; } while (InDone < InUsed); if (OutUsed > 0) { int w = Out.Write(OutBuf, OutUsed); if (w >= 0) Status += w; w = Out.Write((char*)"\r\n", 2); if (w >= 0) Status += w; } #if 0 double Sec = (double)((int64)LgiCurrentTime() - Start) / 1000.0; double Kb = (double)FileDes->Sizeof() / 1024.0; LgiTrace("rate: %ikb/s\n", (int)(Kb / Sec)); #endif } else Log->Print("%s:%i - Error allocating buffers\n", _FL); DeleteArray(InBuf); DeleteArray(OutBuf); return Status; } int MailMessage::EncodeQuotedPrintable(GStreamI &Out, GStreamI &In) { int Status = 0; char OutBuf[100], InBuf[1024]; int ch = 0; int InLen; // Read the input data one chunk at a time while ((InLen = In.Read(InBuf, sizeof(InBuf))) > 0) { // For all the input bytes we just got for (char *s = InBuf; s - InBuf < InLen; ) { if (*s == '\n') { ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "\r\n"); int w = Out.Write(OutBuf, ch); if (w <= 0) break; ch = 0; Status += w; } else if (*s == '.') { // If the '.' character happens to fall at the // end of a paragraph and gets pushed onto the next line it // forms the magic \r\n.\r\n sequence that ends an SMTP data // session. Which is bad. The solution taken here is to // hex encode it if it falls at the start of the line. // Otherwise allow it through unencoded. if (ch == 0) { ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=%2.2X", (uchar)*s); } else { OutBuf[ch++] = *s; } } else if (*s & 0x80 || *s == '=') { // Require hex encoding of 8-bit chars and the equals itself. ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=%2.2X", (uchar)*s); } else if (*s != '\r') { OutBuf[ch++] = *s; } s++; if (ch > 73) { // time for a new line. ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=\r\n"); int w = Out.Write(OutBuf, ch); if (w <= 0) break; ch = 0; Status += w; } } } ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "\r\n"); int w = Out.Write(OutBuf, ch); if (w > 0) Status += w; return Status; } int MailMessage::EncodeText(GStreamI &Out, GStreamI &In) { int Status = 0; char InBuf[4096]; int InLen, InUsed = 0; const char *Eol = "\r\n"; while ((InLen = In.Read(InBuf+InUsed, sizeof(InBuf)-InUsed)) > 0) { InUsed += InLen; char *s; for (s = InBuf; s - InBuf < InUsed; ) { // Do we have a complete line? int RemainingBytes = InUsed - (s - InBuf); char *NewLine = strnchr(s, '\n', RemainingBytes); if (NewLine) { // Yes... write that out. int Len = NewLine - s; if (Len > 0 && s[Len-1] == '\r') Len--; if (Len == 1 && s[0] == '.') { // this removes the sequence ".\n" // which is the END OF MAIL in the SMTP protocol. int w = Out.Write((char*)". ", 2); if (w <= 0) break; Status += w; } else if (Len) { int w = Out.Write(s, Len); if (w <= 0) break; Status += w; } int w = Out.Write(Eol, 2); if (w <= 0) break; s = NewLine + 1; Status += w; } else { // No... move the data down to the start of the buffer memmove(InBuf, s, RemainingBytes); InUsed = RemainingBytes; s = 0; break; } } if (s) InUsed -= s - InBuf; } if (InUsed) { int w = Out.Write(InBuf, InUsed); if (w > 0) Status += w; w = Out.Write(Eol, 2); if (w > 0) Status += w; } return Status; } #define SEND_BUF_SIZE (16 << 10) class SendBuf : public GStream { GStreamI *Out; int Used; uchar Buf[SEND_BUF_SIZE]; public: bool Status; SendBuf(GStreamI *out) { Out = out; Used = 0; Status = true; } ~SendBuf() { Flush(); } int64 GetSize() { return Used; } int64 SetSize(int64 Size) { return Out->SetSize(Size); } int Read(void *Buffer, int Size, int Flags = 0) { return -1; } void Flush() { if (Used > 0) { int w = Out->Write(Buf, Used, 0); if (w < Used) { Status = false; } Used = 0; } } int Write(const void *Buffer, int Size, int Flags = 0) { int64 w = 0; uchar *Ptr = (uchar*)Buffer; while (Ptr && Size > 0) { if (Size + Used >= SEND_BUF_SIZE) { int Chunk = SEND_BUF_SIZE - Used; memcpy(Buf + Used, Ptr, Chunk); int s = Out->Write(Buf, SEND_BUF_SIZE, 0); if (s < SEND_BUF_SIZE) { return -1; break; } Ptr += Chunk; Size -= Chunk; w += Chunk; Used = 0; } else { memcpy(Buf + Used, Ptr, Size); Used += Size; w += Size; Size = 0; } } return (int)w; } }; // Encode the whole email bool MailMessage::Encode(GStreamI &Out, GStream *HeadersSink, MailProtocol *Protocol, bool Mime) { GStringPipe p; bool Status = EncodeHeaders(p, Protocol, Mime); if (Status) { int Len = (int)p.GetSize(); char *Headers = p.NewStr(); if (HeadersSink) { HeadersSink->Write(Headers, Len); } else { InternetHeader = NewStr(Headers); } if (Headers && Out.Write(Headers, Len)) { SendBuf *Buf = new SendBuf(&Out); if (Buf) { Status = EncodeBody(*Buf, Protocol, Mime); if (Status) { Buf->Flush(); Status = Buf->Status; if (!Status) Log->Print("%s:%i - Buffer status failed.\n", _FL); } else Log->Print("%s:%i - EncodeBody failed.\n", _FL); DeleteObj(Buf); } } else Log->Print("%s:%i - Headers output failed.\n", _FL); DeleteArray(Headers); } else Log->Print("%s:%i - EncodeHeaders failed.\n", _FL); return Status; } #define WriteOutput() \ if (Out.Write(Buffer, Len) != Len) \ { \ Log->Print("%s:%i - Write failed.\n", _FL); \ Status = false; \ } // This encodes the main headers but not the headers relating to the // actual content. Thats done by the ::EncodeBody function. bool MailMessage::EncodeHeaders(GStreamI &Out, MailProtocol *Protocol, bool Mime) { bool Status = true; // Setup char Buffer[1025]; // Construct date const char *Weekday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char *Month[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; LDateTime Dt; int TimeZone = Dt.SystemTimeZone(); Dt.SetNow(); int Len = sprintf_s(Buffer, sizeof(Buffer), "Date: %s, %i %s %i %i:%2.2i:%2.2i %s%2.2d%2.2d\r\n", Weekday[Dt.DayOfWeek()], Dt.Day(), Month[Dt.Month()-1], Dt.Year(), Dt.Hours(), Dt.Minutes(), Dt.Seconds(), (TimeZone >= 0) ? "+" : "", TimeZone / 60, abs(TimeZone) % 60); WriteOutput(); if (Protocol && Protocol->ProgramName) { // X-Mailer: Len = sprintf_s(Buffer, sizeof(Buffer), "X-Mailer: %s\r\n", Protocol->ProgramName.Get()); WriteOutput(); } if (Protocol && Protocol->ExtraOutgoingHeaders) { for (char *s=Protocol->ExtraOutgoingHeaders; s && *s; ) { char *e = s; while (*e && *e != '\r' && *e != '\n') e++; int l = e-s; if (l > 0) { Status &= Out.Write(s, l) > 0; Status &= Out.Write((char*)"\r\n", 2) > 0; } while (*e && (*e == '\r' || *e == '\n')) e++; s = e; } if (!Status) Log->Print("%s:%i - Writing ExtraOutgoingHeaders failed.\n", _FL); } if (Priority != MAIL_PRIORITY_NORMAL) { // X-Priority: Len = sprintf_s(Buffer, sizeof(Buffer), "X-Priority: %i\r\n", Priority); WriteOutput(); } if (MarkColour >= 0) { // X-Color (HTML Colour Ref for email marking) Len = sprintf_s(Buffer, sizeof(Buffer), "X-Color: #%2.2X%2.2X%2.2X\r\n", R24(MarkColour), G24(MarkColour), B24(MarkColour)); WriteOutput(); } // Message-ID: if (MessageID) { for (char *m=MessageID; *m; m++) { if (*m <= ' ') { printf("%s:%i - Bad message ID '%s'\n", _FL, MessageID.Get()); return false; } } Len = sprintf_s(Buffer, sizeof(Buffer), "Message-ID: %s\r\n", MessageID.Get()); WriteOutput(); } // References: if (ValidStr(References)) { char *Dir = strrchr(References, '/'); GAutoString a; char *Ref = 0; if (Dir) { GUri u; a = u.Decode(Dir + 1); Ref = a; } else Ref = References; int Len = sprintf_s(Buffer, sizeof(Buffer), "References: <%s>\r\n", Ref); WriteOutput(); } // To: char *ToAddr = CreateAddressTag(To, 0, &Protocol->CharsetPrefs); if (ToAddr) { Status &= Out.Write(ToAddr, strlen(ToAddr)) > 0; DeleteArray(ToAddr); if (!Status) Log->Print("%s:%i - Writing ToAddr failed.\n", _FL); } char *CcAddr = CreateAddressTag(To, 1, &Protocol->CharsetPrefs); if (CcAddr) { Status &= Out.Write(CcAddr, strlen(CcAddr)) > 0; DeleteArray(CcAddr); if (!Status) Log->Print("%s:%i - Writing CcAddr failed.\n", _FL); } // From: if (From && From->Addr) { Len = sprintf_s(Buffer, sizeof(Buffer), "From: "); char *Nme = EncodeRfc2047(NewStr(From->Name), 0, &Protocol->CharsetPrefs); if (Nme) { if (strchr(Nme, '\"')) Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "'%s' ", Nme); else Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "\"%s\" ", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "<%s>\r\n", From->Addr); WriteOutput(); } else { Log->Print("%s:%i - No 'from' address to send email.\n", _FL); return false; } // Reply-To: if (Reply && ValidStr(Reply->Addr)) { Len = sprintf_s(Buffer, sizeof(Buffer), "Reply-To: "); char *Nme = EncodeRfc2047(NewStr(Reply->Name), 0, &Protocol->CharsetPrefs); if (Nme) { if (strchr(Nme, '\"')) Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "'%s' ", Nme); else Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "\"%s\" ", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "<%s>\r\n", Reply->Addr); WriteOutput(); } // Subject: char *Subj = EncodeRfc2047(NewStr(Subject), 0, &Protocol->CharsetPrefs, 9); Len = sprintf_s(Buffer, sizeof(Buffer), "Subject: %s\r\n", (Subj) ? Subj : ""); WriteOutput(); DeleteArray(Subj); // DispositionNotificationTo if (DispositionNotificationTo) { Len = sprintf_s(Buffer, sizeof(Buffer), "Disposition-Notification-To:"); char *Nme = EncodeRfc2047(NewStr(From->Name), 0, &Protocol->CharsetPrefs); if (Nme) { Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, " \"%s\"", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, " <%s>\r\n", From->Addr); WriteOutput(); } return Status; } bool MailMessage::EncodeBody(GStreamI &Out, MailProtocol *Protocol, bool Mime) { bool Status = true; char Buffer[1025]; if (Mime) { bool MultiPart = ((Text ? 1 : 0) + (Html ? 1 : 0) + FileDesc.Length()) > 1; bool MultipartAlternate = ValidStr(Text) && ValidStr(Html); bool MultipartMixed = FileDesc.Length() > 0; uint64 Now = LgiCurrentTime(); char Separator[256]; sprintf_s(Separator, sizeof(Separator), "----=_NextPart_%8.8X.%8.8X", (uint32)Now, (unsigned)(int64)LgiGetCurrentThread()); int Len = sprintf_s(Buffer, sizeof(Buffer), "MIME-Version: 1.0\r\n"); Status &= Out.Write(Buffer, Len) > 0; if (MultiPart) { const char *Type = MultipartMixed ? EncryptedMsg ? sMultipartEncrypted : sMultipartMixed : sMultipartAlternative; Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s;\r\n\tboundary=\"%s\"\r\n", Type, Separator); Status &= Out.Write(Buffer, Len) > 0; } if (ValidStr(Text) || ValidStr(Html)) { char AlternateBoundry[128] = ""; if (MultiPart) { Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; if (MultipartMixed && MultipartAlternate) { sprintf_s(AlternateBoundry, sizeof(AlternateBoundry), "----=_NextPart_%8.8X.%8.8X", (uint32)++Now, (uint32)(int64)LgiGetCurrentThread()); Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s;\r\n\tboundary=\"%s\"\r\n", sMultipartAlternative, AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (ValidStr(Text)) { const char *Cs = 0; char *Txt = Text, *Mem = 0; // Detect charset if (!TextCharset || _stricmp(TextCharset, "utf-8") == 0) { Cs = LgiDetectCharset(Text, -1, Protocol ? &Protocol->CharsetPrefs : 0); if (Cs) { Mem = Txt = (char*)LgiNewConvertCp(Cs, Text, "utf-8", -1); } } if (!Cs) Cs = TextCharset; // Content type Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/plain; charset=\"%s\"\r\n", Cs ? Cs : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); // Transfer encoding if (Txt && (Is8Bit(Txt) || MaxLineLen(Txt) >= 80)) { char QuotPrint[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(QuotPrint, strlen(QuotPrint)) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); GMemStream TxtStr(Txt, strlen(Txt), false); Status &= EncodeQuotedPrintable(Out, TxtStr) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); } else { char Cte[] = "Content-Transfer-Encoding: 7bit\r\n\r\n"; Status &= Out.Write(Cte, strlen(Cte)) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); GMemStream TxtStr(Txt, strlen(Txt), false); Status &= EncodeText(Out, TxtStr) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); } DeleteArray(Mem); } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); // Break alternate part if (AlternateBoundry[0]) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } else if (MultipartAlternate) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } if (ValidStr(Html)) { // Content type Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/html; charset=\"%s\"\r\n", TextCharset ? TextCharset : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; // Transfer encoding if (Is8Bit(Html) || MaxLineLen(Html) >= 80) { char Qp[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(Qp, strlen(Qp)) > 0; GMemStream HtmlStr(Html, strlen(Html), false); Status &= EncodeQuotedPrintable(Out, HtmlStr) > 0; } else { char Sb[] = "Content-Transfer-Encoding: 7bit\r\n\r\n"; Status &= Out.Write(Sb, strlen(Sb)) > 0; GMemStream HtmlStr(Html, strlen(Html), false); Status &= EncodeText(Out, HtmlStr) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (AlternateBoundry[0]) { // End alternate part Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s--\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); int SizeEst = 1024; FileDescriptor *FileDes = FileDesc.First(); for (; FileDes; FileDes=FileDesc.Next()) { SizeEst += FileDes->Sizeof() * 4 / 3; } Out.SetSize(SizeEst); FileDes = FileDesc.First(); while (FileDes) { GStreamI *F = FileDes->GotoObject(); // write a MIME segment for this attachment if (MultiPart) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } char FileName[256]; char *s = FileDes->Name(), *d = FileName; if (!s) Log->Print("%s:%i - File descriptor has no name.\n", _FL); else { while (*s) { if (*s != '\\') { *d++ = *s++; } else { *d++ = '\\'; *d++ = '\\'; s++; } } *d = 0; char *FName = EncodeRfc2047(NewStr(FileName), 0, &Protocol->CharsetPrefs); Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s; name=\"%s\"\r\n" "Content-Disposition: attachment\r\n", FileDes->GetMimeType() ? FileDes->GetMimeType() : "application/x-zip-compressed", (FName) ? FName : FileName); Status &= Out.Write(Buffer, Len) > 0; DeleteArray(FName); if (FileDes->GetContentId()) { Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Id: %s\r\n", FileDes->GetContentId()); Status &= Out.Write(Buffer, Len) > 0; } Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Transfer-Encoding: base64\r\n\r\n"); Status &= Out.Write(Buffer, Len) > 0; Status &= F ? EncodeBase64(Out, *F) > 0 : false; } FileDes = FileDesc.Next(); } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (MultiPart) { // write final separator Len = sprintf_s(Buffer, sizeof(Buffer), "--%s--\r\n\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } } else { // send content type int Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/plain; charset=\"%s\"\r\n", TextCharset ? TextCharset : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; if (Is8Bit(Text)) { // send the encoding and a blank line char Qp[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(Qp, strlen(Qp)) > 0; // send message text GMemStream TextStr(Text, strlen(Text), false); Status &= EncodeQuotedPrintable(Out, TextStr) > 0; } else { // send a blank line Status &= Out.Write((char*)"\r\n", 2) > 0; // send message text GMemStream TextStr(Text, strlen(Text), false); Status &= EncodeText(Out, TextStr) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); return true; } */ diff --git a/src/common/INet/MailImap.cpp b/src/common/INet/MailImap.cpp --- a/src/common/INet/MailImap.cpp +++ b/src/common/INet/MailImap.cpp @@ -1,3322 +1,3298 @@ #include #ifdef LINUX #include #endif #include "Lgi.h" #include "GToken.h" #include "Mail.h" #include "Base64.h" #include "INetTools.h" #include "GDocView.h" #include "IHttp.h" #include "HttpTools.h" #include "OpenSSLSocket.h" #include "LJson.h" #define DEBUG_OAUTH2 0 #ifdef _DEBUG #define DEBUG_FETCH 0 #else #define DEBUG_FETCH 0 #endif #define OPT_ImapOAuth2AccessToken "OAuth2AccessTok" #undef _FL #define _FL LgiGetLeaf(__FILE__), __LINE__ //////////////////////////////////////////////////////////////////////////// #if GPL_COMPATIBLE #include "AuthNtlm/Ntlm.h" #else #include "../../src/common/INet/libntlm-0.4.2/ntlm.h" #endif #if HAS_LIBGSASL #include "gsasl.h" #endif static const char *sRfc822Header = "RFC822.HEADER"; static const char *sRfc822Size = "RFC822.SIZE"; -bool Base64Str(GString &s) -{ - GString b64; - ssize_t Base64Len = BufferLen_BinTo64(s.Length()); - if (!b64.Set(NULL, Base64Len)) - return false; - - ssize_t Ch = ConvertBinaryToBase64(b64.Get(), b64.Length(), (uchar*)s.Get(), s.Length()); - LgiAssert(Ch == b64.Length()); - s = b64; - return true; -} - -bool UnBase64Str(GString &s) -{ - GString Bin; - ssize_t BinLen = BufferLen_64ToBin(s.Length()); - if (!Bin.Set(NULL, BinLen)) - return false; - - ssize_t Ch = ConvertBase64ToBinary((uchar*)Bin.Get(), Bin.Length(), s.Get(), s.Length()); - LgiAssert(Ch <= (int)Bin.Length()); - s = Bin; - s.Get()[Ch] = 0; - return true; -} - struct TraceLog : public GStream { ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return 0; } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { LgiTrace("%.*s", (int)Size, Buffer); return Size; } }; /* #define SkipWhiteSpace(s) while (*s && IsWhiteSpace(*s)) s++; bool JsonDecode(GXmlTag &t, const char *s) { if (*s != '{') return false; s++; while (*s) { SkipWhiteSpace(s); if (*s != '\"') break; GAutoString Variable(LgiTokStr(s)); SkipWhiteSpace(s); if (*s != ':') return false; s++; SkipWhiteSpace(s); GAutoString Value(LgiTokStr(s)); SkipWhiteSpace(s); t.SetAttr(Variable, Value); if (*s != ',') break; s++; } if (*s != '}') return false; s++; return true; } */ #define SkipWhite(s) while (*s && strchr(WhiteSpace, *s)) s++ #define SkipSpaces(s) while (*s && strchr(" \t", *s)) s++ #define SkipNonWhite(s) while (*s && !strchr(WhiteSpace, *s)) s++; #define ExpectChar(ch) if (*s != ch) return 0; s++ ssize_t MailIMap::ParseImapResponse(char *Buffer, ssize_t BufferLen, GArray &Ranges, int Names) { Ranges.Length(0); if (!*Buffer || *Buffer != '*') return 0; char *End = Buffer + BufferLen; char *s = Buffer + 1; char *Start; for (int n=0; nOpen(u.Host, u.Port?u.Port:443)) return false; ssize_t w = S->Write(Req, ReqLen); if (w != ReqLen) return false; char Buf[256]; GArray Res; ssize_t r; ssize_t ContentLen = 0; ssize_t HdrLen = 0; while ((r = S->Read(Buf, sizeof(Buf))) > 0) { ssize_t Old = Res.Length(); Res.Length(Old + r); memcpy(&Res[Old], Buf, r); if (ContentLen) { if (Res.Length() >= HdrLen + ContentLen) break; } else { char *Eoh = strnstr(&Res[0], "\r\n\r\n", Res.Length()); if (Eoh) { HdrLen = Eoh - &Res[0]; GAutoString c(InetGetHeaderField(&Res[0], "Content-Length", HdrLen)); if (c) { ContentLen = atoi(c); } } } } char *Rp = &Res[0]; char *Eoh = strnstr(Rp, "\r\n\r\n", Res.Length()); if (Eoh) { if (OutHeaders) OutHeaders->Reset(NewStr(Rp, Eoh-Rp)); if (OutBody) OutBody->Reset(NewStr(Eoh + 4, Res.Length() - (Eoh-Rp) - 4)); if (StatusCode) { *StatusCode = 0; char *Eol = strchr(Rp, '\n'); if (Eol) { GToken t(Rp, " \t\r\n", true, Eol - Rp); if (t.Length() > 2) { *StatusCode = atoi(t[1]); } } } } else return false; #ifndef _DEBUG GFile f; if (f.Open("c:\\temp\\http.html", O_WRITE)) { f.SetSize(0); f.Write(&Res[0], Res.Length()); f.Close(); } #endif return true; } GAutoString ImapBasicTokenize(char *&s) { if (s) { while (*s && strchr(WhiteSpace, *s)) s++; char start = 0, end = 0; if (*s == '\'' || *s == '\"') start = end = *s; else if (*s == '[') { start = '['; end = ']'; } else if (*s == '(') { start = '('; end = ')'; } else if (*s == '{') { start = '{'; end = '}'; } if (start && end) { s++; char *e = strchr(s, end); if (e) { char *n = NewStr(s, e - s); s = e + 1; return GAutoString(n); } } else { char *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; if (e > s) { char *n = NewStr(s, e - s); s = e + (*e != 0); return GAutoString(n); } } } s += strlen(s); return GAutoString(); } char *Tok(char *&s) { char *Ret = 0; while (*s && strchr(WhiteSpace, *s)) s++; if (*s == '=' || *s == ',') { Ret = NewStr(s++, 1); } else if (*s == '\'' || *s == '\"') { char d = *s++; char *e = strchr(s, d); if (e) { Ret = NewStr(s, e - s); s = e + 1; } } else if (*s) { char *e; for (e=s; *e && (IsDigit(*e) || IsAlpha(*e) || *e == '-'); e++); Ret = NewStr(s, e - s); s = e; } return Ret; } char *DecodeImapString(const char *s) { GStringPipe p; while (s && *s) { if (*s == '&') { char Escape = *s++; const char *e = s; while (*e && *e != '-') { e++; } ssize_t Len = e - s; if (Len) { char *Base64 = new char[Len + 4]; if (Base64) { memcpy(Base64, s, Len); char *n = Base64 + Len; for (ssize_t i=Len; i%4; i++) *n++ = '='; *n++ = 0; Len = strlen(Base64); ssize_t BinLen = BufferLen_64ToBin(Len); uint16 *Bin = new uint16[(BinLen/2)+1]; if (Bin) { BinLen = ConvertBase64ToBinary((uchar*)Bin, BinLen, Base64, Len); if (BinLen) { ssize_t Chars = BinLen / 2; Bin[Chars] = 0; for (int i=0; i>8) | ((Bin[i]&0xff)<<8); } char *c8 = WideToUtf8((char16*)Bin, BinLen); if (c8) { p.Push(c8); DeleteArray(c8); } } DeleteArray(Bin); } DeleteArray(Base64); } } else { p.Push(&Escape, 1); } s = e + 1; } else { p.Push(s, 1); s++; } } return p.NewStr(); } char *EncodeImapString(const char *s) { GStringPipe p; ssize_t Len = s ? strlen(s) : 0; while (s && *s) { int c = LgiUtf8To32((uint8*&)s, Len); DoNextChar: if ((c >= ' ' && c < 0x80) || c == '\n' || c == '\t' || c == '\r') { // Literal char ch = c; p.Push(&ch, 1); } else { // Encoded GArray Str; Str[0] = c; while ((c = LgiUtf8To32((uint8*&)s, Len))) { if ((c >= ' ' && c < 0x80) || c == '\n' || c == '\t' || c == '\r') { break; } else { Str[Str.Length()] = c; } } for (uint32 i=0; i>8) | ((Str[i]&0xff)<<8); } ssize_t BinLen = Str.Length() << 1; ssize_t BaseLen = BufferLen_BinTo64(BinLen); char *Base64 = new char[BaseLen+1]; if (Base64) { ssize_t Bytes = ConvertBinaryToBase64(Base64, BaseLen, (uchar*)&Str[0], BinLen); while (Bytes > 0 && Base64[Bytes-1] == '=') { Base64[Bytes-1] = 0; Bytes--; } Base64[Bytes] = 0; p.Print("&%s-", Base64); DeleteArray(Base64); } goto DoNextChar; } } return p.NewStr(); } void ChopNewLine(char *Str) { char *End = Str+strlen(Str)-1; if (*End == '\n') { *End-- = 0; } if (*End == '\r') { *End-- = 0; } } MailImapFolder::MailImapFolder() { Sep = '/'; Path = 0; NoSelect = false; NoInferiors = false; Marked = false; Exists = -1; Recent = -1; // UnseenIndex = -1; Deleted = 0; } MailImapFolder::~MailImapFolder() { DeleteArray(Path); } void MailImapFolder::operator =(LHashTbl,int> &v) { int o = v.Find("exists"); if (o >= 0) Exists = o; o = v.Find("recent"); if (o >= 0) Recent = o; } char *MailImapFolder::GetPath() { return Path; } void MailImapFolder::SetPath(const char *s) { char *NewPath = DecodeImapString(s); DeleteArray(Path); Path = NewPath; } char *MailImapFolder::GetName() { if (Path) { char *s = strrchr(Path, Sep); if (s) { return s + 1; } else { return Path; } } return 0; } void MailImapFolder::SetName(const char *s) { if (s) { char Buf[256]; strcpy_s(Buf, sizeof(Buf), Path?Path:(char*)""); DeleteArray(Path); char *Last = strrchr(Buf, Sep); if (Last) { Last++; strcpy_s(Last, sizeof(Buf)-(Last-Buf), s); Path = NewStr(Buf); } else { Path = NewStr(s); } } } ///////////////////////////////////////////// class MailIMapPrivate : public LMutex { public: int NextCmd; bool Logging; bool ExpungeOnExit; char FolderSep; char *Current; char *Flags; LHashTbl,bool> Capability; GString WebLoginUri; GViewI *ParentWnd; LCancel *Cancel; OsThread InCommand; GString LastWrite; MailIMapPrivate() : LMutex("MailImapSem") { ParentWnd = NULL; FolderSep = '/'; NextCmd = 1; Logging = true; ExpungeOnExit = true; Current = 0; Flags = 0; InCommand = 0; Cancel = NULL; } ~MailIMapPrivate() { DeleteArray(Current); DeleteArray(Flags); } }; MailIMap::MailIMap() { d = new MailIMapPrivate; Buffer[0] = 0; } MailIMap::~MailIMap() { if (Lock(_FL)) { ClearDialog(); ClearUid(); DeleteObj(d); } } bool MailIMap::Lock(const char *file, int line) { if (!d->Lock(file, line)) return false; return true; } bool MailIMap::LockWithTimeout(int Timeout, const char *file, int line) { if (!d->LockWithTimeout(Timeout, file, line)) return false; return true; } void MailIMap::Unlock() { d->Unlock(); d->InCommand = 0; } void MailIMap::SetCancel(LCancel *Cancel) { d->Cancel = Cancel; } void MailIMap::SetParentWindow(GViewI *wnd) { d->ParentWnd = wnd; } const char *MailIMap::GetWebLoginUri() { return d->WebLoginUri; } bool MailIMap::IsOnline() { return Socket ? Socket->IsOpen() : 0; } char MailIMap::GetFolderSep() { return d->FolderSep; } char *MailIMap::GetCurrentPath() { return d->Current; } bool MailIMap::GetExpungeOnExit() { return d->ExpungeOnExit; } void MailIMap::SetExpungeOnExit(bool b) { d->ExpungeOnExit = b; } void MailIMap::ClearUid() { if (Lock(_FL)) { Uid.DeleteArrays(); Unlock(); } } void MailIMap::ClearDialog() { if (Lock(_FL)) { Dialog.DeleteArrays(); Unlock(); } } bool MailIMap::WriteBuf(bool ObsurePass, const char *Buffer, bool Continuation) { if (Socket) { if (!Buffer) Buffer = Buf; ssize_t Len = strlen(Buffer); d->LastWrite = Buffer; if (!Continuation && d->InCommand) { GString Msg; Msg.Printf("%s:%i - WriteBuf failed(%s)\n", LgiGetLeaf(__FILE__), __LINE__, d->LastWrite.Strip().Get()); Socket->OnInformation(Msg); LgiAssert(!"Can't be issuing new commands while others are still running."); return false; } /* else { GString Msg; Msg.Printf("%s:%i - WriteBuf ok(%s)\n", LgiGetLeaf(__FILE__), __LINE__, d->LastWrite.Strip().Get()); Socket->OnInformation(Msg); } */ if (Socket->Write((void*)Buffer, Len, 0) == Len) { if (ObsurePass) { char *Sp = (char*)strrchr(Buffer, ' '); if (Sp) { Sp++; GString s; s.Printf("%.*s********\r\n", Sp - Buffer, Buffer); Log(s.Get(), GSocketI::SocketMsgSend); } } else Log(Buffer, GSocketI::SocketMsgSend); d->InCommand = LgiGetCurrentThread(); return true; } else Log("Failed to write data to socket.", GSocketI::SocketMsgError); } else Log("Not connected.", GSocketI::SocketMsgError); return false; } bool MailIMap::Read(GStreamI *Out, int Timeout) { int Lines = 0; while (!Lines && Socket) { ssize_t r = Socket->Read(Buffer, sizeof(Buffer)); if (Timeout > 0 && Socket->IsOpen() && r <= 0) { auto St = LgiCurrentTime(); auto Rd = Socket->IsReadable(Timeout); auto End = LgiCurrentTime(); if (Rd) { r = Socket->Read(Buffer, sizeof(Buffer)); if (r < 0) { Socket->Close(); LgiTrace("%s:%i - Wut? IsReadable/Read mismatch.\n", _FL); return false; } } else { if (End - St < Timeout - 20) LgiTrace("%s:%i - IsReadable broken (again)\n", _FL); return false; } } if (r > 0) { ReadBuf.Push(Buffer, r); while (ReadBuf.Pop(Buffer, sizeof(Buffer))) { // Trim trailing whitespace char *e = Buffer + strlen(Buffer) - 1; while (e > Buffer && strchr(WhiteSpace, *e)) *e-- = 0; Lines++; if (Out) { Out->Write(Buffer, strlen(Buffer)); Out->Write((char*)"\r\n", 2); } else { Dialog.Add(NewStr(Buffer)); } } } else break; } return Lines > 0; } bool MailIMap::IsResponse(const char *Buf, int Cmd, bool &Ok) { char Num[8]; int Ch = sprintf_s(Num, sizeof(Num), "A%4.4i ", Cmd); if (!Buf || _strnicmp(Buf, Num, Ch) != 0) return false; Ok = _strnicmp(Buf+Ch, "OK", 2) == 0; if (!Ok) SetError(L_ERROR_GENERIC, "Error: %s", Buf+Ch); return true; } bool MailIMap::ReadResponse(int Cmd, bool Plus) { bool Done = false; bool Status = false; if (Socket) { ssize_t Pos = Dialog.Length(); while (!Done) { if (Read(NULL)) { for (char *Dlg = Dialog[Pos]; !Done && Dlg; Dlg = Dialog.Next()) { Pos++; if (Cmd < 0 || (Plus && *Dlg == '+')) { Status = Done = true; } if (IsResponse(Dlg, Cmd, Status)) Done = true; if (d->Logging) { bool Good = strchr("*+", *Dlg) != NULL || Status; Log(Dlg, Good ? GSocketI::SocketMsgReceive : GSocketI::SocketMsgError); } } } else { // LgiTrace("%s:%i - 'Read' failed.\n", _FL); break; } } } return Status; } void Hex(char *Out, int OutLen, uchar *In, ssize_t InLen = -1) { if (Out && In) { if (InLen < 0) InLen = strlen((char*)In); for (int i=0; i 0) { Out += ch; OutLen -= ch; } else break; } } } void _unpack(void *ptr, int ptrsize, char *b64) { ConvertBase64ToBinary((uchar*) ptr, ptrsize, b64, strlen(b64)); } bool MailIMap::ReadLine() { int Len = 0; Buf[0] = 0; do { ssize_t r = Socket->Read(Buf+Len, sizeof(Buf)-Len); if (r < 1) return false; Len += r; } while (!stristr(Buf, "\r\n")); Log(Buf, GSocketI::SocketMsgReceive); return true; } #if HAS_LIBGSASL int GsaslCallback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) { return 0; } #endif class OAuthWebServer : public LThread, public LMutex { bool Loop; int Port; GSocket Listen; GAutoString Req; GString Resp; bool Finished; public: OAuthWebServer(int DesiredPort = 0) : LThread("OAuthWebServerThread"), LMutex("OAuthWebServerMutex") { Loop = false; if (Listen.Listen(DesiredPort)) { Port = Listen.GetLocalPort(); Run(); } else Port = 0; Finished = false; } ~OAuthWebServer() { if (Loop) { Loop = false; while (!IsExited()) LgiSleep(10); } } int GetPort() { return Port; } GString GetRequest(LCancel *Loop, uint64 TimeoutMs = 0) { GString r; uint64 Start = LgiCurrentTime(); while (!r && (!Loop || !Loop->IsCancelled())) { if (Lock(_FL)) { if (Req) r = Req; Unlock(); } if (TimeoutMs) { uint64 Now = LgiCurrentTime(); if (Now - Start >= TimeoutMs) break; } if (!r) LgiSleep(50); } return r; } void SetResponse(const char *r) { if (Lock(_FL)) { Resp = r; Unlock(); } } bool IsFinished() { return Finished; } int Main() { GAutoPtr s; Loop = true; while (Loop) { if (Listen.CanAccept(100)) { s.Reset(new GSocket); if (!Listen.Accept(s)) s.Reset(); else { GArray Mem; ssize_t r; char buf[512]; do { r = s->Read(buf, sizeof(buf)); if (r > 0) { Mem.Add(buf, r); bool End = strnstr(&Mem[0], "\r\n\r\n", Mem.Length()) != NULL; if (End) break; } } while (r > 0); if (Lock(_FL)) { Mem.Add(0); Req.Reset(Mem.Release()); Unlock(); } // Wait for the response... GString Response; do { if (Lock(_FL)) { if (Resp) Response = Resp; Unlock(); } if (!Response) LgiSleep(10); } while (Loop && !Response); if (Response) s->Write(Response, Response.Length()); Loop = false; } } else LgiSleep(10); } Finished = true; return 0; } }; static void AddIfMissing(GArray &Auths, const char *a, GString *DefaultAuthType = NULL) { for (unsigned i=0; iLastWrite.Strip().Get()); Socket->OnInformation(Msg); */ d->InCommand = 0; d->LastWrite.Empty(); } -bool MailIMap::Open(GSocketI *s, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *SettingStore, int Flags) +bool MailIMap::Open(GSocketI *s, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *settingStore, int Flags) { bool Status = false; + + if (settingStore) + SettingStore = settingStore; if (SocketLock.Lock(_FL)) { Socket.Reset(s); SocketLock.Unlock(); } if (Socket && ValidStr(RemoteHost) && ValidStr(User) && ( ValidStr(Password) || OAuth2.IsValid() ) && Lock(_FL)) { // prepare address if (Port < 1) { if (Flags & MAIL_SSL) Port = IMAP_SSL_PORT; else Port = IMAP_PORT; } char Remote[256]; strcpy_s(Remote, sizeof(Remote), RemoteHost); char *Colon = strchr(Remote, ':'); if (Colon) { *Colon++ = 0; Port = atoi(Colon); } // Set SSL mode GVariant v; if (Flags == MAIL_SSL) v = "SSL"; Socket->SetValue(GSocket_Protocol, v); // connect if (Socket->Open(Remote, Port)) { bool IMAP4Server = false; GArray Auths; // check capability int CapCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i CAPABILITY\r\n", CapCmd); if (WriteBuf()) { bool Rd = ReadResponse(CapCmd); CommandFinished(); if (Rd) { for (char *r=Dialog.First(); r; r=Dialog.Next()) { GToken T(r, " "); if (T.Length() > 1 && _stricmp(T[1], "CAPABILITY") == 0) { for (unsigned i=2; i 0) { Auths.DeleteAt(n); Auths.AddAt(0, DefaultAuthType); break; } } // SSL bool TlsError = false; if (TestFlag(Flags, MAIL_USE_STARTTLS)) { int CapCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STARTTLS\r\n", CapCmd); if (WriteBuf()) { bool Rd = ReadResponse(CapCmd); CommandFinished(); if (Rd) { GVariant v; TlsError = !Socket->SetValue(GSocket_Protocol, v="SSL"); } else { TlsError = true; } } else LgiAssert(0); if (TlsError) { Log("STARTTLS failed", GSocketI::SocketMsgError); } } // login bool LoggedIn = false; char AuthTypeStr[256] = ""; for (unsigned i=0; i 0) { strconcat(AuthTypeStr, ", "); } strconcat(AuthTypeStr, AuthType); } // Do auth #if HAS_LIBGSASL if (!_stricmp(AuthType, "GSSAPI")) { int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%04.4i AUTHENTICATE GSSAPI\r\n", AuthCmd); if (WriteBuf() && ReadLine() && Buf[0] == '+') { // Start GSSAPI Gsasl *ctx = NULL; Gsasl_session *sess = NULL; int rc = gsasl_init(&ctx); if (rc == GSASL_OK) { char *mechs; rc = gsasl_client_mechlist(ctx, &mechs); gsasl_callback_set(ctx, GsaslCallback); rc = gsasl_client_start(ctx, AuthType, &sess); if (rc != GSASL_OK) { Log("gsasl_client_start failed", GSocketI::SocketMsgError); } // gsasl_step(ctx, gsasl_done(ctx); } else Log("gsasl_init failed", GSocketI::SocketMsgError); } else Log("AUTHENTICATE GSSAPI failed", GSocketI::SocketMsgError); } else #endif if (_stricmp(AuthType, "LOGIN") == 0 || _stricmp(AuthType, "OTP") == 0) { // clear text authentication int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LOGIN %s %s\r\n", AuthCmd, User, Password); if (WriteBuf(true)) { LoggedIn = ReadResponse(AuthCmd); CommandFinished(); } } else if (_stricmp(AuthType, "PLAIN") == 0) { // plain auth type char s[256]; char *e = s; *e++ = 0; strcpy_s(e, sizeof(s)-(e-s), User); e += strlen(e); e++; strcpy_s(e, sizeof(s)-(e-s), Password); e += strlen(e); *e++ = '\r'; *e++ = '\n'; ssize_t Len = e - s - 2; int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE PLAIN\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd, true)) { ssize_t b = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*)s, Len); strcpy_s(Buf+b, sizeof(Buf)-b, "\r\n"); if (WriteBuf(false, NULL, true)) { bool Rd = ReadResponse(AuthCmd); CommandFinished(); if (Rd) { LoggedIn = true; } else { // Look for WEBALERT from Google for (char *s = Dialog.First(); s; s = Dialog.Next()) { char *start = strchr(s, '['); char *end = start ? strrchr(start, ']') : NULL; if (start && end) { start++; if (_strnicmp(start, "WEBALERT", 8) == 0) { start += 8; while (*start && strchr(WhiteSpace, *start)) start++; d->WebLoginUri.Set(start, end - start); } } } } } } } } #if (GPL_COMPATIBLE || defined(_LIBNTLM_H)) && defined(WINNATIVE) else if (_stricmp(AuthType, "NTLM") == 0) { // NT Lan Man authentication OSVERSIONINFO ver; ZeroObj(ver); ver.dwOSVersionInfoSize = sizeof(ver); if (!GetVersionEx(&ver)) { DWORD err = GetLastError(); Log("Couldn't get OS version", GSocketI::SocketMsgError); } else { // Username is in the format: User[@Domain] char UserDom[256]; strcpy_s(UserDom, sizeof(UserDom), User); char *Domain = strchr(UserDom, '@'); if (Domain) *Domain++ = 0; int AuthCmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%04.4i AUTHENTICATE NTLM\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd, true)) { tSmbNtlmAuthNegotiate negotiate; tSmbNtlmAuthChallenge challenge; tSmbNtlmAuthResponse response; buildSmbNtlmAuthNegotiate(&negotiate, 0, 0); if (NTLM_VER(&negotiate) == 2) { negotiate.v2.version.major = (uint8) ver.dwMajorVersion; negotiate.v2.version.minor = (uint8) ver.dwMinorVersion; negotiate.v2.version.buildNumber = (uint16) ver.dwBuildNumber; negotiate.v2.version.ntlmRevisionCurrent = 0x0f; } ZeroObj(Buf); int negotiateLen = SmbLength(&negotiate); int c = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*)&negotiate, negotiateLen); strcpy_s(Buf+c, sizeof(Buf)-c, "\r\n"); WriteBuf(false, NULL, true); /* read challange data from server, convert from base64 */ Buf[0] = 0; ClearDialog(); if (ReadResponse()) { /* buffer should contain the string "+ [base 64 data]" */ #if 1 ZeroObj(challenge); char *Line = Dialog.First(); LgiAssert(Line != NULL); ChopNewLine(Line); int LineLen = strlen(Line); int challengeLen = sizeof(challenge); c = ConvertBase64ToBinary((uchar*) &challenge, sizeof(challenge), Line+2, LineLen-2); if (NTLM_VER(&challenge) == 2) challenge.v2.bufIndex = c - (challenge.v2.buffer-(uint8*)&challenge); else challenge.v1.bufIndex = c - (challenge.v1.buffer-(uint8*)&challenge); #endif /* prepare response, convert to base64, send to server */ ZeroObj(response); FILETIME time = {0, 0}; SYSTEMTIME stNow; GetSystemTime(&stNow); SystemTimeToFileTime(&stNow, &time); char HostName[256] = ""; gethostname(HostName, sizeof(HostName)); buildSmbNtlmAuthResponse(&challenge, &response, UserDom, HostName, Domain, Password, (uint8*)&time); if (NTLM_VER(&response) == 2) { response.v2.version.major = (uint8) ver.dwMajorVersion; response.v2.version.minor = (uint8) ver.dwMinorVersion; response.v2.version.buildNumber = (uint16) ver.dwBuildNumber; response.v2.version.ntlmRevisionCurrent = 0x0f; } #if 0 { uint8 *r1 = (uint8*)&response; uint8 *r2 = (uint8*)&response_good; for (int i=0; iNextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE DIGEST-MD5\r\n", AuthCmd); if (WriteBuf()) { if (ReadResponse(AuthCmd)) { char *TestCnonce = 0; #if 0 // Test case strcpy(Buf, "+ cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hhcnNldD11dGYtOA=="); RemoteHost = "elwood.innosoft.com"; User = "chris"; Password = "secret"; TestCnonce = "OA6MHXh6VqTrRk"; #endif char *In = (char*)Buf; if (In[0] == '+' && In[1] == ' ') { In += 2; uchar Out[2048]; ssize_t b = ConvertBase64ToBinary(Out, sizeof(Out), In, strlen(In)); Out[b] = 0; LHashTbl, char*> Map; char *s = (char*)Out; while (s && *s) { char *Var = Tok(s); char *Eq = Tok(s); char *Val = Tok(s); char *Comma = Tok(s); if (Var && Eq && Val && strcmp(Eq, "=") == 0) { Map.Add(Var, Val); Val = 0; } DeleteArray(Var); DeleteArray(Eq); DeleteArray(Val); DeleteArray(Comma); } int32 CnonceI[2] = { (int32)LgiRand(), (int32)LgiRand() }; char Cnonce[32]; if (TestCnonce) strcpy_s(Cnonce, sizeof(Cnonce), TestCnonce); else Cnonce[ConvertBinaryToBase64(Cnonce, sizeof(Cnonce), (uchar*)&CnonceI, sizeof(CnonceI))] = 0; s = strchr(Cnonce, '='); if (s) *s = 0; int Nc = 1; char *Realm = Map.Find("realm"); char DigestUri[256]; sprintf_s(DigestUri, sizeof(DigestUri), "imap/%s", Realm ? Realm : RemoteHost); GStringPipe p; p.Print("username=\"%s\"", User); p.Print(",nc=%08.8i", Nc); p.Print(",digest-uri=\"%s\"", DigestUri); p.Print(",cnonce=\"%s\"", Cnonce); char *Nonce = Map.Find("nonce"); if (Nonce) { p.Print(",nonce=\"%s\"", Nonce); } if (Realm) { p.Print(",realm=\"%s\"", Realm); } char *Charset = Map.Find("charset"); if (Charset) { p.Print(",charset=%s", Charset); } char *Qop = Map.Find("qop"); if (Qop) { p.Print(",qop=%s", Qop); } // Calculate A1 char a1[256]; uchar md5[16]; sprintf_s(Buf, sizeof(Buf), "%s:%s:%s", User, Realm ? Realm : (char*)"", Password); MDStringToDigest((uchar*)a1, Buf); char *Authzid = Map.Find("authzid"); int ch = 16; if (Authzid) ch += sprintf_s(a1+ch, sizeof(a1)-ch, ":%s:%s:%s", Nonce, Cnonce, Authzid); else ch += sprintf_s(a1+ch, sizeof(a1)-ch, ":%s:%s", Nonce, Cnonce); MDStringToDigest(md5, a1, ch); char a1hex[256]; Hex(a1hex, sizeof(a1hex), (uchar*)md5, sizeof(md5)); // Calculate char a2[256]; if (Qop && (_stricmp(Qop, "auth-int") == 0 || _stricmp(Qop, "auth-conf") == 0)) sprintf_s(a2, sizeof(a2), "AUTHENTICATE:%s:00000000000000000000000000000000", DigestUri); else sprintf_s(a2, sizeof(a2), "AUTHENTICATE:%s", DigestUri); MDStringToDigest(md5, a2); char a2hex[256]; Hex(a2hex, sizeof(a2hex), (uchar*)md5, sizeof(md5)); // Calculate the final response sprintf_s(Buf, sizeof(Buf), "%s:%s:%8.8i:%s:%s:%s", a1hex, Nonce, Nc, Cnonce, Qop, a2hex); MDStringToDigest(md5, Buf); Hex(Buf, sizeof(Buf), (uchar*)md5, sizeof(md5)); p.Print(",response=%s", Buf); if ((s = p.NewStr())) { ssize_t Chars = ConvertBinaryToBase64(Buf, sizeof(Buf) - 4, (uchar*)s, strlen(s)); LgiAssert(Chars < sizeof(Buf)); strcpy_s(Buf+Chars, sizeof(Buf)-Chars, "\r\n"); if (WriteBuf(false, NULL, true) && Read()) { for (char *Dlg = Dialog.First(); Dlg; Dlg=Dialog.Next()) { if (Dlg[0] == '+' && Dlg[1] == ' ') { Log(Dlg, GSocketI::SocketMsgReceive); strcpy_s(Buf, sizeof(Buf), "\r\n"); if (WriteBuf(false, NULL, true)) { LoggedIn = ReadResponse(AuthCmd); } } else { Log(Dlg, GSocketI::SocketMsgError); break; } } } DeleteArray(s); } } } CommandFinished(); } } else if (!_stricmp(AuthType, "XOAUTH2")) { if (stristr(RemoteHost, "office365.com")) { Log("office365.com doesn't support OAUTH2:", GSocketI::SocketMsgInfo); Log("\thttps://stackoverflow.com/questions/29747477/imap-auth-in-office-365-using-oauth2", GSocketI::SocketMsgInfo); Log("\tSo why does it report support in the CAPABILITY response? Don't ask me - fret", GSocketI::SocketMsgInfo); continue; } else if (!OAuth2.IsValid()) { sprintf_s(Buf, sizeof(Buf), "Error: Unknown OAUTH2 server '%s' (ask fret@memecode.com to add)", RemoteHost); Log(Buf, GSocketI::SocketMsgError); continue; } TraceLog TLog; - LOAuth2 Auth(OAuth2, User, &TLog); + LOAuth2 Auth(OAuth2, User, SettingStore, &TLog); auto AccessToken = Auth.GetAccessToken(); if (!AccessToken) { sprintf_s(Buf, sizeof(Buf), "Warning: No OAUTH2 Access Token."); #if DEBUG_OAUTH2 LgiTrace("%s:%i - %s.\n", _FL, Buf); #endif Log(Buf, GSocketI::SocketMsgWarning); break; } // Construct the XOAUTH2 parameter GString s; s.Printf("user=%s\001auth=Bearer %s\001\001", User, AccessToken.Get()); #if DEBUG_OAUTH2 LgiTrace("%s:%i - s=%s.\n", _FL, s.Replace("\001", "%01").Get()); #endif Base64Str(s); // Issue the IMAP command int AuthCmd = d->NextCmd++; GString AuthStr; AuthStr.Printf("A%4.4i AUTHENTICATE XOAUTH2 %s\r\n", AuthCmd, s.Get()); if (WriteBuf(false, AuthStr)) { Dialog.DeleteArrays(); if (Read(NULL)) { for (char *l = Dialog.First(); l; l = Dialog.Next()) { if (*l == '+') { l++; while (*l && strchr(WhiteSpace, *l)) l++; s = l; UnBase64Str(s); Log(s, GSocketI::SocketMsgError); LJson t; t.SetJson(s); int StatusCode = (int)t.Get("status").Int(); LgiTrace("%s:%i - HTTP status: %i\n%s\n", _FL, StatusCode, s.Get()); sprintf_s(Buf, sizeof(Buf), "\r\n"); WriteBuf(false, NULL, true); if (StatusCode == 400) { // Refresh the token...? } } else if (*l == '*') { Log(l, GSocketI::SocketMsgReceive); } else { if (IsResponse(l, AuthCmd, LoggedIn) && LoggedIn) { Log(l, GSocketI::SocketMsgReceive); if (SettingStore) { // Login successful, so persist the AuthCode for next time GVariant v = AccessToken.Get(); bool b = SettingStore->SetValue(OPT_ImapOAuth2AccessToken, v); if (!b) { Log("Couldn't store access token.", GSocketI::SocketMsgWarning); } } break; } else { Log(l, GSocketI::SocketMsgError); } } } } CommandFinished(); } if (!LoggedIn && SettingStore) { GVariant v; SettingStore->SetValue(OPT_ImapOAuth2AccessToken, v); break; } } else { char s[256]; sprintf_s(s, sizeof(s), "Warning: Unsupported authentication type '%s'", AuthType); Log(s, GSocketI::SocketMsgWarning); } } if (LoggedIn) { Status = true; // Ask server for it's heirarchy (folder) separator. int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LIST \"\" \"\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { for (char *Dlg = Dialog.First(); Dlg; Dlg=Dialog.Next()) { GArray t; char *s = Dlg; while (*s) { GAutoString a = ImapBasicTokenize(s); if (a) t.New() = a; else break; } if (t.Length() >= 5 && strcmp(t[0], "*") == 0 && _stricmp(t[1], "list") == 0) { for (unsigned i=2; iFolderSep = *s; break; } } break; } } } CommandFinished(); } } else { SetError(L_ERROR_UNSUPPORTED_AUTH, "Authentication failed, types available:\n\t%s", ValidStr(AuthTypeStr) ? AuthTypeStr : "(none)"); } } } Unlock(); } return Status; } bool MailIMap::Close() { bool Status = false; if (Socket && Lock(_FL)) { if (d->ExpungeOnExit) { ExpungeFolder(); } int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LOGOUT\r\n", Cmd); if (WriteBuf()) { Status = true; } CommandFinished(); Unlock(); } return Status; } bool MailIMap::GetCapabilities(GArray &s) { // char *k = 0; // for (bool p=d->Capability.First(&k); p; p=d->Capability.Next(&k)) for (auto i : d->Capability) { s.Add(i.key); } return s.Length() > 0; } bool MailIMap::ServerOption(char *Opt) { return d->Capability.Find(Opt) != 0; } char *MailIMap::GetSelectedFolder() { return d->Current; } bool MailIMap::SelectFolder(const char *Path, StrMap *Values) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; char *Enc = EncodePath(Path); sprintf_s(Buf, sizeof(Buf), "A%4.4i SELECT \"%s\"\r\n", Cmd, Enc); DeleteArray(Enc); if (WriteBuf()) { DeleteArray(d->Current); ClearDialog(); if (ReadResponse(Cmd)) { Uid.DeleteArrays(); if (Values) { for (GString Dlg = Dialog.First(); Dlg; Dlg = Dialog.Next()) { GString::Array t = Dlg.SplitDelimit(" []"); if (t.Length() > 0 && t[0].Equals("*")) { for (unsigned i=1; iAdd(t[i], t[i-1]); } else if (t[i].Equals("unseen")) { //char *val = t[i+1]; if (t[i+1].IsNumeric()) Values->Add(t[i], t[i+1]); } else if (t[i].Equals("flags")) { ssize_t s = Dlg.Find("("); ssize_t e = Dlg.Find(")", s + 1); if (e >= 0) { GString Val = Dlg(s+1, e); Values->Add(t[i], Val); } } } } } } Status = true; d->Current = NewStr(Path); ClearDialog(); } CommandFinished(); } Unlock(); } return Status; } int MailIMap::GetMessages(const char *Path) { int Status = 0; if (Socket && Lock(_FL)) { StrMap f; if (SelectFolder(Path, &f)) { GString Exists = f.Find("exists"); if (Exists && Exists.Int() >= 0) Status = (int)Exists.Int(); else LgiTrace("%s:%i - Failed to get 'exists' value.\n", _FL); } Unlock(); } return Status; } int MailIMap::GetMessages() { return GetMessages("INBOX"); } char *MailIMap::SequenceToString(GArray *Seq) { if (!Seq) return NewStr("1:*"); GStringPipe p; // int Last = 0; for (unsigned s=0; sLength(); ) { unsigned e = s; while (eLength()-1 && (*Seq)[e] == (*Seq)[e+1]-1) e++; if (s) p.Print(","); if (e == s) p.Print("%i", (*Seq)[s]); else p.Print("%i:%i", (*Seq)[s], (*Seq)[e]); s = e + 1; } return p.NewStr(); } static void RemoveBytes(GArray &a, ssize_t &Used, ssize_t Bytes) { if (Used >= Bytes) { ssize_t Remain = Used - Bytes; if (Remain > 0) memmove(&a[0], &a[Bytes], Remain); Used -= Bytes; } else LgiAssert(0); } static bool PopLine(GArray &a, ssize_t &Used, GAutoString &Line) { for (ssize_t i=0; iNextCmd++; GStringPipe p(256); p.Print("A%4.4i %sFETCH ", Cmd, ByUid ? "UID " : ""); p.Write(Seq, strlen(Seq)); p.Print(" (%s)\r\n", Parts); GAutoString WrBuf(p.NewStr()); if (WriteBuf(false, WrBuf)) { ClearDialog(); GArray Buf; Buf.Length(1024 + (SizeHint>0?(uint32)SizeHint:0)); ssize_t Used = 0; ssize_t MsgSize; // int64 Start = LgiCurrentTime(); int64 Bytes = 0; bool Done = false; // uint64 TotalTs = 0; bool Blocking = Socket->IsBlocking(); Socket->IsBlocking(false); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Starting loop\n", _FL); #endif uint64 LastActivity = LgiCurrentTime(); bool Debug = false; while (!Done && Socket->IsOpen()) { ssize_t r; // We don't wait for 'readable' with select here because // a) The socket is in non-blocking mode and // b) For OpenSSL connections 'readable' on the socket != can get bytes from 'read'. // Just try the read first and see if it gives you bytes, if not then 'select' on the socket. while (true) { // Extend the buffer if getting used up if (Buf.Length()-Used <= 256) { Buf.Length(Buf.Length() + (64<<10)); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Ext buf: %i\n", _FL, Buf.Length()); #endif } // Try and read bytes from server. r = Socket->Read(Buf.AddressOf(Used), Buf.Length()-Used-1); // -1 for NULL terminator #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: r=%i, used=%i, buf=%i\n", _FL, r, Used, Buf.Length()); #endif if (r > 0) { if (RawCopy) RawCopy->Write(&Buf[Used], r); Used += r; Bytes += r; LastActivity = LgiCurrentTime(); } else { LgiSleep(1); // Don't eat the whole CPU... break; } if (Debug) LgiTrace("%s:%i - Recv=%i\n", _FL, r); } // See if we can parse out a single response GArray Ranges; LgiAssert(Used < Buf.Length()); Buf[Used] = 0; // NULL terminate before we parse while (true) { MsgSize = ParseImapResponse(&Buf[0], Used, Ranges, 2); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: MsgSize=%i\n", _FL, MsgSize); #endif if (Debug) LgiTrace("%s:%i - ParseImapResponse=%i\n", _FL, MsgSize); if (!MsgSize) break; if (!Debug) LastActivity = LgiCurrentTime(); char *b = &Buf[0]; if (MsgSize > Used) { // This is an error... ParseImapResponse should always return <= Used. // If this triggers, ParseImapResponse is skipping a NULL that it shouldn't. #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Wrong size %i, %i\n", _FL, MsgSize, Used); #endif Ranges.Length(0); LgiAssert(0); #if _DEBUG ParseImapResponse(&Buf[0], Used, Ranges, 2); #endif Done = true; break; } LgiAssert(Ranges.Length() >= 2); // Setup strings for callback char *Param = b + Ranges[0].Start; Param[Ranges[0].Len()] = 0; char *Name = b + Ranges[1].Start; Name[Ranges[1].Len()] = 0; if (_stricmp(Name, "FETCH")) { // Not the response we're looking for. #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Wrong response: %s\n", _FL, Name); #endif } else { // Process ranges into a hash table StrMap Parts; for (unsigned i=2; i 0 && Buf[0] != '*') { GAutoString Line; while (PopLine(Buf, Used, Line)) { #if DEBUG_FETCH LgiTrace("%s:%i - Fetch: Line='%s'\n", _FL, Line.Get()); #endif GToken t(Line, " \r\n"); if (t.Length() >= 2) { char *r = t[0]; if (*r == 'A') { bool IsOk = !_stricmp(t[1], "Ok"); int Response = atoi(r + 1); Log(Line, IsOk ? GSocketI::SocketMsgReceive : GSocketI::SocketMsgError); if (Response == Cmd) { Done = true; break; } } else Log(&Buf[0], GSocketI::SocketMsgError); } else { // This is normal behaviour... just don't have the end marker yet. Done = true; break; } } } } Socket->IsBlocking(Blocking); CommandFinished(); #if DEBUG_FETCH LgiTrace("%s:%i - Fetch finished, status=%i\n", _FL, Status); #endif } Unlock(); return Status; } bool IMapHeadersCallback(MailIMap *Imap, char *Msg, MailIMap::StrMap &Parts, void *UserData) { char *s = Parts.Find(sRfc822Header); if (s) { Parts.Delete(sRfc822Header); GAutoString *Hdrs = (GAutoString*)UserData; Hdrs->Reset(s); } return true; } char *MailIMap::GetHeaders(int Message) { GAutoString Text; if (Lock(_FL)) { char Seq[64]; sprintf_s(Seq, sizeof(Seq), "%i", Message + 1); Fetch( false, Seq, sRfc822Header, IMapHeadersCallback, &Text, NULL); Unlock(); } return Text.Release(); } struct ReceiveCallbackState { MailTransaction *Trans; MailCallbacks *Callbacks; }; static bool IMapReceiveCallback(MailIMap *Imap, char *Msg, MailIMap::StrMap &Parts, void *UserData) { ReceiveCallbackState *State = (ReceiveCallbackState*) UserData; char *Flags = Parts.Find("FLAGS"); if (Flags) { State->Trans->Imap.Set(Flags); } char *Hdrs = Parts.Find(sRfc822Header); if (Hdrs) { ssize_t Len = strlen(Hdrs); State->Trans->Stream->Write(Hdrs, Len); } char *Body = Parts.Find("BODY[TEXT]"); if (Body) { ssize_t Len = strlen(Body); State->Trans->Stream->Write(Body, Len); } State->Trans->Status = Hdrs != NULL || Body != NULL; if (Imap->Items) Imap->Items->Value++; Parts.Empty(); if (State->Callbacks) { bool Ret = State->Callbacks->OnReceive(State->Trans, State->Callbacks->CallbackData); if (!Ret) return false; } return true; } bool MailIMap::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Lock(_FL)) { int Errors = 0; ReceiveCallbackState State; State.Callbacks = Callbacks; for (unsigned i=0; iStatus = false; char Seq[64]; sprintf_s(Seq, sizeof(Seq), "%i", State.Trans->Index + 1); Fetch ( false, Seq, "FLAGS RFC822.HEADER BODY[TEXT]", IMapReceiveCallback, &State, NULL ); if (State.Trans->Status) { Status = true; } else if (Errors++ > 5) { // Yeah... not feelin' it Status = false; break; } } Unlock(); } return Status; } bool MailIMap::Append(const char *Folder, ImapMailFlags *Flags, const char *Msg, GString &NewUid) { bool Status = false; if (Folder && Msg && Lock(_FL)) { GAutoString Flag(Flags ? Flags->Get() : NULL); GAutoString Path(EncodePath(Folder)); int Cmd = d->NextCmd++; int Len = 0; for (const char *m = Msg; *m; m++) { if (*m == '\n') { Len += 2; } else if (*m != '\r') { Len++; } } // Append on the end of the mailbox int c = sprintf_s(Buf, sizeof(Buf), "A%4.4i APPEND \"%s\"", Cmd, Path.Get()); if (Flag) c += sprintf_s(Buf+c, sizeof(Buf)-c, " (%s)", Flag.Get()); c += sprintf_s(Buf+c, sizeof(Buf)-c, " {%i}\r\n", Len); if (WriteBuf()) { if (Read()) { bool GotPlus = false; for (char *Dlg = Dialog.First(); Dlg; Dlg = Dialog.Next()) { if (Dlg[0] == '+') { Dialog.Delete(Dlg); DeleteArray(Dlg); GotPlus = true; break; } } if (GotPlus) { int Wrote = 0; for (const char *m = Msg; *m; ) { while (*m == '\r' || *m == '\n') { if (*m == '\n') { Wrote += Socket->Write((char*)"\r\n", 2); } m++; } const char *e = m; while (*e && *e != '\r' && *e != '\n') e++; if (e > m) { Wrote += Socket->Write(m, e-m); m = e; } else break; } LgiAssert(Wrote == Len); Wrote += Socket->Write((char*)"\r\n", 2); // Read response.. ClearDialog(); if ((Status = ReadResponse(Cmd))) { char Tmp[16]; sprintf_s(Tmp, sizeof(Tmp), "A%4.4i", Cmd); for (char *Line = Dialog.First(); Line; Line = Dialog.Next()) { GAutoString c = ImapBasicTokenize(Line); if (!c) break; if (!strcmp(Tmp, c)) { GAutoString a; while ((a = ImapBasicTokenize(Line)).Get()) { GToken t(a, " "); if (t.Length() > 2 && !_stricmp(t[0], "APPENDUID")) { NewUid = t[2]; break; } } } } } } } CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Delete(int Message) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STORE %i FLAGS (\\deleted)\r\n", Cmd, Message+1); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Delete(bool ByUid, const char *Seq) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i %sSTORE %s FLAGS (\\deleted)\r\n", Cmd, ByUid?"UID ":"", Seq); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } int MailIMap::Sizeof(int Message) { int Status = 0; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i FETCH %i (%s)\r\n", Cmd, Message+1, sRfc822Size); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { char *d = Dialog.First(); if (d) { char *t = strstr(d, sRfc822Size); if (t) { t += strlen(sRfc822Size) + 1; Status = atoi(t); } } } CommandFinished(); } Unlock(); } return Status; } bool ImapSizeCallback(MailIMap *Imap, char *Msg, MailIMap::StrMap &Parts, void *UserData) { GArray *Sizes = (GArray*) UserData; int Index = atoi(Msg); if (Index < 1) return false; char *Sz = Parts.Find(sRfc822Size); if (!Sz) return false; (*Sizes)[Index - 1] = atoi(Sz); return true; } bool MailIMap::GetSizes(GArray &Sizes) { return Fetch(false, "1:*", sRfc822Size, ImapSizeCallback, &Sizes) != 0; } bool MailIMap::GetUid(int Message, char *Id, int IdLen) { bool Status = false; if (Lock(_FL)) { if (FillUidList()) { char *s = Uid.ItemAt(Message); if (s && Id) { strcpy_s(Id, IdLen, s); Status = true; } } Unlock(); } return Status; } bool MailIMap::FillUidList() { bool Status = false; if (Socket && Lock(_FL)) { if (Uid.Length() == 0) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i UID SEARCH ALL\r\n", Cmd); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (char *d = Dialog.First(); d && !Status; d=Dialog.Next()) { GToken T(d, " "); if (T[1] && strcmp(T[1], "SEARCH") == 0) { for (unsigned i=2; i &Id) { bool Status = false; if (Lock(_FL)) { if (FillUidList()) { for (char *s=Uid.First(); s; s=Uid.Next()) { Id.Insert(NewStr(s)); } Status = true; } Unlock(); } return Status; } bool MailIMap::GetFolders(GArray &Folders) { bool Status = false; if (Socket && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i LIST \"\" \"*\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Buf[0] = 0; if (ReadResponse(Cmd)) { char Sep[] = { GetFolderSep(), 0 }; for (char *d = Dialog.First(); d; d=Dialog.Next()) { GArray t; char *s; while ((s = LgiTokStr((const char*&)d))) { t[t.Length()].Reset(s); } if (t.Length() >= 5) { if (strcmp(t[0], "*") == 0 && _stricmp(t[1], "LIST") == 0) { char *Folder = t[t.Length()-1]; MailImapFolder *f = new MailImapFolder(); if (f) { Folders.Add(f); f->Sep = Sep[0]; // Check flags f->NoSelect = stristr(t[2], "NoSelect") != 0; f->NoInferiors = stristr(t[2], "NoInferiors") != 0; // LgiTrace("Imap folder '%s' %s\n", Folder, t[2].Get()); // Alloc name if (Folder[0] == '\"') { char *p = TrimStr(Folder, "\""); f->Path = DecodeImapString(p); DeleteArray(p); } else { f->Path = DecodeImapString(Folder); } } } } } Status = true; ClearDialog(); } CommandFinished(); } Unlock(); } return Status; } bool MailIMap::CreateFolder(MailImapFolder *f) { bool Status = false; // char Dir[2] = { d->FolderSep, 0 }; if (f && f->GetPath() && Lock(_FL)) { int Cmd = d->NextCmd++; char *Enc = EncodePath(f->GetPath()); sprintf_s(Buf, sizeof(Buf), "A%4.4i CREATE \"%s\"\r\n", Cmd, Enc); DeleteArray(Enc); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); if (Status) { char *End = f->Path + strlen(f->Path) - 1; if (*End == GetFolderSep()) { f->NoSelect = true; *End = 0; } else { f->NoInferiors = true; } } CommandFinished(); } Unlock(); } return Status; } char *MailIMap::EncodePath(const char *Path) { if (!Path) return 0; char Sep = GetFolderSep(); char Native[MAX_PATH], *o = Native, *e = Native + sizeof(Native) - 1; for (const char *i = *Path == '/' ? Path + 1 : Path; *i && o < e; i++) { if (*i == '/') *o++ = Sep; else *o++ = *i; } *o++ = 0; return EncodeImapString(Native); } bool MailIMap::DeleteFolder(const char *Path) { bool Status = false; if (Path && Lock(_FL)) { // Close the current folder if required. if (d->Current && _stricmp(Path, d->Current) == 0) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i CLOSE\r\n", Cmd); if (WriteBuf()) { ClearDialog(); ReadResponse(Cmd); DeleteArray(d->Current); CommandFinished(); } } // Delete the folder int Cmd = d->NextCmd++; char *NativePath = EncodePath(Path); sprintf_s(Buf, sizeof(Buf), "A%4.4i DELETE \"%s\"\r\n", Cmd, NativePath); DeleteArray(NativePath); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::RenameFolder(const char *From, const char *To) { bool Status = false; if (From && To && Lock(_FL)) { int Cmd = d->NextCmd++; GAutoString f(EncodePath(From)); GAutoString t(EncodePath(To)); sprintf_s(Buf, sizeof(Buf), "A%4.4i RENAME \"%s\" \"%s\"\r\n", Cmd, f.Get(), t.Get()); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::SetFolderFlags(MailImapFolder *f) { bool Status = false; if (f && Lock(_FL)) { /* int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i RENAME \"%s\" \"%s\"\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); } */ Unlock(); } return Status; } bool MailIMap::SetFlagsByUid(GArray &Uids, const char *Flags) { bool Status = false; if (Lock(_FL)) { int Cmd = d->NextCmd++; GStringPipe p; p.Print("A%04.4i UID STORE ", Cmd); if (Uids.Length()) { for (unsigned i=0; i &InUids, const char *DestFolder) { bool Status = false; if (Lock(_FL)) { int Cmd = d->NextCmd++; GAutoString Dest(EncodePath(DestFolder)); GStringPipe p(1024); p.Print("A%04.4i UID COPY ", Cmd); for (unsigned i=0; iNextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i EXPUNGE\r\n", Cmd); if (WriteBuf()) { ClearDialog(); Status = ReadResponse(Cmd); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Search(bool Uids, GArray &SeqNumbers, const char *Filter) { bool Status = false; if (ValidStr(Filter) && Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i %sSEARCH %s\r\n", Cmd, Uids?"UID ":"", Filter); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (char *d = Dialog.First(); d; d = Dialog.Next()) { if (*d != '*') continue; d++; GAutoString s(Tok(d)); if (!s || _stricmp(s, "Search")) continue; while (s.Reset(Tok(d))) { SeqNumbers.New() = s; Status = true; } } } CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Status(char *Path, int *Recent) { bool Status = false; if (Path && Recent && Lock(_FL)) { GAutoString Dest(EncodePath(Path)); int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i STATUS %s (RECENT)\r\n", Cmd, Dest.Get()); if (WriteBuf()) { ClearDialog(); if (ReadResponse(Cmd)) { for (char *d=Dialog.First(); d; d=Dialog.Next()) { if (*d != '*') continue; d++; GAutoString Cmd = ImapBasicTokenize(d); GAutoString Folder = ImapBasicTokenize(d); GAutoString Fields = ImapBasicTokenize(d); if (Cmd && Folder && Fields && !_stricmp(Cmd, "status") && !_stricmp(Folder, Dest)) { char *f = Fields; GAutoString Field = ImapBasicTokenize(f); GAutoString Value = ImapBasicTokenize(f); if (Field && Value && !_stricmp(Field, "recent")) { *Recent = atoi(Value); Status = true; break; } } } } else LgiTrace("%s:%i - STATUS cmd failed.\n", _FL); CommandFinished(); } Unlock(); } return Status; } bool MailIMap::Poll(int *Recent, GArray *New) { bool Status = true; if (Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i NOOP\r\n", Cmd); if (WriteBuf()) { ClearDialog(); if ((Status = ReadResponse(Cmd))) { int LocalRecent; if (!Recent) Recent = &LocalRecent; *Recent = 0; for (char *Dlg=Dialog.First(); Dlg; Dlg=Dialog.Next()) { if (Recent && stristr(Dlg, " RECENT")) { *Recent = atoi(Dlg + 2); } } if (*Recent && New) { Search(false, *New, "new"); } } CommandFinished(); } Unlock(); } return Status; } bool MailIMap::StartIdle() { bool Status = false; if (Lock(_FL)) { int Cmd = d->NextCmd++; sprintf_s(Buf, sizeof(Buf), "A%4.4i IDLE\r\n", Cmd); Status = WriteBuf(); CommandFinished(); Unlock(); } return Status; } bool MailIMap::OnIdle(int Timeout, GArray &Resp) { bool Status = false; if (Lock(_FL)) { auto Blk = Socket->IsBlocking(); Socket->IsBlocking(false); #if 0 // def _DEBUG auto Start = LgiCurrentTime(); #endif Read(NULL, Timeout); #if 0 // def _DEBUG auto Time = LgiCurrentTime() - Start; if (Timeout > 0 && Time < (uint64)(Timeout * 0.9)) { printf("Short rd " LPrintfInt64 " of %i\n", Time, Timeout); } #endif Socket->IsBlocking(Blk); char *Dlg; while ((Dlg = Dialog.First())) { Dialog.Delete(Dlg); Log(Dlg, GSocketI::SocketMsgReceive); if (Dlg[0] == '*' && Dlg[1] == ' ') { char *s = Dlg + 2; GAutoString a = ImapBasicTokenize(s); GAutoString b = ImapBasicTokenize(s); if (a && b) { Untagged &u = Resp.New(); if (IsDigit(a[0])) { u.Cmd = b.Get(); u.Id = atoi(a); if (ValidStr(s)) u.Param = s; } else { u.Param = Dlg + 2; } Status = true; } } DeleteArray(Dlg); } Unlock(); } return Status; } bool MailIMap::FinishIdle() { bool Status = false; if (Lock(_FL)) { if (WriteBuf(false, "DONE\r\n")) { Status = ReadResponse(); CommandFinished(); } Unlock(); } return Status; }