任務需求:一個會議投稿系統,在作者提交論文摘要時自動向作者發送一封確認郵件。
(註:投稿系統使用的是myreview
)
仔細考慮一下,這個任務可以有以下幾種方式完成:
- 在伺服器上配置sendmail服務,使用php中的的mail函數發送郵件;
- 在伺服器上安裝mutt + msmtp,或者在伺服器上安裝其它的smtp用戶端程式,使用smtp客戶程式發送郵件
- 寫一個C/S程式,每當有作者提交論文時,伺服器上的client端程式即把郵件內容組合好,然後通知在我的電腦上監聽的server端程式,server端程式於是調用我的電腦上的郵件發送指令碼來發送郵件。這個c/s程式可以用java, c, php等來寫
- 用nc或者telnet使用raw smtp協議來發送郵件
從用linux以來就對sendmail這個服務沒有好感,方法一放棄。也不想在伺服器上安裝附加的軟體,所以方法二放棄。對比起來,方法四比方法三更簡單一些。
方法四的思路:使用tcpdump攔截一次完整的smtp發信過程,然後寫程式類比這個過程。
首先啟動tcpdump對來往的資料包進行監控:
[root@jcwkyl review]# tcpdump -vvv -t -X -s 1500 -w data host mail.jlu.edu.cn
-vvv表示用最詳細的格式來記錄捕獲的資料包,-t表示不記錄時間戳記,-X表示用hex和ascii顯示資料包內容,-s表示顯示長度為1500而不是預設的68,-w表示輸出到data檔案中。
同時,發送一封郵件。
[whb@jcwkyl bash]$ echo "mail content" | mutt -s "test subject" jcwkyl@gmail.com
看這邊tcpdump:
[root@jcwkyl review]# tcpdump -vvv -t -X -s 1500 -w data host mail.jlu.edu.cn
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 1500 bytes
Got 34
捕獲了34個資料包。查看記錄檔案:
[root@jcwkyl review]# tcpdump -vvv -t -X -s 1500 -r data | less
輸出很多,從其中可以看到資料往來的格式,截取其中三個資料包為例:
<!--
@page { size: 8.5in 11in; margin: 0.79in }
P { margin-bottom: 0.08in }
-->
IP (tos 0x0, ttl
61, id 51365, offset 0, flags [DF], proto: TCP (6), length: 63)
mail.jlu.edu.cn.sm
tp > jcwkyl.gridlab.57415: P, cksum 0x9765
(correct), 1:12(11) ack 1 win 1448 <nop,nop,timestamp 203
2907544
165074058>
0x0000:
4500 003f c8a5 4000 3d06 577f cac6 1038
E..?..@.=.W....8
0x0010: 0a3c 385a 0019 e047 c0c6 7dbd eeb9 52bd
.<8Z...G..}...R.
0x0020: 8018 05a8 9765 0000 0101 080a 792b b518
.....e......y+..
0x0030: 09d6 d48a 3232 3020 4553 4d54 500d 0a
....220.ESMTP..
IP (tos 0x0, ttl 64, id 34360, offset 0,
flags [DF], proto: TCP (6), length: 52) jcwkyl.gridlab.574
15 >
mail.jlu.edu.cn.smtp: ., cksum 0xebee (correct), 1:1(0) ack 12 win 46
<nop,nop,timestamp 165074
058 2032907544>
0x0000: 4500 0034 8638 4000 4006 96f7 0a3c 385a
E..4.8@.@....<8Z
0x0010: cac6 1038 e047 0019 eeb9 52bd c0c6 7dc8
...8.G....R...}.
0x0020: 8010 002e ebee 0000 0101 080a 09d6 d48a
................
0x0030: 792b b518
y+..
IP (tos 0x0, ttl 64, id 34361, offset 0, flags [DF],
proto: TCP (6), length: 68) jcwkyl.gridlab.574
15 >
mail.jlu.edu.cn.smtp: P, cksum 0x1dcb (incorrect (-> 0x8015),
1:17(16) ack 12 win 46 <nop,nop,t
imestamp 165074058
2032907544>
0x0000:
4500 0044 8639 4000 4006 96e6 0a3c 385a
E..D.9@.@....<8Z
0x0010: cac6 1038 e047 0019 eeb9 52bd c0c6 7dc8
...8.G....R...}.
0x0020: 8018 002e 1dcb 0000 0101 080a 09d6 d48a
................
0x0030: 792b b518 4548 4c4f 206c 6f63 616c 686f
y+..EHLO.localho
0x0040: 7374 0d0a
st..
第一個資料包是伺服器發給我的,內容就是:220 ESMTP,第二個資料包沒有內容不用管它,第三個資料包也是我發給伺服器的,資料內容是: EHLO/x20localhost/x0d/x0a
就這樣在這34個資料包裡找,把所有的從我發到郵件伺服器的資料內容提取出來,最後的結果如下:
EHLO localhost/r/n
AUTH LOGIN/r/n
<para1>
<para2>
MAIL FROM:<whb@jlu.edu.cn>/r/n
RCPT TO:<jcwkyl@gmail.com>/r/n
DATA/r/n
Date: Fri, 1 5 Jan 2010 17:10 :06 +0800 /r/n
From: Email Address <addr@jlu.edu.cn>/r/n
To: jcwkyl@gmail.com/r/n
Subject: test subject/r/n
Message-ID: <20100115091 006 GA12962@jcwkyl.gridlab>/r/n
Mime-Version: 1.0/r/n
Content-Type: text/plain; charset=us-ascii/r/n
Content-Disposition: inline/r/n
User-Agent: Mutt/1.4.2.2i/r/n/r/n
mail content/r/n/x2e/r/n
QUIT/r/n
以上就是整個過程。注意郵件內文開始前有兩個/r/n,郵件內文以/r/n/x2e/r/n結束。/r就是十六進位的/x0d,/n就是十六進位的/x0a。
上面的<para1>和<para2>本來是兩個字串,從上下文猜測是用來身分識別驗證的,這裡用<para1>和<para2>代表。
接下來就是寫程式類比這個過程,因為會議投稿系統是用php寫的,所以就用php寫程式類比這個郵件發送過程,最終的代碼如下:
// Encapsulate the mail function<br />function SendMail ($to, $subject, $mail,<br /> $from="", $replyTo="", $cc="")<br />{<br /> // Construct the header<br /> $header = "";<br /> if (!empty($from)) $header .= "From: $from/r/n";<br /> if (!empty($cc)) $header .= "Cc: $cc/r/n";<br /> if (!empty($cc)) $header .= "Reply-to: $replyTo/r/n";<br /> // Add the signature file<br /> /* if (file_exists("Signature"))<br /> {<br /> $mail .= readfile ("Signature");<br /> }<br /> */<br /> // Use the standard mail function<br /> // Sometimes the -f option does not work<br /> //mail ($to, $subject, $mail, $header, "-f $from");<br /> //mail ($to, $subject, $mail, $header);<br /> $protocol_data = array(<br /> 'EHLO' => 'EHLO localhost',<br /> 'AUTH' => 'AUTH LOGIN',<br /> 'PARA1' => '<para1>', // 應該替換成自己的身份認證字串<br /> 'PARA2' => '<para2>',<br /> 'MAILFROM' => 'MAIL FROM:<addr@jlu.edu.cn>',<br /> 'RCPTTO' => 'RCPT TO:<'.$to.'>',<br /> 'DATA' => 'DATA'<br /> );<br /> $mailcontent = "From: From Address <addr@jlu.edu.cn>/r/n";<br /> $mailcontent .= 'To: '.$to."/r/n";<br /> $mailcontent .= 'Subject: '.$subject."/r/n/r/n";<br /> $mailcontent .= $mail."/r/n/x2e/r/n";<br /> $mailcontent .= "QUIT/r/n";<br /> $handler = popen("/usr/bin/nc mail.jlu.edu.cn 25", "w");<br /> if(!$handler) {<br /> echo '<mce:script type="text/javascript"><!--<br />alert("error");<br />// --></mce:script>';<br /> }<br /> foreach($protocol_data as $value) {<br /> fwrite($handler, $value."/r/n");<br /> }<br /> fwrite($handler, $mailcontent."/r/n");<br /> pclose($handler);<br />}<br />
被注釋掉的mail函數是投稿系統以前的代碼,mail函數之後是自己寫的類比郵件發送過程的代碼。同樣的功能很容易用其他語言實現。