# Eamil Log Parsing 流程(測試用):
1. 會先到Postman打phishCampaign這隻api,它會寄送3封email,並且db的phish_email會有三筆新紀錄
(https://twmsocialengineeringt.com/platform/phish-camps?accountId=1)
2. 那三筆記錄會先壓messageId上去,以防止mail server的parsing程式沒抓到messageId
3. 另外同時,我們會先把mail_log_parse_ap這個專案包成jar檔後上傳到mail server去執行它(之後會變成背景一直跑)
4. phish-email的新資料一開始會呈現TBS的email_status,表示他在等待每五分鐘(時間結尾0,5)會有統一發送信的機制,這時mail server的mail log就會有新的紀錄
5. 當mail log有新紀錄時,我們的mail server parsing程式就只會去針對新加進去的log一行行去parsing,來取出他們的messageId, queueId, dsn代碼來存進去db的postfix_email表,以利未來有需要這些資料時可以取用
6. 另外在每parsing每行的messageId, queueId, dsn時,會針對phish_email的messageId去比對是否存在,如果messageId相同的話,我們就會將dsn值回寫回去(條件是如果dsn為2.0.0,email_status就更新成SUC,如果dsn為2.0.0以外的就更新為FAIL)
**=>** 此目的是因為我們是用relay mail server來去寄送釣魚信件給受害者的server,而不是從我們本機java程式端去發送,所以我們才需要去抓受害者端回傳給relay mail server的dsn代碼來確定我們是否有真的寄送成功,而不是一開始java程式到mail server的response代碼
# 程式碼細節介紹:
## (一) Mail_log_parser_ap Project
### SmsNewApplication main class for running
主要重點是這幾行:
String myMessageId = parseWritingLine.findQueueIdByMessageId(line);
String myDsn = parseWritingLine.findDSN(line);
callPostMethod(myMessageId,myDsn,"phish","BrianTesting");
lastPosition = file.getFilePointer();
=>外層會有WatchService物件去監聽是否有新的mail log加入,如果有就會在無窮whie loop中繼續執行抓出 messageId 和 dsn 的方法,而lastPosition紀錄著之前在mail log檔案中指到第幾行,以便從新資料繼續parsing,減少程式重複從頭run之情形,而callPostMethod是最後將取出的dsn代碼,messageId和預設帶入的token: BrianTesting, type: phish,做為參數去呼叫寫在socialEngineerProject的更新emailStatus那隻api
```java
@Override
public void run(String... args) throws Exception {
Path logFilePath = Paths.get(emailLogFile);
long lastPosition = 0;
try {
WatchService watchService = FileSystems.getDefault().newWatchService();
logFilePath.getParent().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
boolean running = true;
while (running) {
WatchKey key;
try {
key = watchService.take();
} catch (InterruptedException e) {
break;
}
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
Path modifiedFilePath = (Path) event.context();
if (modifiedFilePath.equals(logFilePath.getFileName())) {
// 執行相應的處理邏輯,例如讀取郵件日誌中的新資料
try(RandomAccessFile file = new RandomAccessFile(logFilePath.toFile(), "r")){
file.seek(lastPosition);
String line;
while ((line = file.readLine()) != null) {
System.out.println("Log entry: " + line);
// 執行相應的處理邏輯,例如解析郵件日誌中的新資料
String myMessageId = parseWritingLine.findQueueIdByMessageId(line);
String myDsn = parseWritingLine.findDSN(line);
callPostMethod(myMessageId,myDsn,"phish","BrianTesting");
lastPosition = file.getFilePointer(); // 更新上一次讀取的位置,很重要
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
key.reset();
}
} catch (IOException e) {
e.printStackTrace();
}
}
```
### callPostMethod in SmsNewApplication class for outer api calling
HttpPost httpPost = new HttpPost("https://twmsocialengineeringt.com/platform/phish-email-status/");
=>這個要等之後我寫的那隻api整合進去後就可以執行了,我已經在localhost版本測過是可以打api的了
```java
public static void callPostMethod(String messageId,String dsn,String type,String token) throws Exception {
// Create an HttpClient instance
HttpClient httpClient = HttpClients.createDefault();
// Create an HttpPost request with the target API URL
HttpPost httpPost = new HttpPost("https://twmsocialengineeringt.com/platform/phish-email-status/");
//HttpPost httpPost = new HttpPost("https://localhost:8080/platform/phish-email-status/");
// Set request headers
httpPost.setHeader("Content-Type", "application/json");
// Create a request object with class attributes as parameters
MyRequestObject requestObject = new MyRequestObject();
requestObject.setMessageId(messageId);
requestObject.setEmailStatus(dsn);
requestObject.setType(type);
requestObject.setToken(token);
// Convert the request object to JSON string
Gson gson = new Gson();
String jsonPayload = gson.toJson(requestObject);
// Set the JSON payload as the request entity
StringEntity requestEntity = new StringEntity(jsonPayload);
httpPost.setEntity(requestEntity);
// Send the request and receive the response
HttpResponse response = httpClient.execute(httpPost);
// Get the response entity
HttpEntity entity = response.getEntity();
// Read the response as a string
String responseBody = EntityUtils.toString(entity);
// Process the response as needed
System.out.println("Print the responseBody:"+responseBody);
}
```
### 用messageId找queueId (ParseWritingLine class)
1. 尋找mail log的messageId正規化表達式:
String messageIDRegix = "message-id\\s*=\\s*(?:<([^>]+)> | ([^;]+))";
=>messageId等號後面可能會有<>包住我們要的值,或是沒有<>都要考慮,所以用|(or)來找尋符合其中一個條件的messageId


2. 群找mail log的queueId正規化表達式:
String queueIDRegix = ".* : (\\w{10,20}): .*";
=>從上面兩個範例可以看到queueId是在兩個 : 之間的值,所以正規劃就是判斷,不管兩個冒號之外的值有多少,抓出兩個冒號間長度介在10~20的字串
3. 流程: 當每行行mail log在parsing時,如果正規化有找到messageId存在的話,先去db看postfix email table內是否已經有該messageId存過了,如果沒有的話就直接新增一個postfixEmail物件,並將parsing其該列的messageId,queueId一同存入,最後在將新筆資料 save and flush 資料庫中
```java
public String findQueueIdByMessageId(String line) {
if(line == null) {
return null;
}
String messageIDRegix = "message-id\\s*=\\s*(?:<([^>]+)>|([^;]+))";
String queueIDRegix = ".*: (\\w{10,20}):.*";
Pattern pattern = Pattern.compile(messageIDRegix);
Pattern queueIdPattern = Pattern.compile(queueIDRegix);
Matcher matcher = pattern.matcher(line);
Matcher queueIdMatcher = queueIdPattern.matcher(line);
if (matcher.find()) { //這個是在做把queueId存進去我們預設的串列中
String messageID = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
logger.info("Found message ID: " +messageID);
Optional<PostfixEmail> postfixEmailLog = postfixEmailJpaRepository.findByMessageId(messageID);
if (postfixEmailLog.isPresent()) {//如果messageId存在在資料表裡面的話,表示之前已經有寫入進去messageId 和queueId了
return null;
}
PostfixEmail postfixEmail = new PostfixEmail();
postfixEmail.setMessageId(messageID);
if(queueIdMatcher.matches()) {
String queueId = queueIdMatcher.group(1) != null ? queueIdMatcher.group(1) : queueIdMatcher.group(2);
logger.info("Set Queue ID in line49: " +queueId);
postfixEmail.setQueueId(queueId);
postfixEmailJpaRepository.saveAndFlush(postfixEmail);//這行沒有加的話會無法寫進去資料庫
}
return messageID;
}
return null;
}
```
### 用queueId找dsn (ParseWritingLine class in mail_log_parser_ap Project)
1. 尋找mail log的dsn的正規化表達式:
String dsnRegix = "dsn=(\\d\\.\\d\\.\\d),";
=> 抓出符合dsn= 後面的兩個x.x.x的形式之字串
2. 流程: 如果一行行parsing時有符合queueId的正規化表示式的值出現,就去資料庫的postfix_email table看是否已經存在queueId了,如果有就將該行parse出的dsn代碼update資料庫抓出來的那筆postfix_email列的dsn欄位值
```java
public String findDSN(String line) {
if(line == null) {
return null;
}
String queueIDRegix = ".*: (\\w{10,20}):.*";
String dsnRegix = "dsn=(\\d\\.\\d\\.\\d),";
Pattern queueIdPattern = Pattern.compile(queueIDRegix);
Pattern dsnPattern = Pattern.compile(dsnRegix);
String queueId = "";
Matcher queueIdMatcher = queueIdPattern.matcher(line);
if(queueIdMatcher.find()) {
queueId = queueIdMatcher.group(1);
logger.info("Find Queue ID in func findDSN: " +queueId);
Optional<PostfixEmail> postfixEmailLog = postfixEmailJpaRepository.findByQueueId(queueId);
if (!postfixEmailLog.isPresent()) {
return null;
}
PostfixEmail postfixEmail = postfixEmailLog.get();
String dbQueueId = postfixEmail.getQueueId();
logger.info("line 83 testing, print db postfixQueueId:"+dbQueueId);
Matcher matcher = dsnPattern.matcher(line);
if(matcher.find()) {
String dsn = matcher.group(1);
logger.info("Found DSN: " +dsn);
postfixEmail.setDsn(dsn);
postfixEmailJpaRepository.saveAndFlush(postfixEmail);//這行沒有加的話會無法寫進去資料庫
return dsn;
}
}
return null;
}
```
## (二) SocialEngineerProject
### Update Email Status API 的 controller
API request parameter有四個(一定要傳入): type, messageId, emailStatus, token
```java
@Tag(name = "PhishEmail", description = "EmailStatusUpdate")
public class EmailCtrl {
@Autowired
private EmailService emailService;
@Operation(summary = "更新寄信回傳狀態")
@PostMapping(value = "/phish-email-status")
public ModelWrapper<Boolean> updateEmailStatus(@RequestParam @Parameter(description = "type of phishEmail or sms") String type,
@RequestParam @Parameter(description = "message Id") String messageId,
@RequestParam @Parameter(description = "eamil Status") String emailStatus,
@RequestParam @Parameter(description = "token") String token){
try {
return ModelWrapper.success(emailService.updateEmailStatus(type,messageId,emailStatus,token));
} catch (RestApiException ex) {
return ModelWrapper.error(ex);
}
}
}
```
## Spring Security 的設定(SocialEngineerProject)
http.csrf().disable():停用 CSRF(Cross-Site Request Forgery)保護,這意味著禁用跨站請求偽造的防護機制。
=>多加最後一行:antMatchers("/phish-email-status/...").permitAll():允許 "/phish-email-status/..." 路徑下的請求不需要身份驗證。
```java
http.csrf().disable()
.authorizeHttpRequests((authz) -> authz
.antMatchers("/authenticate/**").permitAll()
.antMatchers("/reset-password/**").permitAll()
.antMatchers("/edu/**").permitAll()
//下面這句超重要,沒有的話會導致Preflight Request被JWT擋住
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/phish-email-status/**").permitAll()
```
## Update Email Status API 的 service impl (SocialEngineerProject)
token先預設BrianTesting, type預設phish,首先透過phishEmailJpa來抓出你想要update的那個messageId列,如果token符合且type為phish的話表示是釣魚信件的資料,所以進行後續dsn判斷,最後再更新EmailStatus欄位值
```java
@Override
public Boolean updateEmailStatus(String type, String messageId, String status, String token) {
//method用4個參數
Optional<PhishEmail> phishEmail0 = phishEmailJpaRepository.findByMessageId(messageId);
if (!phishEmail0.isPresent()) {
throw new ResourceNotFoundException(ApiErrorCode.UNEXPECTED_ERROR, "找不到對應的postfixEmail物件");
}
PhishEmail phishEmail = phishEmail0.get();
if(token.equals("BrianTesting")) {
if(type.equals("phish")) {
if(status.equals("2.0.0")) {
phishEmail.setEmailStatus("SUC");
}else {
phishEmail.setEmailStatus("FAIL");
}
phishEmail.setPostfixStatus("Update Email Status");
phishEmailJpaRepository.saveAndFlush(phishEmail);
}
}else {
return false;
}
return true;
}
```
# Mail Server相關指令:
### Q:如何將專案打包成jar檔?
1. 先切terminal路徑到專案底下(也就是在mail_log_parse_ap下),即為pom.xml的上一層
2. 執行這行: mvn clean package -DskipTests
是一個 Maven 命令,用於在項目中執行特定的操作。
這個命令執行了以下幾個步驟:
* clean: 清理項目目錄,刪除之前編譯生成的目錄和文件。
* package: 對項目進行打包操作。Maven根據pom.xml配置文件中的相關設定,將項目源碼編譯、打包,生成可部署的輸出物件(例如JAR、WAR或EAR文件)。
* -DskipTests: 這是一個Maven的系統屬性。通過設置該屬性為true,告訴Maven在執行打包操作時跳過測試階段。測試階段通常包括執行單元測試和集成測試。跳過測試可以加快打包過程,特別是在開發階段不需要運行測試時。
=>總結來說,mvn clean package -DskipTests 命令的目的是將項目進行清理、編譯和打包的操作,並跳過測試階段。這個命令通常用於項目的打包部署,例如生成可運行的JAR文件或部署到應用服務器上。
P.S. 預設情況下,Maven 會在目標(target)目錄中生成一個名為 ${project.artifactId}-${project.version}.jar 的 JAR 檔案,其中 ${project.artifactId} 是專案的建構ID(Artifact ID),${project.version} 是專案的版本號。
=>如果你使用了其他 Maven 插件或設定了其他構建步驟,這可能會導致生成的 JAR 檔案名稱不同,具體取決於你的專案配置,否則JAR檔案名稱為上述的預設行為。
**=>這是看你的pom.xml怎麼設定的,因為你只需要 src 和 pom.xml 就可以打包出現target資料夾**


### Q:如何將jar檔丟上去mail server以進行後續操作?
1. 將路徑切到mail_log_parse_ap/target底下
* ls -altr 可以看到你的資料夾下所有資料的建立時間順序
2. 下smtp指令: sftp -i ~/.ssh/maip-t-chrisyen chrisyen@35.194.181.142 後就進入到可以傳輸到mail server的smtp連線下了
3. 將我們的jar檔傳上去mail server: put -r smstest-0.0.1-SNAPSHOT.jar /home/chrisyen (這個不寫也是會在這底下為mail server預設的路經)
4. 按exit後連進去mail server內: ssh -i ~/.ssh/maip-t-chrisyen chrisyen@35.194.181.142 輸入密碼Brianhuang881215
5. 將jar檔放移進去/ap/postfix_log_parser 底下: mv smstest-0.0.1-SNAPSHOT.jar /ap/postfix_log_parser
=>而你會看到在這個資料夾下裡面以經有放execjar.sh的shell檔了(圖一),我們可以透過執行它來啟動java spring boot 專案,其裡面程式細節在圖二。


6. 更改jar檔名: mv 原檔名.jar 新檔名.jar
7. 如果要改sh檔裡面的內容:
* 先 vi xxxx.sh
* 再輸入 i 就會切換成編輯模式
* 然後要存檔離開的話,先按control c 然後輸入:wq就會存檔並離開,如果不想要存的話就直接按control z