Saturday, December 22, 2007

ldap connect AD

開始:
1. 建立 IIS SSL
2. 將 CA Certificate 加入至 jre keystore 裡
3. JNDI 連 AD

1. 建立 IIS SSL:
Install Windows 2003 Server:

Install AD:
Start -> Run -> dcpromote
domain name : joeyta-DOT-local
NT domain name : joeytaserver
即 Fully Qualified Domain Name (FQDN) 為 joeytaserver.joeyta-DOT-local

先安裝 IIS , 再安裝 CA.

Install IIS:
Start -> Programs -> Administrative Tools -> Configure Your Server Wizard
->> Next -> Next -> Application server (IIS, ASP.NET) -> Next

進入 http://joeyserver.joeyta.local/postinfo.html 表示安裝成功.

Install CA:
Start -> Settings -> Control Panel -> Add or Remove Programs
->> Add/Remove Windows Components
選擇 Certificate Services -> Next
選擇 Enterprise root CA -> Next
Common name for this CA: testca -> Next

進入 http://joeyserver.joeyta.local/CertSrv 表示安裝成功.


Generating a Certificate Signing Request:
Start -> Programs -> Administrative Tools -> Internet Information Services (IIS) Manager
->> Internet Information Services -> (local computer) -> Web Sites
-> > 右鍵點選 Default Web Site -> Properties
選擇 "Directory Security" -> Server Certificate
->> Create a new certificate -> Prepare the request now, but send it later
一直按 Next , 需要注意的是 Common name 必須為 joeyserver.joeyta.local, 這是給使用者連 ssl 的 website.
最後產生 certificate request file , 預設為 c:\certreq.txt


Request a certificate on CA:
進入 http://joeyserver.joeyta.local/CertSrv
按 Request a certificate -> advanced certificate request
-> Submit a certificate request by using a base-64-encoded CMC or PKCS#10 file, or submit a renewal request by using a base-64-encoded PKCS#7 file
使用 notepad 打開 c:\certreq.txt , copy c:\certreq.txt 內容貼至 Saved Request:
Certificate Template 選擇 Web Server, 按 Submit
然後點選 Download certificate , 將 certnew.cer 儲存至 c:\certnew.cer


Installing a Certificate:
Start -> Programs -> Administrative Tools -> Internet Information Services (IIS) Manager
->> Internet Information Services -> (local computer) -> Web Sites
-> > 右鍵點選 Default Web Site -> Properties
選擇 "Directory Security" -> Server Certificate
->> Process the pending request and install the certificate -> Next
Path and file name: c:\certnew.cer -> Next
SSL port this web site should use: 443 -> Next -> Next -> Finish


2. 將 CA Certificate 加入至 jre keystore 裡:
進入 http://joeyserver.joeyta.local/CertSrv
點選 Download a CA certificate, certificate chain, or CRL
點選 Download CA certificate , 然後下載並改名為 c:\testca_cert.cer

然後執行 command:
c:\temp>keytool -import -alias testca_cert -file "/testca_cert.cer" -keystore "/jdk1.5.0_09/jre/lib/security/cacerts" -storepass "changeit"

出現 Trusted this certificate? 按 "y" 即新增成功.


3. JNDI 連 AD:

/***************************** LDAPFastBind.java *****************/
package test.ldap;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;

class FastBindConnectionControl implements Control {
public byte[] getEncodedValue() {
return null;
}

public String getID() {
return "1.2.840.113556.1.4.1781";
}

public boolean isCritical() {
return true;
}
}

public class LDAPFastBind {
public Hashtable env = null;

public LdapContext ctx = null;

public Control[] connCtls = null;

public LDAPFastBind(String ldapurl) {
env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.PROVIDER_URL, ldapurl);

env.put(Context.SECURITY_PROTOCOL,"ssl");

String keystore = "/jdk1.5.0_09/jre/lib/security/cacerts";
System.setProperty("javax.net.ssl.trustStore",keystore);

connCtls = new Control[] { new FastBindConnectionControl() };

// first time we initialize the context, no credentials are supplied
// therefore it is an anonymous bind.

try {
ctx = new InitialLdapContext(env, connCtls);

} catch (NamingException e) {
System.out.println("Naming exception " + e);
}
}

public boolean Authenticate(String username, String password) {
try {
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, username);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
ctx.reconnect(connCtls);
System.out.println(username + " is authenticated");
return true;
}

catch (AuthenticationException e) {
System.out.println(username + " is not authenticated");
System.out.println(e);
return false;
} catch (NamingException e) {
System.out.println(username + " is not authenticated");
System.out.println(e);
return false;
}
}

public void finito() {
try {
ctx.close();
System.out.println("Context is closed");
} catch (NamingException e) {
System.out.println("Context close failure " + e);
}
}

public void printUserAccountControl() {
try {

// Create the search controls
SearchControls searchCtls = new SearchControls();

// Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

// specify the LDAP search filter
//String searchFilter = "(&(objectClass=user)(CN=test))";
//String searchFilter = "(&(objectClass=group))";
String searchFilter = "(&(objectClass=user)(CN=peter lee))";

// Specify the Base for the search
String searchBase = "DC=joeyta,DC=local";

// initialize counter to total the group members
int totalResults = 0;

// Specify the attributes to return
String returnedAtts[] = { "givenName", "mail" };
searchCtls.setReturningAttributes(returnedAtts);

// Search for objects using the filter
NamingEnumeration answer = ctx.search(searchBase, searchFilter,
searchCtls);

// Loop through the search results
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult) answer.next();

System.out.println(">>>" + sr.getName());

// Print out the groups

Attributes attrs = sr.getAttributes();
if (attrs != null) {

try {
for (NamingEnumeration ae = attrs.getAll(); ae
.hasMore();) {
Attribute attr = (Attribute) ae.next();
System.out.println("Attribute: " + attr.getID());
for (NamingEnumeration e = attr.getAll(); e
.hasMore(); totalResults++) {

System.out.println(" " + totalResults + ". "
+ e.next());
}

}

} catch (NamingException e) {
System.err.println("Problem listing membership: " + e);
}

}
}

System.out.println("Total attrs: " + totalResults);

}

catch (NamingException e) {
System.err.println("Problem searching directory: " + e);
}

}

public boolean adminChangePassword(String sUserName, String sNewPassword){
try {

//set password is a ldap modfy operation
ModificationItem[] mods = new ModificationItem[1];

//Replace the "unicdodePwd" attribute with a new value
//Password must be both Unicode and a quoted string
String newQuotedPassword = "\"" + sNewPassword + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");

mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));

// Perform the update
ctx.modifyAttributes(sUserName, mods);

System.out.println("Reset Password for: " + sUserName);

return true;
}
catch (NamingException e) {
System.out.println("Problem resetting password: " + e);
}
catch (UnsupportedEncodingException e) {
System.out.println("Problem encoding password: " + e);
}
return false;
}

public boolean userChangePassword(String sUserName, String sOldPassword, String sNewPassword){
try {
//StartTlsResponse tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest());
//tls.negotiate();

//change password is a single ldap modify operation
//that deletes the old password and adds the new password
ModificationItem[] mods = new ModificationItem[2];

//Firstly delete the "unicdodePwd" attribute, using the old password
//Then add the new password,Passwords must be both Unicode and a quoted string
String oldQuotedPassword = "\"" + sOldPassword + "\"";
byte[] oldUnicodePassword = oldQuotedPassword.getBytes("UTF-16LE");
String newQuotedPassword = "\"" + sNewPassword + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");

mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("unicodePwd", oldUnicodePassword));
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));

// Perform the update
ctx.modifyAttributes(sUserName, mods);

System.out.println("Changed Password for: " + sUserName);
//tls.close();
return true;

}
catch (NamingException e) {
System.err.println("Problem changing password: " + e);
}
catch (UnsupportedEncodingException e) {
System.err.println("Problem encoding password: " + e);
} catch ( Exception e){
System.err.println("Problem: " + e);
}
return false;
}

public boolean createNewUser(String sGroupName, String sUserName){
try {
// Create attributes to be associated with the new user
Attributes attrs = new BasicAttributes(true);

//These are the mandatory attributes for a user object
//Note that Win2K3 will automagically create a random
//samAccountName if it is not present. (Win2K does not)
attrs.put("objectClass","user");
attrs.put("sAMAccountName","AlanT");
attrs.put("cn","Alan Tang");

//These are some optional (but useful) attributes
attrs.put("givenName","Alan");
attrs.put("sn","Tang");
attrs.put("displayName","Alan Tang");
attrs.put("description","Engineer");
attrs.put("userPrincipalName","alan-AT-joeyta.local");
attrs.put("mail","alang-AT-mail.joeyta-DOT-local");
attrs.put("telephoneNumber","123 456 789");

//some useful constants from lmaccess.h
int UF_ACCOUNTDISABLE = 0x0002;
int UF_PASSWD_NOTREQD = 0x0020;
int UF_PASSWD_CANT_CHANGE = 0x0040;
int UF_NORMAL_ACCOUNT = 0x0200;
int UF_DONT_EXPIRE_PASSWD = 0x10000;
int UF_PASSWORD_EXPIRED = 0x800000;

//Note that you need to create the user object before you can
//set the password. Therefore as the user is created with no
//password, user AccountControl must be set to the following
//otherwise the Win2K3 password filter will return error 53
//unwilling to perform.

attrs.put("userAccountControl",Integer.toString(UF_NORMAL_ACCOUNT + UF_PASSWD_NOTREQD + UF_PASSWORD_EXPIRED+ UF_ACCOUNTDISABLE));

// Create the context
Context result = ctx.createSubcontext(sUserName, attrs);
System.out.println("Created disabled account for: " + sUserName);

//now that we've created the user object, we can set the
//password and change the userAccountControl
//and because password can only be set using SSL/TLS
//lets use StartTLS

//StartTlsResponse tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest());
//tls.negotiate();

//set password is a ldap modfy operation
//and we'll update the userAccountControl
//enabling the acount and force the user to update ther password
//the first time they login
ModificationItem[] mods = new ModificationItem[2];

//Replace the "unicdodePwd" attribute with a new value
//Password must be both Unicode and a quoted string
String newQuotedPassword = "\"P-AT-ssw0rd\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");

mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));
mods[1] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_NORMAL_ACCOUNT + UF_PASSWORD_EXPIRED)));

// Perform the update
ctx.modifyAttributes(sUserName, mods);
System.out.println("Set password & updated userccountControl");


//now add the user to a group.

try {
ModificationItem member[] = new ModificationItem[1];
member[0]= new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", sUserName));

ctx.modifyAttributes(sGroupName,member);
System.out.println("Added user to group: " + sGroupName);

}
catch (NamingException e) {
System.err.println("Problem adding user to group: " + e);
}
//Could have put tls.close() prior to the group modification
//but it seems to screw up the connection or context ?
//tls.close();

System.out.println("Successfully created User: " + sUserName);
return true;

}
catch (NamingException e) {
System.err.println("Problem creating object: " + e);
}

catch (IOException e) {
System.err.println("Problem creating object: " + e);
}
return false;
}

public boolean addUserToGroup(LdapContext ctx, String userDN, String groupDN) {
try{
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", userDN));
ctx.modifyAttributes(groupDN, mods);
System.out.println("Added user " + userDN + " to group " + groupDN);
return true;
} catch (NamingException ne){
System.err.println("Problem add user to group: " + ne);
}
return false;
}

public boolean removeUserFromGroup(LdapContext ctx, String userDN, String groupDN) {
try{
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("member", userDN));
ctx.modifyAttributes(groupDN, mods);
System.out.println("Remove user " + userDN + " from group " + groupDN);
return true;
} catch (NamingException ne){
System.err.println("Problem remove user from group: " + ne);
}
return false;
}

}
/***************************** LDAPFastBind.java *****************/





/***************************** LDAPClient.java *****************/
package test.ldap;

class LDAPClient {
public static void main(String[] args) {
// Could also use ldaps over port 636 to protect the communication to
// the
// Active Directory domain controller. Would also need to add
// env.put(Context.SECURITY_PROTOCOL,"ssl") to the "server" code
//String ldapurl = "ldap://joeyserver.joeyta.local:389";
String ldapurl = "ldap://joeyserver.joeyta.local:636";

LDAPFastBind ctx = new LDAPFastBind(ldapurl);

String sAdminUserName = "CN=Administrator,CN=Users,DC=joeyta,DC=local";
String sAdminPassword = "I@mRoot";

// String sUserName = "CN=peter lee,CN=Users,DC=joeyta,DC=local";
String sUserName = "joeyta\\peter";
// String sUserName = "peter@joeyta.local";

String sOldPassword = "P@ssw0rd";
String sNewPassword = "P@$w0rd";

String sNewUserName = "CN=Alan Tang,CN=Users,DC=joyeta,DC=local";
String sNewGroupName = "CN=test,CN=Users,DC=joeyta,DC=local";

boolean IsAuthenticated = ctx.Authenticate(sAdminUserName, sAdminPassword);
// boolean IsAuthenticated = ctx.Authenticate(sUserName, sOldPassword);

ctx.printUserAccountControl();

ctx.createNewUser(sNewGroupName, sNewUserName);

//boolean IsAdminSuccessChangePWD = ctx.adminChangePassword(sUserName,sNewPassword);
//boolean IsUserSuccessChangePWD = ctx.userChangePassword(sUserName,sOldPassword,sNewPassword);

ctx.finito();

}
}
/***************************** LDAPClient.java *****************/

Saturday, December 15, 2007

Ubuntu 好用軟體

edonkey - aMule

sudo apt-get install aMule

不用說了吧,反正就是那回事,中文,日文都 ok 的啦

IM - Gaim(Pidgin)

萬用 Instant Messager,Ubuntu內建,新版改名叫 Pidgin,變得漂亮多了

BBS - PCManX

sudo apt-get install pcmanx-gtk2

好用的 BBS client,記得要換字型比較好看

Packet Sniffer - Ethereal

sudo apt-get install ethereal

看封包內容的軟體,功能強大,不過我只拿來 debug http 的 resquest/response

BT - Deluge

sudo apt-get install deluge-torrent

就是 BT,gnome 是有內建,但是那個太陽春了,換這個 python 寫的吧

看漫畫 - Comix

sudo apt-get install comix

這個超讚,用電腦看漫畫必備!可以設定左右兩頁合併成一頁,也支援右頁翻到左頁 (漫畫都是右到左)。當然你要拿來瀏覽一般圖片也行,速度很快。

向量繪圖 - Inkscape

sudo apt-get install inkscape

雖然我不會美工,但有時候還是得改一些 icon,或是做一些簡單的 logo。inkscape 用起來挺直覺的,連我這個繪圖白吃都玩的起~

安裝外部軟體 - Automatix

sudo apt-get install automatix2

Skype, Adobe Reader, Windows codec, 讀寫 NTFS 等等涉及版權的軟體安裝起來都超麻煩的,這時 Automatix 就派上用場了,點幾下那些難纏的軟體通通裝好設定完全。爽~

錄音 - Audacity

sudo apt-get install audacity

多軌錄音軟體,介面很醜... 不過夠我錄音樂用了。

看影片 - vlc

sudo apt-get install vlc

你什麼 codec 都可以不裝,就是不能漏掉這個 vlc,連 wmv9 都可以看,真是天賜啊

字典 - 星際譯王

sudo apt-get install stardict

沒有這個字典活不下去

virtual machine - VirtualBox

sudo apt-get install virtualbox

我已經放棄 vmware 了,vmware player 太陽春,vmware workstation 要錢,vmware server 安裝太麻煩。每次 kernel 升級 vmware 就要重新 compile... etc 真是煩死了。一氣之下轉換到 virtualbox,從此過著幸福快樂的日子。還死守在 vmware 的同胞們快點換吧~

screen action - brightside

sudo apt-get install brightside

裝了這個就可以設定滑鼠碰觸畫面角落,開啟特定程式或是顯示桌面,我用慣 mac 的滑鼠顯示桌面了,所以裝這個來模擬。

Palm - J-Pilot

sudo apt-get install jpilot

如果你跟我一樣還在用那個不上道的 palm (treo650),這個可以解決 sync 的問題,Big5 中文也通喔!(如果弄得到 iphone 我就把 treo 給丟了)

nVidia - envy

sudo apt-get install envy

nVidia 的用戶,一定得安裝 envy。這個軟體可以解決所有有關 nVidia 的問題,XWindow 裝不起來嗎?beryl 特效弄不出來嗎?想裝 nVidia 官方的 driver 嗎?裝 envy 就對了。我的 LCD 是 LED 背光,因此顏色是偏藍的,這時就得靠 nVidia 官方的 nvidia-setting 這個工具來微調了。用 envy 安裝官方 driver 後,輕輕鬆鬆搞定!

Google Desktop

http://desktop.google.com/linux

google 最新推出的 linux 版桌面搜尋,安裝後 ctrl 兩下就能搜尋囉,還蠻好用地,算是補足 linux 一直欠缺的功能。啥?你說之前已經有 beagle 了?拜託,那是用 mono 寫得耶,好不容易轉到 linux 了,幹嘛還被 ms 套牢?

中文字型 OpenDesktop Font

http://www.opendesktop.org.tw/modules/news/article.php?storyid=106

經由 OpenDesktop 團隊的努力,我們有全世界最漂亮、最清楚的中文字型囉。Mac上字型透過獨家的 render 技術產生的字型很棒,但就算 render 技術再強,永遠也比不上一個個精雕細啄的點陣字啊!

其他太基本的 gcin, java, eclipse, gimp, remote desktop 等等就不提了

Thursday, October 25, 2007

vpn client in ubuntu

1. 安裝 Network Manager

在 gnome 環境下,有一個套件叫作 Network Manager。它有幾個好處:

* 可以像 windows 一樣自動尋找,並管理無線網路
* 可以在有線及無線網路中互相切換
* 可以利用 plugin 來連結 vpn :D

用了這個軟體後,在 nb 上使用無線網路就變得簡單又方便了。所以首先我們要安裝 Network Manager

sudo apt-get install network-manager
sudo apt-get install network-manager-gnome

Network Manager 是一個系統的 service,而 Network Manager Gnome 則提供了一個 gnome 的 applet 來顯示網路狀態。所以我們要讓 gnome 在啟動時,自動執行這個 applet。

在 系統>偏好設定>作業階段>初始啟動程式 下
新增一個初始啟動指令 nm-applet
這樣下次重新啟動 xorg 或重啟電腦後,在右上角的程式狀態通知區就會看到一個網路連線的圖示了。(目前先不要重啟哦)

不過如果要讓 Network Manager 管理網路連線,就不能讓 gnome 內建的網路管理工具管理。所以我們要先取消它的管理權。

在 系統>管理>網路 下
針對你想讓 Network Manager 管理的介面,設定屬性
啟用這個連線上不要打勾!

2. 安裝及設定 VPN

接著要安裝 pptp vpn 相關的軟體,由於 Network Manager 的 pptp Plugin 目前還是 universe的,所以要先打開 universe 的套件庫。然後執行以下指令

sudo apt-get install pptp-linux
sudo apt-get install network-manager-pptp


接下來,就可以重啟 xorg 或電腦了。
重啟後,在右上角看到網路連線的圖示,按左鍵就可以設定網路或 vpn.
a. 選擇 VPN Connections > Configure VPN


b. 選擇 PPTP Tunnel

c. 設定 vpn gateway

d. 在 Authentication 下面我是這樣設的。

e. Encryption 下我是都沒有勾

存檔後就可以了,接著就來連線。
點選 vpn 的選項之後,右上角的圖示就會轉來轉去,轉完後就可以連上了。

3. Troubleshooting

我第一次裝的時候,vpn connection 設定,我所設定的 vpn 就是不會出來。網路上有 FAQ 說要刪除一些設定檔: (原文)

/etc/NetworkManager/VPN/nm-pptp-service.name
/etc/dbus-1/system.d/nm-pptp-service.conf

我當時不加思索照作,當場更慘,一旦想要增加新的 vpn,馬上就會出現沒有安裝 vpn 相關軟體的對話窗。我當場利用 windows 思維,重裝 network-manager-pptp,不過並沒有效。
後來是把 network-manager, linux-pptp, network-manager-pptp 及 network-manager-gnome 全部都進行徹底移除 (用 synaptic 即可)。確定 /etc/NetworkManager 及 ~/.gconf/system/networking/vpn_connections 不存在或裏面沒檔案了,再一個個重新裝回來。重裝之後就正常了。

根據我現在正常的狀態,上述兩個檔案的確是不存在,所以該 FAQ 的作法似乎也有其道理。不過確切的原因還有待研究。

Wednesday, October 17, 2007

用 linux 連接 windows 的share folder

mount -t smbfs -o iocharset=utf8,codepage=unicode,unicode,username='<domain>\doug' //192.168.128.221/TDD /mnt/tdd/

Friday, August 10, 2007

java display 中文 in linux

1.
首先在java目錄下的jre/lib/fonts下建立fallback目錄
例如
$sudo mkdir /usr/lib/jdk1.5.0_05/jre/lib/fonts/fallback
2.
然後在fallback目錄下放置中文字體的檔案或連結
$sudo ln -s /usr/share/fonts/truetype/arphic/uming.ttf
/usr/lib/jdk1.5.0_05/jre/lib/fonts/fallback/
3.
進入fallback目錄中建立fonts.scale檔案
$cd /usr/lib/jdk1.5.0_05/jre/lib/fonts/fallback/
$sudo mkfontscale
4
最後將fonts/fallback/fonts.scale檔的內容貼到fonts/fonts.dir
兩個檔案的第一行數字加總到fonts.dir內

測試的話可以ControlCenter試試,若叫出來是中文的那麼應該就可以了

$/usr/lib/jdk1.5.0_05/bin/ControlPane

Sunday, May 13, 2007

抗工作壓力

時間久了,往往對寫程式的熱情會遞減
原本在 10 分鐘可以完成的,會因為下不了手而延遲,導致時間到了
卻沒有任何進展

如何解決此問題呢,自己也在慢慢的摸索當中

跳脫此框框可以讓自己再往上成長一步,也讓生活可以更快活
不是嗎?

Thursday, May 10, 2007

Java Web Calendar

Another important mission is to look for web calendar that written in java

Any solution out there?

For what I know, there are Web Calendar write in java applet and php, but
not j2ee based purely.

headache. *_*

Wednesday, May 9, 2007

bug tracking system

Which bug tracking system should be used for a company?

There are several open source there. Some are written in java, others are written in python.

No matter what language is used for the bug tracking system, easy use and maintain is
the one I need.

Scarab, trac, jtrac?