diff options
-rw-r--r-- | fs/cifs/CHANGES | 6 | ||||
-rw-r--r-- | fs/cifs/Makefile | 2 | ||||
-rw-r--r-- | fs/cifs/cifsencrypt.c | 6 | ||||
-rw-r--r-- | fs/cifs/cifsfs.c | 5 | ||||
-rw-r--r-- | fs/cifs/cifsfs.h | 2 | ||||
-rw-r--r-- | fs/cifs/cifsglob.h | 11 | ||||
-rw-r--r-- | fs/cifs/cifsproto.h | 10 | ||||
-rw-r--r-- | fs/cifs/cifssmb.c | 32 | ||||
-rw-r--r-- | fs/cifs/connect.c | 50 | ||||
-rw-r--r-- | fs/cifs/dir.c | 4 | ||||
-rw-r--r-- | fs/cifs/inode.c | 3 | ||||
-rw-r--r-- | fs/cifs/misc.c | 6 | ||||
-rw-r--r-- | fs/cifs/ntlmssp.c | 130 | ||||
-rw-r--r-- | fs/cifs/ntlmssp.h | 2 |
14 files changed, 215 insertions, 54 deletions
diff --git a/fs/cifs/CHANGES b/fs/cifs/CHANGES index d335015473a..a9cf779cf35 100644 --- a/fs/cifs/CHANGES +++ b/fs/cifs/CHANGES @@ -1,3 +1,9 @@ +Version 1.41 +------------ +Fix NTLMv2 security (can be enabled in /proc/fs/cifs) so customers can +configure stronger authentication. Fix sfu symlinks so they can +be followed (not just recognized). + Version 1.40 ------------ Use fsuid (fsgid) more consistently instead of uid (gid). Improve performance diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile index 7384947a0f9..58c77254a23 100644 --- a/fs/cifs/Makefile +++ b/fs/cifs/Makefile @@ -3,4 +3,4 @@ # obj-$(CONFIG_CIFS) += cifs.o -cifs-objs := cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o inode.o link.o misc.o netmisc.o smbdes.o smbencrypt.o transport.o asn1.o md4.o md5.o cifs_unicode.o nterr.o xattr.o cifsencrypt.o fcntl.o readdir.o ioctl.o +cifs-objs := cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o inode.o link.o misc.o netmisc.o smbdes.o smbencrypt.o transport.o asn1.o md4.o md5.o cifs_unicode.o nterr.o xattr.o cifsencrypt.o fcntl.o readdir.o ioctl.o ntlmssp.o diff --git a/fs/cifs/cifsencrypt.c b/fs/cifs/cifsencrypt.c index a2c24858d40..c2cbe0ed98b 100644 --- a/fs/cifs/cifsencrypt.c +++ b/fs/cifs/cifsencrypt.c @@ -1,7 +1,7 @@ /* * fs/cifs/cifsencrypt.c * - * Copyright (C) International Business Machines Corp., 2005 + * Copyright (C) International Business Machines Corp., 2005,2006 * Author(s): Steve French (sfrench@us.ibm.com) * * This library is free software; you can redistribute it and/or modify @@ -36,7 +36,8 @@ extern void mdfour(unsigned char *out, unsigned char *in, int n); extern void E_md4hash(const unsigned char *passwd, unsigned char *p16); -static int cifs_calculate_signature(const struct smb_hdr * cifs_pdu, const char * key, char * signature) +static int cifs_calculate_signature(const struct smb_hdr * cifs_pdu, + const char * key, char * signature) { struct MD5Context context; @@ -259,4 +260,5 @@ void CalcNTLMv2_response(const struct cifsSesInfo * ses,char * v2_session_respon /* hmac_md5_update(v2_session_response+16)client thing,8,&context); */ /* BB fix */ hmac_md5_final(v2_session_response,&context); + cifs_dump_mem("v2_sess_rsp: ", v2_session_response, 32); /* BB removeme BB */ } diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 79eeccd0437..8219c02145a 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -93,13 +93,10 @@ cifs_read_super(struct super_block *sb, void *data, int rc = 0; sb->s_flags |= MS_NODIRATIME; /* and probably even noatime */ - sb->s_fs_info = kmalloc(sizeof(struct cifs_sb_info),GFP_KERNEL); + sb->s_fs_info = kzalloc(sizeof(struct cifs_sb_info),GFP_KERNEL); cifs_sb = CIFS_SB(sb); if(cifs_sb == NULL) return -ENOMEM; - else - memset(cifs_sb,0,sizeof(struct cifs_sb_info)); - rc = cifs_mount(sb, cifs_sb, data, devname); diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index 821a8eb2255..4cf10f23cda 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h @@ -99,5 +99,5 @@ extern ssize_t cifs_getxattr(struct dentry *, const char *, void *, size_t); extern ssize_t cifs_listxattr(struct dentry *, char *, size_t); extern int cifs_ioctl (struct inode * inode, struct file * filep, unsigned int command, unsigned long arg); -#define CIFS_VERSION "1.40" +#define CIFS_VERSION "1.41" #endif /* _CIFSFS_H */ diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 7bed27601ce..006eb33bff5 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1,7 +1,7 @@ /* * fs/cifs/cifsglob.h * - * Copyright (C) International Business Machines Corp., 2002,2005 + * Copyright (C) International Business Machines Corp., 2002,2006 * Author(s): Steve French (sfrench@us.ibm.com) * * This library is free software; you can redistribute it and/or modify @@ -430,6 +430,15 @@ struct dir_notify_req { #define CIFS_LARGE_BUFFER 2 #define CIFS_IOVEC 4 /* array of response buffers */ +/* Type of session setup needed */ +#define CIFS_PLAINTEXT 0 +#define CIFS_LANMAN 1 +#define CIFS_NTLM 2 +#define CIFS_NTLMSSP_NEG 3 +#define CIFS_NTLMSSP_AUTH 4 +#define CIFS_SPNEGO_INIT 5 +#define CIFS_SPNEGO_TARG 6 + /* ***************************************************************** * All constants go here diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 3c03aadaff0..79e7f5a5432 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -1,7 +1,7 @@ /* * fs/cifs/cifsproto.h * - * Copyright (c) International Business Machines Corp., 2002,2005 + * Copyright (c) International Business Machines Corp., 2002,2006 * Author(s): Steve French (sfrench@us.ibm.com) * * This library is free software; you can redistribute it and/or modify @@ -64,6 +64,14 @@ extern int map_smb_to_linux_error(struct smb_hdr *smb); extern void header_assemble(struct smb_hdr *, char /* command */ , const struct cifsTconInfo *, int /* length of fixed section (word count) in two byte units */); +#ifdef CONFIG_CIFS_EXPERIMENTAL +extern int small_smb_init_no_tc(const int smb_cmd, const int wct, + struct cifsSesInfo *ses, + void ** request_buf); +extern int CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses, + const int stage, int * pNTLMv2_flg, + const struct nls_table *nls_cp); +#endif extern __u16 GetNextMid(struct TCP_Server_Info *server); extern struct oplock_q_entry * AllocOplockQEntry(struct inode *, u16, struct cifsTconInfo *); diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c index b41e8b37965..9d7bbd225ef 100644 --- a/fs/cifs/cifssmb.c +++ b/fs/cifs/cifssmb.c @@ -1,7 +1,7 @@ /* * fs/cifs/cifssmb.c * - * Copyright (C) International Business Machines Corp., 2002,2005 + * Copyright (C) International Business Machines Corp., 2002,2006 * Author(s): Steve French (sfrench@us.ibm.com) * * Contains the routines for constructing the SMB PDUs themselves @@ -186,7 +186,35 @@ small_smb_init(int smb_command, int wct, struct cifsTconInfo *tcon, cifs_stats_inc(&tcon->num_smbs_sent); return rc; -} +} + +#ifdef CONFIG_CIFS_EXPERIMENTAL +int +small_smb_init_no_tc(const int smb_command, const int wct, + struct cifsSesInfo *ses, void **request_buf) +{ + int rc; + struct smb_hdr * buffer; + + rc = small_smb_init(smb_command, wct, NULL, request_buf); + if(rc) + return rc; + + buffer = (struct smb_hdr *)*request_buf; + buffer->Mid = GetNextMid(ses->server); + if (ses->capabilities & CAP_UNICODE) + buffer->Flags2 |= SMBFLG2_UNICODE; + if (ses->capabilities & CAP_STATUS32) + buffer->Flags2 |= SMBFLG2_ERR_STATUS; + + /* uid, tid can stay at zero as set in header assemble */ + + /* BB add support for turning on the signing when + this function is used after 1st of session setup requests */ + + return rc; +} +#endif /* CONFIG_CIFS_EXPERIMENTAL */ /* If the return code is zero, this function must fill in request_buf pointer */ static int diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index ef5ae6f93c7..16535b510a9 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -1,7 +1,7 @@ /* * fs/cifs/connect.c * - * Copyright (C) International Business Machines Corp., 2002,2005 + * Copyright (C) International Business Machines Corp., 2002,2006 * Author(s): Steve French (sfrench@us.ibm.com) * * This library is free software; you can redistribute it and/or modify @@ -2525,7 +2525,7 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid, __u32 negotiate_flags, capabilities; __u16 count; - cFYI(1, ("In NTLMSSP sesssetup (negotiate) ")); + cFYI(1, ("In NTLMSSP sesssetup (negotiate)")); if(ses == NULL) return -EINVAL; domain = ses->domainName; @@ -2575,7 +2575,8 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid, SecurityBlob->MessageType = NtLmNegotiate; negotiate_flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_OEM | - NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_NTLM | 0x80000000 | + NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_NEGOTIATE_56 | /* NTLMSSP_NEGOTIATE_ALWAYS_SIGN | */ NTLMSSP_NEGOTIATE_128; if(sign_CIFS_PDUs) negotiate_flags |= NTLMSSP_NEGOTIATE_SIGN; @@ -2588,26 +2589,11 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid, SecurityBlob->WorkstationName.Length = 0; SecurityBlob->WorkstationName.MaximumLength = 0; - if (domain == NULL) { - SecurityBlob->DomainName.Buffer = 0; - SecurityBlob->DomainName.Length = 0; - SecurityBlob->DomainName.MaximumLength = 0; - } else { - __u16 len; - negotiate_flags |= NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED; - strncpy(bcc_ptr, domain, 63); - len = strnlen(domain, 64); - SecurityBlob->DomainName.MaximumLength = - cpu_to_le16(len); - SecurityBlob->DomainName.Buffer = - cpu_to_le32((long) &SecurityBlob-> - DomainString - - (long) &SecurityBlob->Signature); - bcc_ptr += len; - SecurityBlobLength += len; - SecurityBlob->DomainName.Length = - cpu_to_le16(len); - } + /* Domain not sent on first Sesssetup in NTLMSSP, instead it is sent + along with username on auth request (ie the response to challenge) */ + SecurityBlob->DomainName.Buffer = 0; + SecurityBlob->DomainName.Length = 0; + SecurityBlob->DomainName.MaximumLength = 0; if (ses->capabilities & CAP_UNICODE) { if ((long) bcc_ptr % 2) { *bcc_ptr = 0; @@ -2677,7 +2663,7 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid, SecurityBlob2->MessageType)); } else if (ses) { ses->Suid = smb_buffer_response->Uid; /* UID left in le format */ - cFYI(1, ("UID = %d ", ses->Suid)); + cFYI(1, ("UID = %d", ses->Suid)); if ((pSMBr->resp.hdr.WordCount == 3) || ((pSMBr->resp.hdr.WordCount == 4) && (blob_len < @@ -2685,17 +2671,17 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid, if (pSMBr->resp.hdr.WordCount == 4) { bcc_ptr += blob_len; - cFYI(1, - ("Security Blob Length %d ", + cFYI(1, ("Security Blob Length %d", blob_len)); } - cFYI(1, ("NTLMSSP Challenge rcvd ")); + cFYI(1, ("NTLMSSP Challenge rcvd")); memcpy(ses->server->cryptKey, SecurityBlob2->Challenge, CIFS_CRYPTO_KEY_SIZE); - if(SecurityBlob2->NegotiateFlags & cpu_to_le32(NTLMSSP_NEGOTIATE_NTLMV2)) + if(SecurityBlob2->NegotiateFlags & + cpu_to_le32(NTLMSSP_NEGOTIATE_NTLMV2)) *pNTLMv2_flag = TRUE; if((SecurityBlob2->NegotiateFlags & @@ -2818,7 +2804,7 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid, bcc_ptr++; } else cFYI(1, - ("Variable field of length %d extends beyond end of smb ", + ("Variable field of length %d extends beyond end of smb", len)); } } else { @@ -2830,7 +2816,7 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid, } } else { cERROR(1, - (" Invalid Word count %d: ", + (" Invalid Word count %d:", smb_buffer_response->WordCount)); rc = -EIO; } @@ -3447,7 +3433,7 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *pSesInfo, if (extended_security && (pSesInfo->capabilities & CAP_EXTENDED_SECURITY) && (pSesInfo->server->secType == NTLMSSP)) { - cFYI(1, ("New style sesssetup ")); + cFYI(1, ("New style sesssetup")); rc = CIFSSpnegoSessSetup(xid, pSesInfo, NULL /* security blob */, 0 /* blob length */, @@ -3455,7 +3441,7 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *pSesInfo, } else if (extended_security && (pSesInfo->capabilities & CAP_EXTENDED_SECURITY) && (pSesInfo->server->secType == RawNTLMSSP)) { - cFYI(1, ("NTLMSSP sesssetup ")); + cFYI(1, ("NTLMSSP sesssetup")); rc = CIFSNTLMSSPNegotiateSessSetup(xid, pSesInfo, &ntlmv2_flag, diff --git a/fs/cifs/dir.c b/fs/cifs/dir.c index fed55e3c53d..77e1fc01d74 100644 --- a/fs/cifs/dir.c +++ b/fs/cifs/dir.c @@ -255,12 +255,10 @@ cifs_create(struct inode *inode, struct dentry *direntry, int mode, CIFSSMBClose(xid, pTcon, fileHandle); } else if(newinode) { pCifsFile = - kmalloc(sizeof (struct cifsFileInfo), GFP_KERNEL); + kzalloc(sizeof (struct cifsFileInfo), GFP_KERNEL); if(pCifsFile == NULL) goto cifs_create_out; - memset((char *)pCifsFile, 0, - sizeof (struct cifsFileInfo)); pCifsFile->netfid = fileHandle; pCifsFile->pid = current->tgid; pCifsFile->pInode = newinode; diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index 59359911f48..0fb42feff3c 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -609,9 +609,8 @@ int cifs_unlink(struct inode *inode, struct dentry *direntry) } } else if (rc == -EACCES) { /* try only if r/o attribute set in local lookup data? */ - pinfo_buf = kmalloc(sizeof(FILE_BASIC_INFO), GFP_KERNEL); + pinfo_buf = kzalloc(sizeof(FILE_BASIC_INFO), GFP_KERNEL); if (pinfo_buf) { - memset(pinfo_buf, 0, sizeof(FILE_BASIC_INFO)); /* ATTRS set to normal clears r/o bit */ pinfo_buf->Attributes = cpu_to_le32(ATTR_NORMAL); if (!(pTcon->ses->flags & CIFS_SES_NT4)) diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index 812c6bb0fe3..0f3ebad09d3 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -72,10 +72,9 @@ sesInfoAlloc(void) struct cifsSesInfo *ret_buf; ret_buf = - (struct cifsSesInfo *) kmalloc(sizeof (struct cifsSesInfo), + (struct cifsSesInfo *) kzalloc(sizeof (struct cifsSesInfo), GFP_KERNEL); if (ret_buf) { - memset(ret_buf, 0, sizeof (struct cifsSesInfo)); write_lock(&GlobalSMBSeslock); atomic_inc(&sesInfoAllocCount); ret_buf->status = CifsNew; @@ -110,10 +109,9 @@ tconInfoAlloc(void) { struct cifsTconInfo *ret_buf; ret_buf = - (struct cifsTconInfo *) kmalloc(sizeof (struct cifsTconInfo), + (struct cifsTconInfo *) kzalloc(sizeof (struct cifsTconInfo), GFP_KERNEL); if (ret_buf) { - memset(ret_buf, 0, sizeof (struct cifsTconInfo)); write_lock(&GlobalSMBSeslock); atomic_inc(&tconInfoAllocCount); list_add(&ret_buf->cifsConnectionList, diff --git a/fs/cifs/ntlmssp.c b/fs/cifs/ntlmssp.c new file mode 100644 index 00000000000..4aabe2d7cb7 --- /dev/null +++ b/fs/cifs/ntlmssp.c @@ -0,0 +1,130 @@ +/* + * fs/cifs/ntlmssp.h + * + * Copyright (c) International Business Machines Corp., 2006 + * Author(s): Steve French (sfrench@us.ibm.com) + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "cifspdu.h" +#include "cifsglob.h" +#include "cifsproto.h" +#include "cifs_unicode.h" +#include "cifs_debug.h" +#include "ntlmssp.h" +#include "nterr.h" + +#ifdef CONFIG_CIFS_EXPERIMENTAL +static __u32 cifs_ssetup_hdr(struct cifsSesInfo *ses, SESSION_SETUP_ANDX *pSMB) +{ + __u32 capabilities = 0; + + /* init fields common to all four types of SessSetup */ + /* note that header is initialized to zero in header_assemble */ + pSMB->req.AndXCommand = 0xFF; + pSMB->req.MaxBufferSize = cpu_to_le16(ses->server->maxBuf); + pSMB->req.MaxMpxCount = cpu_to_le16(ses->server->maxReq); + + /* Now no need to set SMBFLG_CASELESS or obsolete CANONICAL PATH */ + + /* BB verify whether signing required on neg or just on auth frame + (and NTLM case) */ + + capabilities = CAP_LARGE_FILES | CAP_NT_SMBS | CAP_LEVEL_II_OPLOCKS | + CAP_LARGE_WRITE_X | CAP_LARGE_READ_X; + + if(ses->server->secMode & (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED)) + pSMB->req.hdr.Flags2 |= SMBFLG2_SECURITY_SIGNATURE; + + if (ses->capabilities & CAP_UNICODE) { + pSMB->req.hdr.Flags2 |= SMBFLG2_UNICODE; + capabilities |= CAP_UNICODE; + } + if (ses->capabilities & CAP_STATUS32) { + pSMB->req.hdr.Flags2 |= SMBFLG2_ERR_STATUS; + capabilities |= CAP_STATUS32; + } + if (ses->capabilities & CAP_DFS) { + pSMB->req.hdr.Flags2 |= SMBFLG2_DFS; + capabilities |= CAP_DFS; + } + + /* BB check whether to init vcnum BB */ + return capabilities; +} +int +CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses, const int type, + int * pNTLMv2_flg, const struct nls_table *nls_cp) +{ + int rc = 0; + int wct; + struct smb_hdr *smb_buffer; + char *bcc_ptr; + SESSION_SETUP_ANDX *pSMB; + __u32 capabilities; + + if(ses == NULL) + return -EINVAL; + + cFYI(1,("SStp type: %d",type)); + if(type < CIFS_NTLM) { +#ifndef CONFIG_CIFS_WEAK_PW_HASH + /* LANMAN and plaintext are less secure and off by default. + So we make this explicitly be turned on in kconfig (in the + build) and turned on at runtime (changed from the default) + in proc/fs/cifs or via mount parm. Unfortunately this is + needed for old Win (e.g. Win95), some obscure NAS and OS/2 */ + return -EOPNOTSUPP; +#endif + wct = 10; /* lanman 2 style sessionsetup */ + } else if(type < CIFS_NTLMSSP_NEG) + wct = 13; /* old style NTLM sessionsetup */ + else /* same size for negotiate or auth, NTLMSSP or extended security */ + wct = 12; + + rc = small_smb_init_no_tc(SMB_COM_SESSION_SETUP_ANDX, wct, ses, + (void **)&smb_buffer); + if(rc) + return rc; + + pSMB = (SESSION_SETUP_ANDX *)smb_buffer; + + capabilities = cifs_ssetup_hdr(ses, pSMB); + bcc_ptr = pByteArea(smb_buffer); + if(type > CIFS_NTLM) { + pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC; + capabilities |= CAP_EXTENDED_SECURITY; + pSMB->req.Capabilities = cpu_to_le32(capabilities); + /* BB set password lengths */ + } else if(type < CIFS_NTLM) /* lanman */ { + /* no capabilities flags in old lanman negotiation */ + /* pSMB->old_req.PasswordLength = */ /* BB fixme BB */ + } else /* type CIFS_NTLM */ { + pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities); + pSMB->req_no_secext.CaseInsensitivePasswordLength = + cpu_to_le16(CIFS_SESSION_KEY_SIZE); + pSMB->req_no_secext.CaseSensitivePasswordLength = + cpu_to_le16(CIFS_SESSION_KEY_SIZE); + } + + +/* rc = SendReceive2(xid, ses, iov, num_iovecs, &resp_buf_type, 0); */ + + cifs_small_buf_release(smb_buffer); + + return rc; +} +#endif /* CONFIG_CIFS_EXPERIMENTAL */ diff --git a/fs/cifs/ntlmssp.h b/fs/cifs/ntlmssp.h index 803389b64a2..d39b712a11c 100644 --- a/fs/cifs/ntlmssp.h +++ b/fs/cifs/ntlmssp.h @@ -1,7 +1,7 @@ /* * fs/cifs/ntlmssp.h * - * Copyright (c) International Business Machines Corp., 2002 + * Copyright (c) International Business Machines Corp., 2002,2006 * Author(s): Steve French (sfrench@us.ibm.com) * * This library is free software; you can redistribute it and/or modify |