0x00 Introduction

---

Previous articles "Zimbra SOAP API Development Guide" and "Zimbra SOAP API Development Guide 2" introduced the invocation methods of Zimbra SOAP API and the open-source code Zimbra_SOAP_API_Manage. This article will expand upon that foundation by adding email operation functionalities.

0x01 Overview

---

This article will cover the following topics:

  • Viewing emails
  • Sending emails
  • Deleting emails

0x02 Viewing Emails

---

Zimbra SOAP API Documentation: https://files.zimbra.com/docs/soap_api/9.0.0/api-reference/index.html

Based on the Zimbra SOAP API documentation and debugging results, the following implementation process is derived:

  1. Invoke the Search command to obtain the Item ID corresponding to the email, using the Item ID as the identifier for the email
  2. After obtaining the Item ID, further operations can be performed on the email, such as viewing email details, moving emails, deleting emails, etc.

1. Obtain the Item ID corresponding to the email

The Search command needs to be used

Documentation: https://files.zimbra.com/docs/soap_api/8.8.15/api-reference/zimbraMail/Search.html

The following parameters are required:

(1) query

Indicates the location to view, examples are as follows:

View inbox: in:inbox

View sent items: in:sent

View trash: in:trash

(2) limit

Indicates the number of query results to return, examples are as follows:

Specify to return 10 results: 10

If this attribute is not specified, the default is 10

Test code:

def searchinbox_request(uri,token):
request_body="""


{token}




in:inbox



"""
try:
print("[*] Try to search")
r=requests.post(uri+"/service/soap",data=request_body.format(token=token),verify=False,timeout=15)
print(r.text)
except Exception as e:
print("[!] Error:%s"%(e))

Example of returned content:

Service zimlet stopped on mail.test.comJun 14 01:49:12 mail zmconfigd[34031]: Service status change: mail.test.com zimlet changed from running to stoppedService service stopped on mail.test.comJun 14 01:49:10 mail zmconfigd[34031]: Service status change: mail.test.com service changed from running to stopped

Analyzing the above format, it is found that the tags correspond to each email's information. The extracted data is as follows:

Service zimlet stopped on mail.test.comJun 14 01:49:12 mail zmconfigd[34031]:Service status change: mail.test.com zimlet changed from running to stopped

The format analysis is as follows:

  • id="-271" corresponds to the Item id of this email
  • *** corresponds to the title of this email
  • *** corresponds to the body content of this email
  • corresponds to the sender of this email
  • sf="1657272073000" corresponds to the receipt time of this email, in Unix timestamp format, with no additional time difference calculation

Example code for time format conversion:

from datetime import datetime
print(str(datetime.fromtimestamp(1657272073)))

Based on the above content, the implementation code for extracting Item ID, sender, subject, body content, and sending time is:

def searchinbox_request(uri,token):
request_body="""


{token}




in:inbox



"""
try:
print("[*] Try to search")
r=requests.post(uri+"/service/soap",data=request_body.format(token=token),verify=False,timeout=15)
pattern_c = re.compile(r"")
maildata = pattern_c.findall(r.text)
print("[+] Total: " + str(len(maildata)))
for i in range(len(maildata)):
pattern_data = re.compile(r"id=\"(.*?)\"")
data = pattern_data.findall(maildata[i])[0]
print("[+] Item id: " + data)
pattern_data = re.compile(r"a=\"(.*?)\"")
data = pattern_data.findall(maildata[i])[0]
print(" From: " + data)
pattern_data = re.compile(r"(.*?)")
data = pattern_data.findall(maildata[i])[0]
print(" Subject: " + data)
pattern_data = re.compile(r"(.*?)")
data = pattern_data.findall(maildata[i])[0]
print(" Body: " + data)
pattern_data = re.compile(r"sf=\"(.*?)\"")
data = pattern_data.findall(maildata[i])[0]
data = str(datetime.fromtimestamp(int(data[:-3])))
print(" UnixTime: " + data)
except Exception as e:
print("[!] Error:%s"%(e))

2. View email content

Testing revealed that viewing email details does not rely on Zimbra SOAP API; accessing a fixed URL is sufficient

URL format: https:///service/home/~/?auth=co&view=text&id=

This method retrieves the complete email content, including Base64-encoded attachments

Implementation code:

def viewmail_request(uri,token):
id = input("[*] Input the item id of the mail:")
headers["Cookie"]="ZM_AUTH_TOKEN="+token+";"
r = requests.get(uri+"/service/home/~/?auth=co&view=text&id="+id,headers=headers,verify=False)
if r.status_code == 200:
print("[*] Try to save the details of the mail")
path = id + ".txt"
with open(path, 'w+', encoding='utf-8') as file_object:
file_object.write(r.text)
print("[+] Save as " + path)
else:
print("[!]")
print(r.status_code)
print(r.text)

0x03 Sending Email

---

When sending emails with attachments, you need to upload the attachments first, then send the email

1. Upload Attachment

The upload functionality is implemented via FileUploadServlet, corresponding code location: /opt/zimbra/lib/jars/zimbrastore.jar in /com.zimbra/cs/service/FileUploadServlet.class

Upload details can be referenced at: https://github.com/Zimbra/zm-mailbox/blob/develop/store/docs/file-upload.txt

Upload URL: https:///service/upload, example response:

If adding parameters fmt=raw,extended, example response:

200,'client_token',[{"aid":"server_token","filename":"image.jpeg","ct":"image/jpeg"}]

After comparison, it was found that adding parameters fmt=raw,extended can additionally obtain file type, example: "ct":"image/jpeg"

Therefore, when uploading, use URL: https:///service/upload?fmt=raw,extended

Based on the above content, the following implementation code is derived:

def uploadattachment_request(uri,token):
fileContent = 0;
path = input("[*] Input the path of the file:")
with open(path,'rb') as f:
fileContent = f.read()
filename = path
print("[*] filepath:"+path)
if "\\" in path:
strlist = path.split('\\')
filename = strlist[-1]
if "/" in path:
strlist = path.split('/')
filename = strlist[-1]
headers = {
"Content-Type":"text/plain",
"Content-Disposition":"attachment; filename=\""+filename+"\"",
}
headers["Cookie"]="ZM_AUTH_TOKEN="+token+";"
files = {filename: fileContent}
r = requests.post(uri+"/service/upload?fmt=raw,extended",files=files,headers=headers,verify=False)
if "200" in r.text:
print("[+] Success")
pattern_id = re.compile(r"aid\":\"(.*?)\"")
attachmentid = pattern_id.findall(r.text)[0]
pattern_type = re.compile(r"ct\":\"(.*?)\"")
attachmenttype = pattern_type.findall(r.text)[0]
print(" name:"+filename)
print(" Type:%s"%(attachmenttype))
print(" Id:%s"%(attachmentid))
return attachmentid
else:
print("[!]")
print(r.text)

2. Send an email with attachments

The SendMsg command needs to be used

Documentation: https://files.zimbra.com/docs/soap_api/8.8.15/api-reference/zimbraMail/SendMsg.html

The following parameters are required:

(1) e

Represents sender, recipient, and related information. Example:

Specify sender:

Specify recipient:

Specify cc:

(2) su

Represents email subject. Example:

subjecttest1

(3)mp

Indicates the body content, example as follows:


"text/plain"
bodytest123456

(4)noSave

If set to 1, it means the email copy is not saved in the sent items after sending, example code:

1

(5)attach

Specifies the aid of the attachment to be sent, example code:


4e53e807-879c-482b-b56f-4c6d927fbab2:50e03447-418f-42e0-932c-c2d3c2616163

Based on the above content, the implementation code for sending an email with attachments is derived:

def sendmsgwithattachment_request(uri,token):
aid = input("[*] Input the id of the attachment:")
request_body="""


{token}




1



subjecttest1

"text/plain"
bodytest123456


{aid}





"""
try:
print("[*] Try to send msg")
r=requests.post(uri+"/service/soap",data=request_body.format(token=token,aid=aid),verify=False,timeout=15)
if "soap:Reason" not in r.text:
print("[+] Success")
else:
print("[!]")
print(r.text)

except Exception as e:
print("[!] Error:%s"%(e))

0x04 Delete Email

---

Requires ConvAction command

Documentation: https://files.zimbra.com/docs/soap_api/8.8.15/api-reference/zimbraMail/ConvAction.html

The following parameters are required:

(1) tcon

Specify to traverse all locations: o

Specify to traverse trash: t

The process of deleting emails through the browser involves first clicking delete to move the email to the trash, then clicking delete again in the trash to permanently remove the email.

The Zimbra-SOAP-API can simplify the above process by directly deleting emails.

Implementation code:

def deletemail_request(uri,token):
id = input("[*] Input the item id of the mail:")
request_body="""


{token}





delete
o
{id}




"""
try:
print("[*] Try to send msg")
r=requests.post(uri+"/service/soap",data=request_body.format(token=token,id=id),verify=False,timeout=15)
if "soap:Reason" not in r.text:
print("[+] Success")
else:
print("[!]")
print(r.text)

except Exception as e:
print("[!] Error:%s"%(e))

0x05 Open Source Code

---

New code has been uploaded to GitHub, address as follows:

An open-source project

Optimized code structure, added the following features:

  • DeleteMail, delete specified email
  • SearchMail, obtain mailbox information, including Item ID, sender, subject, body content, and send time
  • SendTestMailToSelf, send an email with attachment to the current mailbox
  • uploadattachment, upload attachment
  • uploadattachmentraw, another implementation for uploading attachments, used under specific conditions
  • viewmail, view complete email details

0x06 Summary

---

This article expands the calling methods of the Zimbra SOAP API by adding three practical features: viewing emails, sending emails, and deleting emails, while documenting the implementation details.