2010年10月7日 星期四

砍不掉的pid


最近公司同事碰到了一個問題:

當他的設定檔被變更之後/或者是他的daemon(文後稱為A daemon)被關閉時,

他會叫一個handler起來,

那個handler會根據使用者的設定去啟動一些daemon。

而好巧不巧的,那個handler叫起來的其中一支daemon(文後稱為B daemon),

他會把它自已的previous instance幹掉(送SIGTERM),

然後自己再霸佔那個daemon的位置。

這一切看起來很好,哪邊有差錯呢?

首先呢,我們發現當A daemon被幹掉(SIGTERM)的時候,

他會叫起B daemon,這樣是對的,

接下來我們再把A daemon (SIGTERM)殺掉,

他會再次的叫起B daemon,B daemon應該要把先前的B daemon關掉。

但是實際上,我的device上面有兩支B daemon…

what happens?

# for i in `pidof B`; do grep SigBlk /proc/$i/status; done

發現他們的低bit 15 (0x4000)的位置都被設定了,

也就是說,他們的SIGTERM都被block了… 這是怎麼一回事呢?

首先我們該懷疑的是A daemon,因為他有嫌疑…

# for i in `pidof A`; do grep SigBlk /proc/$i/status; done

這一行的結果你應該只會看到一行,不然就詭異了… ><

發現他的blocked signal一切正常,沒有異樣。

一直到一個高手同事直接打開他的程式碼來看他到底是怎麼做這些事的…

發現他是在atexit的時候去叫handler,

atexit的時候有可能是在SIGTERM發生不久的時候,

所以SIGTERM根本就被block掉了(Linux的機制),

而他生出來的handler跟handler生出來的B daemon也全部繼承了這樣的SigBlk…

所以這樣的程式根本沒辦法經由

# kill `pidof B`

去砍掉它,所以B daemon就不斷的被生出來…

2009年3月22日 星期日

動態產生對話框

因為工作上的需要,所以我需要一個動態產生對話框的程式,

經過survey之後,發現CDialog::InitModalIndirected可以滿足我的需求。

// 這些程式碼僅供概念驗證用,不表示這是沒有問題的程式碼。
// 本人亦不對這些程式碼所產生的損害負責。

wchar_t szCaption[] = L"hi";
wchar_t szFontname[] = L"Tahoma";
wchar_t szItemCaption[] = L"Hello";

DWORD dwSize =
((sizeof(DLGTEMPLATE) + sizeof(WORD) * 2 + sizeof(szCaption) + sizeof(WORD) + sizeof(szFontname) + 3) & ~3) +
3 * ((sizeof(DLGITEMTEMPLATE) + sizeof(WORD) + sizeof(WORD) + sizeof(szItemCaption) + sizeof(WORD) + 3) & ~3);

LPBYTE pBuf = new BYTE[dwSize];

memset(pBuf, 0, dwSize);

LPBYTE ptr = pBuf;
LPDLGTEMPLATE pDlgTmpl = (LPDLGTEMPLATE) pBuf;
LPDLGITEMTEMPLATE pItemTmpl = NULL;

pDlgTmpl->cdit = 2;
pDlgTmpl->cx = 200;
pDlgTmpl->cy = 300;
pDlgTmpl->dwExtendedStyle = WS_EX_APPWINDOW;
pDlgTmpl->style = WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

pDlgTmpl->x = 100;
pDlgTmpl->y = 100;

ptr += sizeof(DLGTEMPLATE);
memset(ptr, 0, sizeof(WORD) * 2);
ptr += sizeof(WORD) * 2;
memcpy_s(ptr, pBuf + dwSize - ptr, szCaption, sizeof(szCaption));
ptr += sizeof(szCaption);

*(WORD *)ptr = 9;
ptr += sizeof(WORD);
memcpy_s(ptr, pBuf + dwSize - ptr, szFontname, sizeof(szFontname));
ptr += sizeof(szFontname);

for (int i=0;i<2;i++) ptr =" (BYTE" pitemtmpl =" (LPDLGITEMTEMPLATE)">cx = 50;
pItemTmpl->cy = 30;
pItemTmpl->dwExtendedStyle = 0;
pItemTmpl->id = 0x1000 + i;
pItemTmpl->style = WS_CHILD | WS_VISIBLE;
pItemTmpl->x = 10;
pItemTmpl->y = 10 + i * 100;

ptr += sizeof(DLGITEMTEMPLATE);
*(WORD *)ptr = 0xFFFF;
ptr += sizeof(WORD);
*(WORD *)ptr = 0x0080;
ptr += sizeof(WORD);

memcpy_s(ptr, pBuf + dwSize - ptr, szItemCaption, sizeof(szItemCaption));
ptr += sizeof(szItemCaption);
*(WORD *)ptr = 0;
ptr += sizeof(WORD);
}

CDialog dlg;
dlg.InitModalIndirect(pDlgTmpl);
dlg.DoModal();

但是透過昨天一整天的打拼之後,發現我的程式一整個是徒勞無功,

因為我只能開對話框出來,一旦我的對話框上面有任何控制項的話,

我的對話框就建不出來,於是我把程式reduce到Win32 SDK上面處理,

把我的所有參數餵給CreateDialogIndirect,然後自己再隨便弄一個 DLGPROC。

登登,結果還是失敗的,如果我的對話框沒有任何控制項在上面的話,程式一切正常,

而且我的DLGPROC還會收到許許多多的對話框訊息,看起來一切正常;

但是我的對話框只要有任何一個控制項在裡面的話,就會發生錯誤,

CreateDialogIndirected傳回(HWND) NULL,可是GetLastError()又只會傳回 (DWORD) 0,

比較特別的是DLGPROC會收到一個WM_DESTROY跟一個WM_NCDESTROY,

這明確的表示出我的程式的對話框是有開出來的,但是上面的控制項可能開不出來,

或是有其他原因,所以我的對話框會直接結束…

結果今天下午繼續弄,發現我的DLGTEMPLATE的dwStyle少了一個屬性,DS_SETFONT,

我簡直快瘋了,為了這個小小的參數讓我搞了昨天一整天 :s

所以可以正常跑的程式如下:

// 這些程式碼僅供概念驗證用,不表示這是沒有問題的程式碼。
// 本人亦不對這些程式碼所產生的損害負責。

wchar_t szCaption[] = L"hi";
wchar_t szFontname[] = L"Tahoma";
wchar_t szItemCaption[] = L"Hello";

DWORD dwSize =
((sizeof(DLGTEMPLATE) + sizeof(WORD) * 2 + sizeof(szCaption) + sizeof(WORD) + sizeof(szFontname) + 3) & ~3) +
3 * ((sizeof(DLGITEMTEMPLATE) + sizeof(WORD) + sizeof(WORD) + sizeof(szItemCaption) + sizeof(WORD) + 3) & ~3);

LPBYTE pBuf = new BYTE[dwSize];

memset(pBuf, 0, dwSize);

LPBYTE ptr = pBuf;
LPDLGTEMPLATE pDlgTmpl = (LPDLGTEMPLATE) pBuf;
LPDLGITEMTEMPLATE pItemTmpl = NULL;

pDlgTmpl->cdit = 2;
pDlgTmpl->cx = 200;
pDlgTmpl->cy = 300;
pDlgTmpl->dwExtendedStyle = WS_EX_APPWINDOW;
pDlgTmpl->style = WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | DS_SETFONT;

pDlgTmpl->x = 100;
pDlgTmpl->y = 100;

ptr += sizeof(DLGTEMPLATE);
memset(ptr, 0, sizeof(WORD) * 2);
ptr += sizeof(WORD) * 2;
memcpy_s(ptr, pBuf + dwSize - ptr, szCaption, sizeof(szCaption));
ptr += sizeof(szCaption);

*(WORD *)ptr = 9;
ptr += sizeof(WORD);
memcpy_s(ptr, pBuf + dwSize - ptr, szFontname, sizeof(szFontname));
ptr += sizeof(szFontname);

for (int i=0;i<2;i++) ptr =" (BYTE" pitemtmpl =" (LPDLGITEMTEMPLATE)">cx = 50;
pItemTmpl->cy = 30;
pItemTmpl->dwExtendedStyle = 0;
pItemTmpl->id = 0x1000 + i;
pItemTmpl->style = WS_CHILD | WS_VISIBLE;
pItemTmpl->x = 10;
pItemTmpl->y = 10 + i * 100;

ptr += sizeof(DLGITEMTEMPLATE);
*(WORD *)ptr = 0xFFFF;
ptr += sizeof(WORD);
*(WORD *)ptr = 0x0080;
ptr += sizeof(WORD);

memcpy_s(ptr, pBuf + dwSize - ptr, szItemCaption, sizeof(szItemCaption));
ptr += sizeof(szItemCaption);
*(WORD *)ptr = 0;
ptr += sizeof(WORD);
}

CDialog dlg;
dlg.InitModalIndirect(pDlgTmpl);
dlg.DoModal();

2008年3月1日 星期六

OpenLDAP 升級

現況說明:

目前系計中有三台主要的LDAP Server,其中有
一、cshome,為LDAP Master,別名為 ldapmaster.cs.nctu.edu.tw
二、csmailgate,為LDAP Slave,別名為 ldap.cs.nctu.edu.tw
三、csduty,為LDAP Slave,別名與csmailgate的別名相同。

cshome每天都會把資料庫文字備份起來

一般情況之下,儘可能保持三台Server的版本相同,避免出現版本不相同而不相容的情事。升級並不需要有任何特定順序,惟注意升級Master時,會附掛replication server,若是重建cshome的bdb時,需特別注意相關問題。

升級ldap的時候,一定要保持冷靜,因為除非兩個系統(csmailgate, csduty)都爛掉了,這樣才會變成很嚴重的問題;即使兩台都爛了,只要第三台沒爛,那資料就一定有救,所以不要太緊張,但是一定要謹慎。

第一步驟,以文字資料備份資料庫
# ldapsearch –x –d ‘cn=Manager,dc=cs,dc=nctu,dc=edu,dc=tw’ –W > csdb.ldif
接下來打入密碼

第二步驟,檢查備份資料完整性
目前來說系計中的LDAP資料筆數大約是3000~4000筆之間,用
# vi csdb.ldif
按下G,可以看到輸出的資料筆數。
往上捲動看幾個一般使用者,檢查他們是否都有userPassword以及NTLMpasswd之類的欄位。

第三步驟,開始升級,在這個地方我們比較不建議使用portupgrade openldap23-server,而是
# cd /usr/ports/net/openldap23-client
# make
# cd
/usr/ports/net/openldap23-server
# make
# sudo
/usr/local/etc/rc.d/slapd
stop
# sudo /usr/local/etc/rc.d/slurpd
stop
# make –DFORCE_PKG_REGISTER
reinstall
# sudo
/usr/local/etc/rc.d/slapd start
# sudo
/usr/local/etc/rc.d/slurpd start
#如果這台是cshome的話
因為這樣的動作可以確保OpenLDAP Server的downtime最短,而portupgrade的好處是自動化,但是它卻不會幫你重新啟動ldap server (而是直接停掉)

第四步驟,檢查資料庫正常運作
# ps auxwww grep slapd
如果有process的話,那麼就有機會是好的,可以接下來做第五個步驟的測試,如果沒有的話;那麼可能就需要做第六步驟的還原了。不確定的話,可以再重新把slapd重新啟動一次。

第五步驟,再次確認資料庫正常運作
# ldapsearch –x –H ldaps://HOSTNAME ‘(uid=jtwang)’
HOSTNAME的部份填入相對應的hostname,有可能是csduty、csmailgate、cshome
確定HOSTNAME的部份相當重要。

如果真的有查到資料,恭喜你,你的資料庫已經成功的升級了,可以省略本文件以下的內容。

第六步驟,還原資料庫
如果你的slapd開起來之後沒多久就死掉,或者是根本就開不起來,建議你可以先使用

# /usr/local/libexec/slapd –D 255

啟動slapd來看錯誤訊息,如果發現是無可捥救的錯誤訊息的話,那就可以把資料庫砍了

# sudo mv /var/db/openldap-data /var/db/openldap-data-`date ….`
# sudo mkdir
/var/db/openldap-data
# sudo chmod 700 /var/db/openldap-data
# sudo
chown
ldap:ldap /var/db/openldap-data
# sudo /usr/local/etc/rc.d/slapd
start
然後把原本的資料倒回去
基本上,你要確定倒出的資料前兩筆一定要是

Dn: cn=Manager,dc=cs,dc=nctu,dc=edu,dc=tw .


Dn:
cn=cs,dc=nctu,dc=edu,dc=tw
接下來再
# ldapadd –D “cn=Manager,dc=cs,dc=nctu,dc=edu,dc=tw” –H ldaps://HOSTNAME –W –f csdb.ldif
一定要注意,-H 那一段一定要加,以免倒錯機器。
理論上你會看到一大堆add entry …. “uid=UID,ou=People,dc=cs,dc=nctu,dc=edu,dc=tw”之類的,一直到結束。如果可以安然的執行到結束而且沒有錯誤訊息的話,那麼恭喜你,你的升級動作有驚無險的完成了。可以省略以下的文件。

如果又冒出錯誤訊息,那麼你的升級之路還沒結束…

第N-1步驟,檢查你的錯誤訊息
如果錯誤訊息是syntax error或是什麼value之類的,則詢問一下之前的maintainer或問你自己是不是有修改過schema,可能是之前可以接受的資料類型,而現在不支援了。

第N步驟,Google it
把它找出問題發現的原因,並解決它。這部份,這篇文章只能提示你這些資訊了…

2008年2月18日 星期一

syncpasswd

最近系計中assign了一個工作給我,就是負責把 每天自動sync nis的passwd到ldap上的script改進到也會一起sync gecos info。

這個工作很單純的,但是學長就是說用直接處理的方式會出現問題,所以一定有奇怪的問題存在,所以我就感到有點可怕了…

1. 最基本上想法,就是把nis上的gecos解出來,然後放到ldap的匯入格式 ldif,然後再叫 ldapmodify來吃就好了。
(如果事情有這麼單純,這樣真的就解決了)

2. 但是大家的gecos info都會亂塞資料,像是我的名字是繁體中文的,而ldap只吃utf-8的資料,所以就要先轉碼囉,用perl的話,就要用 Text::Iconv 這個東西了 :p 可是就算是改成utf-8,ldap一樣吃不進去,google告訴我,預設的ldap的nis.schema中的gecos這個欄位只吃IA5String,所以要改一下schema。

#attributetype ( 1.3.6.1.1.1.1.2 NAME 'gecos'
# DESC 'The GECOS field; the common name'
# EQUALITY caseIgnoreIA5Match
# SUBSTR caseIgnoreIA5SubstringsMatch
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )

attributetype ( 1.3.6.1.1.1.1.2 NAME 'gecos'
DESC 'The GECOS field; the common name'
EQUALITY caseExactMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )

這樣就可以在gecos中擺UTF-8字元了

3. 再來就是ldif裡面出現空白好像會出問題,所以要餵LDAP吃有空白的資料的話,那麼就得先把它轉成Base64,這個要用到 MIME::Base64

4. 就可以準備來玩匯入的功能了,不幸的是,有些人的匯入結果會顯示 Invalid format ...
看一下文字檔的內容,應該是因為gecos是沒有東西的關係… 那麼就是 gecos是空白的話,那就不要去replace gecos

5. 匯入還是出現錯誤,結果發現有些使用者不存在LDAP裡面,總管說把那些帳號建回來。(用MigrationTools)

6. 還是有錯誤,一樣是Invalid format,那麼就用暴力法,
# sudo ypcat master.passwd | grep uid
馬賽克:馬賽克:馬賽克:馬賽克::0:0:馬賽克,馬賽克,馬賽克 ,馬賽克,馬賽克:馬賽克:/bin/tcsh

有關個人隱私與安全部份全部被我馬賽克取代了… 看起來一切都好,那問題是出在哪邊呢?
換另外一個試試,這個gecos就比較長了,也讓我好奇是不是超過長度,但是我的gecos跟他的一樣長,我的就沒事哩!?不過我注意到一點,為什麼他的gecos會一半就斷掉了哩?
# sudo ypcat master.passwd | grep uid | cut -d":" -f 8 | hd
結果被我發現帳號的gecos裡面居然藏了一個 0x0A(換行字元),我想應該是這邊出問題吧 Orz

2008年2月17日 星期日

安裝mysql, php, apache2(非AppServer)

Abstract

分別到 www.mysql.com, www.php.net, www.apache.org 捉取最新(或最適合)版本的程式。 特別需要注意的是,PHP我們不捉msi版,而是要捉zip版,因為msi是精簡包,會缺相當多的工具; 不過也可以捉msi版來安裝,把zip版來當工具包。

p.s.

本文撰寫時,MySQL 5.0 的最新的版本為 MySQL 5.0.16,PHP 5則是 PHP 5.1.1

Step by Step

MySQL

除了step by step外,後面還會要求你Configure
MySQL 需要設定成Install As Windows Service
如果需要的話,可以勾選Include Bin Directory in Windows Path
一定要Modify Security Setting,去設定root@localhost的密碼

PHP

不是捉msi版的使用者把 php-5.1.1.zip解開丟到C:\PHP下面,
然後把C:\PHP\php.ini-dist複製一份到C:\Windows底下,然後記得把php.ini-dist改成php.ini,
打開php.ini,找到

extension_dir = "./"
感謝諺哥提醒:
應該要複製一份 c:\php\libmysql.dll到c:\program files\apache group\apache2\bin裡面,
這樣才能用php_mysql.dll, php_mysqli.dll
改成
extension_dir = "C:\PHP\ext"
還有
;extension=php_mysql.dll
;extension=php_mysqli.dll
去掉前面的分號

Apache

接下來,就用Step by Step 的方式把Apache裝起來。
打開
C:\Program Files\Apache Group\Apache2\conf\httpd.conf

#LoadModule ssl_module modules/mod_ssl.so
後面加入一行:
LoadModule php5_module /php/php5apache2.dll
找到
AddType application/x-gzip .gz .tgz
後面加入
AddType application/x-httpd-php .php .phtml
AddType application/x-httpd-php-source .phps
啟動MySQL, Apache2

OpenSSL

需要軟體
openssl

SSL 採用 X.509, 由上而下的金字塔憑證制度 ( Root CA -> CA -> Certificate )

建立RootCA的私鑰(Private Key) rootca.key

# mkdir -p /usr/local/etc/ssl
# /usr/local/etc/ssl
# cd /usr/local/etc/ssl
# openssl genrsa -des3 -out rootca.key 2048

Generating RSA private key, 2048 bit long modulus
..............................................................+++
.........+++
e is 65537 (0x10001)
Enter pass phrase for rootca.key:

要求輸入private key的密碼(repeat)


產生RootCA憑證申請書 rootca.csr


# openssl req -new -key rootca.key -out rootca.req
然後輸入RootCA的私鑰(private key)密碼
然後填資料
* 注意申請書的密碼跟憑證代辦公司的名稱按enter直接跳過

以root CA的身分簽發憑證

需要注意的是最高認證中心不可以太早過期, 因為如果最高認證中心的憑證過期的話, 其簽發的憑證也會失效,所以我們這邊設定成20年

# openssl x509 -req -days 7305 -sha1 -extfile /etc/ssl/openssl.cnf -extensions v3_ca -signkey rootca.key -in rootca.req -out rootca.crt

然後顯示一些資訊後 輸入rootca的私鑰密碼

ldap:

產生ldap server的private key(ldap.cs.nctu.edu.tw.key)


* 如果你為ldap server的私鑰加密碼的話, 每次啟動Server都要再輸入密碼

# openssl genrsa -out ldap.cs.nctu.edu.tw.key 2048
Generating RSA private key, 2048 bit long modulus
............................................................................+++
.............................................................................................+++
e is 65537 (0x10001)

產生ldap server的憑證申請書(ldap.cs.nctu.edu.tw.csr)

# openssl req -new -key ldap.cs.nctu.edu.tw.key -out ldap.cs.nctu.edu.tw.csr
* 輸入資料, 這邊很重要的是Common Name必須設定成 ldap server的FQDN相同, 不同的話會不能用
* 注意申請書的密碼跟憑證代辦公司的名稱按enter直接跳過



最高認證中心(RootCA,自己)發給ldap server憑證(ldap.cs.nctu.edu.tw.crt)

* 此例發給十年的憑證

# openssl x509 -req -days 3650 -sha1 -extfile /etc/ssl/openssl.cnf -extensions v3_req -CA rootca.crt - CAkey rootca.key -CAserial rootca.srl -CAcreateserial -in ldap.cs.nctu.edu.tw.csr -out ldap.cs.nctu.edu.tw.crt

然後顯示一些資訊,再輸入rootca的私鑰密碼。

完成

這樣就完成最高憑證中心與伺服器的認證了,大概看一下檔名與檔案身份的對應

rootca.key (最高憑證中心的私鑰)
rootca.csq (最高憑證中心的憑證申請書)
rootca.crt (最高憑證中心的憑證)

ldap.cs.nctu.edu.tw.key
ldap.cs.nctu.edu.tw.csq
ldap.cs.nctu.edu.tw.crt

Squid安裝

Introduction

效能決定在於:硬碟大小、使用人數、記憶體快取大小。

Proxy可分為硬體式、與軟體式。

Installation (fedora core 1)

1. 取得squid之rpm安裝檔

2. 以rpm –ivh squid-2.5.STABLE1-2.i386.rpm 安裝

3. 設定Squid

甲、 打開/etc/squid/squid.conf

乙、 找到cache_mem 的部份,把cache_men修改為電腦記憶體之三分之一(據說這是最佳值)

丙、 找到cache_dir的部份,將之設定到你要儲存的位罝,並將內容

cache_dir ufs /var/spool/squid 1000 16 256

ufs:代表儲存格式。尚有aufs diskd可供使用

1000:代表快取容量(M)。

16為第一層的資料夾數。256為第二層的資料夾數。

丁、 找到http_access的部份,http_access 設定允許、禁止哪些網域被存取

acl 的設定方式。

acl <列表名稱> <210.240.180.0/24>

http_access <列表名稱>

squid是循序處理規則的,規則的設定應該是由小到大的。

戊、 找到visible_hostname的部份,將之設定為全域名稱

4. 啟動測試 鍵入/etc/init.d/squid start 或是 service squid start都可以

5. 鍵入ntsysv 將squid 設為開機自動啟動。


Installation (gentoo)

1.
# emerge squid
2. 設定Squid

甲、 打開/etc/squid/squid.conf

乙、 找到cache_mem 的部份,把cache_men修改為512M (要是你有1G的記憶體的話)

丙、 找到cache_dir的部份,將之設定到你要儲存的位罝,並將內容

# vi /etc/squid/squid.conf
cache_mem 512M
cache_dir ufs /var/spool/squid 1000 16 256
ufs:代表儲存格式。尚有aufs diskd可供使用

1000:代表快取容量(M)。

16為第一層的資料夾數。256為第二層的資料夾數。

丁、 找到http_access的部份,http_access 設定允許、禁止哪些網域被存取

acl 的設定方式。

acl <列表名稱> <210.240.180.0/24>

for example:
acl dorm src 140.113.252.0/24
acl yahoo dst tw.yahoo.com.tw
acl bid dst tw.bid.yahoo.com.tw
http_access <列表名稱>

for example:
http_access allow dorm
http_access deny bid
http_access allow yahoo
squid是循序處理規則的,規則的設定應該是由小到大的。

戊、 找到visible_hostname的部份,將之設定為全域名稱

例如

visible_hostname proxy.cs.nctu.edu.tw

4. 啟動
# /etc/init.d/squid start
5. 設為開機自動啟動
# rc-update add squid default