由於日常工作的需要,單位使用Serv-U架設了一個FTP伺服器,可是自從接手之後發現存在著一個非常嚴重的問題,這個FTP伺服器是對外公開的,居然很多用戶都沒有設定密碼。如果強制要求所有人設定密碼又必須在伺服器上設,這樣豈不是要所有人都把自己的密碼告訴管理員嗎,畢竟很多人習慣於用同一個密碼的。怎麼辦呢?最好的方法當然是能夠提供一個Web頁面來提供密碼的修改功能。
說乾就乾,在網路上查了一下,有一種方法是使用Serv-U本身提供的ODBC功能,用資料庫來儲存密碼,透過直接對資料庫進行操作來實現密碼的修改功能,但經過考試這種方法並不太可行。因為這個FTP伺服器已經運行了一年之久,裡面已有將近六十個用戶,要將這些用戶從Ini檔案移植到資料庫出現錯誤的幾率還是比較高的,還不如直接對INI檔案進行操作來得乾脆。
首先是要搞清楚Serv-U的使用者資訊在INI檔案中是如何保存的,密碼又是如何加密的。 INI檔案的結構比較簡單,修改密碼的話只要找到以[User=@UserID|1]節,並修改其下的Password鍵的值即可。 @UserID指的是使用者的登入ID。
1[GLOBAL]
2Version=6.1.0.5
3PacketTimeOut=300
4
5
6
7[Domain1]
8User1=
9User2=
10User3=
11
12
13
14[USER=abc|1]
15Password=niE383DC3710266ECAE04A6B3A18A2966D
16HomeDir=D:
17AlwaysAllowLogin=1
18ChangePassword=1
19TimeOut=600
20Note1="Wizard generated account"
21Access1=D:
22
23
使用者密碼的加密方法可以在Ser-U官方網站的知識庫中查到
http://rhinosoft.com/KBArticle.asp?RefNo=1177&prod=su
Manually Entering Encrypted Passwords into the ServUDaemon.ini File
To generate an encrypted password, first two random characters (the 'salt' - in the range a..z, A..Z) are added to the beginning of the clear-text password. This is then hashed using MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting MD5 and the resulting hash is hex-encoded. The result of this is written as plain-text starting with the 2 salt characters followed by the hex-encoded hash.
For a user account in the .ini file, this altwill look like:
Iash
. For a user account in the .ini file, this altwill look like:PassD31443838BF387438 is the string "cb", the MD5 hash is "644FB1F31184F8D3D169B54B3D46AB1A".
When verifying a user's password, Serv-U will do the same. It parses the salt the password, Serv-U will do the same. It parses the salt the password, Serv-ddswill do the same. It parses the salt in users. it the password the user sent to it by the client, MD5 hashes it, and compares the result with the stored hash. If the values are equal, then the entered password is correct.
加密的方法也就是隨機產生兩個字母,然後將字母和密碼進行拼接,再求它們的MD5值,最後將隨機字母放在MD5值的前面便是加密後的密碼。
接下來就可以根據以上的分析編寫程式來實現線上修改了。
1 /**//// <summary>
2 /// 取得指定字串的MD5值
3 /// </summary>
4 /// <param name="strContent"></param>
5 /// <returns></returns>
6 public String MD5( String strContent )
7 {
8 System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
9 byte[] bytes = System.Text.Encoding.UTF8.GetBytes( strContent );
10 bytes = md5.ComputeHash( bytes );
11 md5.Clear();
12 string ret = "";
13 for(int i=0 ; i<bytes.Length ; i++)
14 {
15 ret += Convert.ToString(bytes[i],16).PadLeft(2,'0');
16 }
17 return ret.PadLeft(32,'0').ToUpper();
18 }
19
20
21 /**//// <summary>
22 /// 產生隨便字串,字串長度為2
23 /// </summary>
24 /// <returns></returns>
25 public string GetRandomString()
26 {
27 string strReturn = "";
28 Random ran = new Random();
29 strReturn += Convert.ToChar( ran.Next( 26 ) + 'a' ).ToString();
30 strReturn += Convert.ToChar( ran.Next( 26 ) + 'a' ).ToString();
31 return strReturn;
32 }
33
34 //由指定的隨機字母和登入密碼產生加密後的密碼
35 public string CreateCryPassword( string strFrontChars, string strPassword )
36 {
37 return strFrontChars + MD5( strFrontChars + strPassword ).ToUpper().Trim();
38 }
39
40 /**//// <summary>
41 /// 「修改密碼」的點擊事件,在此事件中對密碼進行修改
42 /// </summary>
43 /// <param name="sender"></param>
44 /// <param name="e"></param>
45 private void btnModifyPwd_Click(object sender, System.EventArgs e)
46 {
47 string strUserID = txtLoginID.Text;
48 if( strUserID == String.Empty )
49 {
50 controlMessage.InnerHtml = "使用者名稱不能為空";
51 return;
52 }
53
54 //判斷兩次密碼輸入是否相同
55 if( txtNewPassword.Text != txtConfirmPassword.Text )
56 {
57 controlMessage.InnerHtml = "輸入兩次的密碼不一致,請重新輸入";
58 return;
59 }
60
61 IniFile ini = new IniFile( _strServUDaemonPath );
62 string strSectionValue = "USER=" + strUserID.Trim() + "|1";
63
64 //透過讀取指定用戶的HomeDir來確定是否存在該用戶
65 if( ini.ReadString( strSectionValue, "HomeDir", "" ) == "" )
66 {
67 controlMessage.InnerHtml = "指定的使用者不存在";
68 return;
69 }
70
71 //開始判斷密碼是否正確
72 string strPassword = ini.ReadString( strSectionValue, "Password", "" );
73
74 string strPasswordFrontTwoChars;
75 bool bPasswordRight = false;
76 if( strPassword.Length > 2 )
77 {
78 //讀取密碼中包含的隨機字母
79 strPasswordFrontTwoChars = strPassword.Substring( 0, 2 );
80 if( CreateCryPassword( strPasswordFrontTwoChars, txtOldPassword.Text ) == strPassword )
81 {//密碼符合
82 bPasswordRight = true;
83 }
84 else
85 {//密碼不符
86 bPasswordRight = false;
87 }
88 }
89 else if( strPassword == txtOldPassword.Text ) //原密碼為空
90 {
91 bPasswordRight = true;
92 }
93 else
94 {
95 bPasswordRight = false;
96 }
97
98 if( bPasswordRight )
99 {
100 //密碼正確,寫入新的密碼,並設置自動加載新的設置,以便下一次更改時仍有效
101 ini.WriteString( strSectionValue, "Password", CreateCryPassword( GetRandomString(), txtNewPassword.Text ) );
102 controlMessage.InnerHtml = "完成密碼修改";
103 }
104 else
105 {
106 controlMessage.InnerHtml = "原密碼錯誤";
107 }
108
109 }
以上程式碼中的_strServUDaemonPath變數用來保存ServUDaemon.ini檔案所在的路徑,該值可以在PageLoad事件中透過Web.Config設定取得。
但事情並沒有就此結束。經過測試,發現在有一個嚴重的問題:修改密碼後只有重啟Serv-U才能使修改後的密碼生效。那不是等於沒什麼用嘛,管理員總不可能沒事老在那裡重啟伺服器來使密碼修改生效吧。
再次回來Serv-U的官方知識庫,查到如下一條內容:
Manually Updating the ServUDaemon.ini File
Whenever changes are made directly to the ServUDaemon.ini file, add the following line under the Global area in the INI file.
ReloadSettings=True
Serv-U regularly checks the INI file for this setting. If it is preswill, Serv-is present rev- all stored settings for every domain on the server. This allows Serv-U to recognize the changes without having to be restarted.
After Serv-U loads the changes, it removes the "ReloadSettings=True" entry. to Thiue allows entry。 next time any changes are made.
也就是說,只要在INI檔案的GLOBAL節加入鍵ReloadSettings並設定它的值為True便可以實現修改密碼後自動更新了。於是只要修改原程式碼,在101行和102行之間插入如下一句程式碼即可:
ini.WriteString( "GLOBAL", "ReloadSettings", "True" );
到這裡,一個線上修改Serv-U密碼的網頁就全部完成了。
程式中的IniFile是一個封裝了API對INI檔案操作的類,只需要實現對字串的讀取和寫入即可。