Date: Thu, 14 May 2009 21:39:08 +0000 (UTC) From: Rick Macklem <rmacklem@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r192121 - in head/sys/fs: nfs nfsclient nfsserver Message-ID: <200905142139.n4ELd8Zw003155@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: rmacklem Date: Thu May 14 21:39:08 2009 New Revision: 192121 URL: http://svn.freebsd.org/changeset/base/192121 Log: Apply changes to the experimental nfs server so that it uses the security flavors as exported in FreeBSD-CURRENT. This allows it to use a slightly modified mountd.c instead of a different utility. Approved by: kib (mentor) Modified: head/sys/fs/nfs/nfs.h head/sys/fs/nfs/nfs_var.h head/sys/fs/nfs/nfsdport.h head/sys/fs/nfs/nfsport.h head/sys/fs/nfsclient/nfs_clkrpc.c head/sys/fs/nfsserver/nfs_nfsdkrpc.c head/sys/fs/nfsserver/nfs_nfsdport.c head/sys/fs/nfsserver/nfs_nfsdserv.c head/sys/fs/nfsserver/nfs_nfsdsocket.c head/sys/fs/nfsserver/nfs_nfsdsubs.c Modified: head/sys/fs/nfs/nfs.h ============================================================================== --- head/sys/fs/nfs/nfs.h Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfs/nfs.h Thu May 14 21:39:08 2009 (r192121) @@ -580,7 +580,6 @@ struct nfsrv_descript { u_int64_t nd_compref; /* Compound RPC ref# */ time_t nd_tcpconntime; /* Time TCP connection est. */ nfsquad_t nd_clientid; /* Implied clientid */ - int nd_credflavor; /* credential flavor */ int nd_gssnamelen; /* principal name length */ char *nd_gssname; /* principal name */ }; @@ -608,8 +607,11 @@ struct nfsrv_descript { #define ND_V4WCCATTR 0x00010000 #define ND_NFSCB 0x00020000 #define ND_AUTHNONE 0x00040000 -#define ND_EXGSSONLY 0x00080000 -#define ND_INCRSEQID 0x00100000 +#define ND_EXAUTHSYS 0x00080000 +#define ND_EXGSS 0x00100000 +#define ND_EXGSSINTEGRITY 0x00200000 +#define ND_EXGSSPRIVACY 0x00400000 +#define ND_INCRSEQID 0x00800000 /* * ND_GSS should be the "or" of all GSS type authentications. @@ -631,11 +633,6 @@ struct nfsv4_opflag { #define NFSRVSEQID_OPEN 0x04 /* - * MNT_EXGSSONLY is the Or of all the EXGSS bits. - */ -#define MNT_EXGSSONLY MNT_EXGSSKRB5 - -/* * assign a doubly linked list to a new head * and prepend one list into another. */ Modified: head/sys/fs/nfs/nfs_var.h ============================================================================== --- head/sys/fs/nfs/nfs_var.h Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfs/nfs_var.h Thu May 14 21:39:08 2009 (r192121) @@ -306,6 +306,7 @@ int nfsrv_putreferralattr(struct nfsrv_d int nfsrv_parsename(struct nfsrv_descript *, char *, u_long *, NFSPATHLEN_T *); void nfsd_init(void); +int nfsd_checkrootexp(struct nfsrv_descript *); /* nfs_clvfsops.c */ @@ -575,6 +576,7 @@ int nfsvno_advlock(vnode_t, int, u_int64 void nfsvno_unlockvfs(mount_t); int nfsvno_lockvfs(mount_t); int nfsrv_v4rootexport(void *, struct ucred *, NFSPROC_T *); +int nfsvno_testexp(struct nfsrv_descript *, struct nfsexstuff *); /* nfs_commonkrpc.c */ int newnfs_nmcancelreqs(struct nfsmount *); Modified: head/sys/fs/nfs/nfsdport.h ============================================================================== --- head/sys/fs/nfs/nfsdport.h Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfs/nfsdport.h Thu May 14 21:39:08 2009 (r192121) @@ -52,8 +52,10 @@ * needs to be returned by nfsd_fhtovp(). */ struct nfsexstuff { - int nes_vfslocked; /* required for all ports */ - int nes_exflag; + int nes_vfslocked; /* required for all ports */ + int nes_exflag; /* export flags */ + int nes_numsecflavor; /* # of security flavors */ + int nes_secflavors[MAXSECFLAVORS]; /* and the flavors */ }; #define NFSVNO_EXINIT(e) ((e)->nes_exflag = 0) @@ -61,11 +63,9 @@ struct nfsexstuff { #define NFSVNO_EXRDONLY(e) ((e)->nes_exflag & MNT_EXRDONLY) #define NFSVNO_EXPORTANON(e) ((e)->nes_exflag & MNT_EXPORTANON) #define NFSVNO_EXSTRICTACCESS(e) ((e)->nes_exflag & MNT_EXSTRICTACCESS) -#define NFSVNO_EXGSSONLY(e) ((e)->nes_exflag & MNT_EXGSSONLY) #define NFSVNO_EXV4ONLY(e) ((e)->nes_exflag & MNT_EXV4ONLY) #define NFSVNO_SETEXRDONLY(e) ((e)->nes_exflag = (MNT_EXPORTED|MNT_EXRDONLY)) -#define NFSVNO_SETEXGSSONLY(e) ((e)->nes_exflag |= MNT_EXGSSONLY) #define NFSVNO_CMPFH(f1, f2) \ ((f1)->fh_fsid.val[0] == (f2)->fh_fsid.val[0] && \ Modified: head/sys/fs/nfs/nfsport.h ============================================================================== --- head/sys/fs/nfs/nfsport.h Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfs/nfsport.h Thu May 14 21:39:08 2009 (r192121) @@ -645,11 +645,6 @@ struct nfsex_args { }; /* - * Define these here, so they don't have to be in mount.h, for now. - */ -#define MNT_EXGSSKRB5 MNT_EXKERB - -/* * These export flags should be defined, but there are no bits left. * Maybe a separate mnt_exflag field could be added or the mnt_flag * field increased to 64 bits? Modified: head/sys/fs/nfsclient/nfs_clkrpc.c ============================================================================== --- head/sys/fs/nfsclient/nfs_clkrpc.c Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfsclient/nfs_clkrpc.c Thu May 14 21:39:08 2009 (r192121) @@ -66,7 +66,7 @@ static void nfscb_program(struct svc_req *rqst, SVCXPRT *xprt) { struct nfsrv_descript nd; - int cacherep; + int cacherep, credflavor; memset(&nd, 0, sizeof(nd)); if (rqst->rq_proc != NFSPROC_NULL && @@ -94,12 +94,14 @@ nfscb_program(struct svc_req *rqst, SVCX nd.nd_cred = NULL; if (nd.nd_procnum != NFSPROC_NULL) { - if (!svc_getcred(rqst, &nd.nd_cred, &nd.nd_credflavor)) { + if (!svc_getcred(rqst, &nd.nd_cred, &credflavor)) { svcerr_weakauth(rqst); svc_freereq(rqst); m_freem(nd.nd_mrep); return; } + + /* For now, I don't care what credential flavor was used. */ #ifdef notyet #ifdef MAC mac_cred_associate_nfsd(nd.nd_cred); Modified: head/sys/fs/nfsserver/nfs_nfsdkrpc.c ============================================================================== --- head/sys/fs/nfsserver/nfs_nfsdkrpc.c Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfsserver/nfs_nfsdkrpc.c Thu May 14 21:39:08 2009 (r192121) @@ -97,7 +97,7 @@ nfssvc_program(struct svc_req *rqst, SVC { struct nfsrv_descript nd; struct nfsrvcache *rp = NULL; - int cacherep; + int cacherep, credflavor; memset(&nd, 0, sizeof(nd)); if (rqst->rq_vers == NFS_VER2) { @@ -186,12 +186,27 @@ nfssvc_program(struct svc_req *rqst, SVC } if (nd.nd_procnum != NFSPROC_NULL) { - if (!svc_getcred(rqst, &nd.nd_cred, &nd.nd_credflavor)) { + if (!svc_getcred(rqst, &nd.nd_cred, &credflavor)) { svcerr_weakauth(rqst); svc_freereq(rqst); m_freem(nd.nd_mrep); return; } + + /* Set the flag based on credflavor */ + if (credflavor == RPCSEC_GSS_KRB5) { + nd.nd_flag |= ND_GSS; + } else if (credflavor == RPCSEC_GSS_KRB5I) { + nd.nd_flag |= (ND_GSS | ND_GSSINTEGRITY); + } else if (credflavor == RPCSEC_GSS_KRB5P) { + nd.nd_flag |= (ND_GSS | ND_GSSPRIVACY); + } else if (credflavor != AUTH_SYS) { + svcerr_weakauth(rqst); + svc_freereq(rqst); + m_freem(nd.nd_mrep); + return; + } + #ifdef MAC mac_cred_associate_nfsd(nd.nd_cred); #endif Modified: head/sys/fs/nfsserver/nfs_nfsdport.c ============================================================================== --- head/sys/fs/nfsserver/nfs_nfsdport.c Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfsserver/nfs_nfsdport.c Thu May 14 21:39:08 2009 (r192121) @@ -2352,14 +2352,16 @@ nfsd_excred(struct nfsrv_descript *nd, s * Check/setup credentials. */ if (nd->nd_flag & ND_GSS) - exp->nes_exflag &= ~(MNT_EXGSSONLY | MNT_EXPORTANON); + exp->nes_exflag &= ~MNT_EXPORTANON; /* - * For AUTH_SYS, check to see if it is allowed. + * Check to see if the operation is allowed for this security flavor. * RFC2623 suggests that the NFSv3 Fsinfo RPC be allowed to * AUTH_NONE or AUTH_SYS for file systems requiring RPCSEC_GSS. + * Also, allow Secinfo, so that it can acquire the correct flavor(s). */ - if (NFSVNO_EXGSSONLY(exp) && + if (nfsvno_testexp(nd, exp) && + nd->nd_procnum != NFSV4OP_SECINFO && nd->nd_procnum != NFSPROC_FSINFO) { if (nd->nd_flag & ND_NFSV4) error = NFSERR_WRONGSEC; @@ -2400,14 +2402,20 @@ int nfsvno_checkexp(struct mount *mp, struct sockaddr *nam, struct nfsexstuff *exp, struct ucred **credp) { - int error; - int numsecflavor, *secflavors; + int i, error, *secflavors; error = VFS_CHECKEXP(mp, nam, &exp->nes_exflag, credp, - &numsecflavor, &secflavors); - if (error && nfs_rootfhset) { - exp->nes_exflag = 0; - error = 0; + &exp->nes_numsecflavor, &secflavors); + if (error) { + if (nfs_rootfhset) { + exp->nes_exflag = 0; + exp->nes_numsecflavor = 0; + error = 0; + } + } else { + /* Copy the security flavors. */ + for (i = 0; i < exp->nes_numsecflavor; i++) + exp->nes_secflavors[i] = secflavors[i]; } return (error); } @@ -2419,21 +2427,26 @@ int nfsvno_fhtovp(struct mount *mp, fhandle_t *fhp, struct sockaddr *nam, struct vnode **vpp, struct nfsexstuff *exp, struct ucred **credp) { - int error; - int numsecflavor, *secflavors; + int i, error, *secflavors; *credp = NULL; + exp->nes_numsecflavor = 0; error = VFS_FHTOVP(mp, &fhp->fh_fid, vpp); if (nam && !error) { error = VFS_CHECKEXP(mp, nam, &exp->nes_exflag, credp, - &numsecflavor, &secflavors); + &exp->nes_numsecflavor, &secflavors); if (error) { if (nfs_rootfhset) { exp->nes_exflag = 0; + exp->nes_numsecflavor = 0; error = 0; } else { vput(*vpp); } + } else { + /* Copy the security flavors. */ + for (i = 0; i < exp->nes_numsecflavor; i++) + exp->nes_secflavors[i] = secflavors[i]; } } return (error); @@ -2539,18 +2552,19 @@ nfsd_fhtovp(struct nfsrv_descript *nd, s */ #ifdef NFS_REQRSVPORT if (!nd->nd_repstat) { - struct sockaddr_in *saddr; - struct sockaddr_in6 *saddr6; - saddr = NFSSOCKADDR(nd->nd_nam, struct sockaddr_in *); - saddr6 = NFSSOCKADDR(nd->nd_nam, struct sockaddr_in6 *); - if (!(nd->nd_flag & ND_NFSV4) && - ((saddr->sin_family == AF_INET && - ntohs(saddr->sin_port) >= IPPORT_RESERVED) || - (saddr6->sin6_family == AF_INET6 && - ntohs(saddr6->sin6_port) >= IPPORT_RESERVED))) { - vput(*vpp); - nd->nd_repstat = (NFSERR_AUTHERR | AUTH_TOOWEAK); - } + struct sockaddr_in *saddr; + struct sockaddr_in6 *saddr6; + + saddr = NFSSOCKADDR(nd->nd_nam, struct sockaddr_in *); + saddr6 = NFSSOCKADDR(nd->nd_nam, struct sockaddr_in6 *); + if (!(nd->nd_flag & ND_NFSV4) && + ((saddr->sin_family == AF_INET && + ntohs(saddr->sin_port) >= IPPORT_RESERVED) || + (saddr6->sin6_family == AF_INET6 && + ntohs(saddr6->sin6_port) >= IPPORT_RESERVED))) { + vput(*vpp); + nd->nd_repstat = (NFSERR_AUTHERR | AUTH_TOOWEAK); + } } #endif /* NFS_REQRSVPORT */ @@ -2598,7 +2612,7 @@ fp_getfvp(struct thread *p, int fd, stru } /* - * Called from newnfssvc() to update the exports list. Just call + * Called from nfssvc() to update the exports list. Just call * vfs_export(). This has to be done, since the v4 root fake fs isn't * in the mount list. */ @@ -2610,11 +2624,6 @@ nfsrv_v4rootexport(void *argp, struct uc struct nameidata nd; fhandle_t fh; - /* - * Until newmountd is using the secflavor fields, just make - * sure it's 0. - */ - nfsexargp->export.ex_numsecflavors = 0; error = vfs_export(&nfsv4root_mnt, &nfsexargp->export); if ((nfsexargp->export.ex_flags & MNT_DELEXPORT)) { nfs_rootfhset = 0; @@ -2843,16 +2852,24 @@ int nfsvno_v4rootexport(struct nfsrv_descript *nd) { struct ucred *credanon; - int exflags, error; + int exflags, error, numsecflavor, *secflavors, i; error = vfs_stdcheckexp(&nfsv4root_mnt, nd->nd_nam, &exflags, - &credanon, NULL, NULL); + &credanon, &numsecflavor, &secflavors); if (error) return (NFSERR_PROGUNAVAIL); - if ((exflags & MNT_EXGSSONLY)) - nd->nd_flag |= ND_EXGSSONLY; if (credanon != NULL) crfree(credanon); + for (i = 0; i < numsecflavor; i++) { + if (secflavors[i] == AUTH_SYS) + nd->nd_flag |= ND_EXAUTHSYS; + else if (secflavors[i] == RPCSEC_GSS_KRB5) + nd->nd_flag |= ND_EXGSS; + else if (secflavors[i] == RPCSEC_GSS_KRB5I) + nd->nd_flag |= ND_EXGSSINTEGRITY; + else if (secflavors[i] == RPCSEC_GSS_KRB5P) + nd->nd_flag |= ND_EXGSSPRIVACY; + } return (0); } @@ -2985,6 +3002,45 @@ nfssvc_srvcall(struct thread *p, struct return (error); } +/* + * Check exports. + * Returns 0 if ok, 1 otherwise. + */ +int +nfsvno_testexp(struct nfsrv_descript *nd, struct nfsexstuff *exp) +{ + int i; + + /* + * This seems odd, but allow the case where the security flavor + * list is empty. This happens when NFSv4 is traversing non-exported + * file systems. Exported file systems should always have a non-empty + * security flavor list. + */ + if (exp->nes_numsecflavor == 0) + return (0); + + for (i = 0; i < exp->nes_numsecflavor; i++) { + /* + * The tests for privacy and integrity must be first, + * since ND_GSS is set for everything but AUTH_SYS. + */ + if (exp->nes_secflavors[i] == RPCSEC_GSS_KRB5P && + (nd->nd_flag & ND_GSSPRIVACY)) + return (0); + if (exp->nes_secflavors[i] == RPCSEC_GSS_KRB5I && + (nd->nd_flag & ND_GSSINTEGRITY)) + return (0); + if (exp->nes_secflavors[i] == RPCSEC_GSS_KRB5 && + (nd->nd_flag & ND_GSS)) + return (0); + if (exp->nes_secflavors[i] == AUTH_SYS && + (nd->nd_flag & ND_GSS) == 0) + return (0); + } + return (1); +} + extern int (*nfsd_call_nfsd)(struct thread *, struct nfssvc_args *); /* Modified: head/sys/fs/nfsserver/nfs_nfsdserv.c ============================================================================== --- head/sys/fs/nfsserver/nfs_nfsdserv.c Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfsserver/nfs_nfsdserv.c Thu May 14 21:39:08 2009 (r192121) @@ -3090,7 +3090,6 @@ nfsrvd_secinfo(struct nfsrv_descript *nd retnes.nes_vfslocked = exp->nes_vfslocked; vput(vp); savflag = nd->nd_flag; - nd->nd_flag |= ND_GSS; /* so nfsd_fhtovp() won't reply Wrongsec */ if (!nd->nd_repstat) { nfsd_fhtovp(nd, &fh, &vp, &retnes, &mp, 0, p); if (vp) @@ -3106,20 +3105,39 @@ nfsrvd_secinfo(struct nfsrv_descript *nd */ len = 0; NFSM_BUILD(sizp, u_int32_t *, NFSX_UNSIGNED); - if (!NFSVNO_EXGSSONLY(&retnes)) { - NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); - *tl = txdr_unsigned(RPCAUTH_UNIX); - len++; - } - for (i = RPCAUTHGSS_SVCNONE; i <= RPCAUTHGSS_SVCPRIVACY; i++) { - NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); - *tl++ = txdr_unsigned(RPCAUTH_GSS); - (void) nfsm_strtom(nd, nfsgss_mechlist[KERBV_MECH].str, - nfsgss_mechlist[KERBV_MECH].len); - NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED); - *tl++ = txdr_unsigned(GSS_KERBV_QOP); - *tl = txdr_unsigned(i); - len++; + for (i = 0; i < retnes.nes_numsecflavor; i++) { + if (retnes.nes_secflavors[i] == AUTH_SYS) { + NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); + *tl = txdr_unsigned(RPCAUTH_UNIX); + len++; + } else if (retnes.nes_secflavors[i] == RPCSEC_GSS_KRB5) { + NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); + *tl++ = txdr_unsigned(RPCAUTH_GSS); + (void) nfsm_strtom(nd, nfsgss_mechlist[KERBV_MECH].str, + nfsgss_mechlist[KERBV_MECH].len); + NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED); + *tl++ = txdr_unsigned(GSS_KERBV_QOP); + *tl = txdr_unsigned(RPCAUTHGSS_SVCNONE); + len++; + } else if (retnes.nes_secflavors[i] == RPCSEC_GSS_KRB5I) { + NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); + *tl++ = txdr_unsigned(RPCAUTH_GSS); + (void) nfsm_strtom(nd, nfsgss_mechlist[KERBV_MECH].str, + nfsgss_mechlist[KERBV_MECH].len); + NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED); + *tl++ = txdr_unsigned(GSS_KERBV_QOP); + *tl = txdr_unsigned(RPCAUTHGSS_SVCINTEGRITY); + len++; + } else if (retnes.nes_secflavors[i] == RPCSEC_GSS_KRB5P) { + NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); + *tl++ = txdr_unsigned(RPCAUTH_GSS); + (void) nfsm_strtom(nd, nfsgss_mechlist[KERBV_MECH].str, + nfsgss_mechlist[KERBV_MECH].len); + NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED); + *tl++ = txdr_unsigned(GSS_KERBV_QOP); + *tl = txdr_unsigned(RPCAUTHGSS_SVCPRIVACY); + len++; + } } *sizp = txdr_unsigned(len); return (0); @@ -3141,7 +3159,7 @@ nfsrvd_setclientid(struct nfsrv_descript nfsquad_t clientid, confirm; if ((!nfs_rootfhset && !nfsv4root_set) || - (nd->nd_flag & (ND_GSS | ND_EXGSSONLY)) == ND_EXGSSONLY) { + nfsd_checkrootexp(nd)) { nd->nd_repstat = NFSERR_WRONGSEC; return (0); } @@ -3250,7 +3268,7 @@ nfsrvd_setclientidcfrm(struct nfsrv_desc nfsquad_t clientid, confirm; if ((!nfs_rootfhset && !nfsv4root_set) || - (nd->nd_flag & (ND_GSS | ND_EXGSSONLY)) == ND_EXGSSONLY) { + nfsd_checkrootexp(nd)) { nd->nd_repstat = NFSERR_WRONGSEC; return (0); } Modified: head/sys/fs/nfsserver/nfs_nfsdsocket.c ============================================================================== --- head/sys/fs/nfsserver/nfs_nfsdsocket.c Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfsserver/nfs_nfsdsocket.c Thu May 14 21:39:08 2009 (r192121) @@ -819,8 +819,7 @@ nfsrvd_compound(struct nfsrv_descript *n op != NFSV4OP_GETFH && op != NFSV4OP_SECINFO) nd->nd_repstat = NFSERR_NOFILEHANDLE; - else if (NFSVNO_EXGSSONLY(&vpnes) && - !(nd->nd_flag & ND_GSS) && + else if (nfsvno_testexp(nd, &vpnes) && op != NFSV4OP_LOOKUP && op != NFSV4OP_GETFH && op != NFSV4OP_GETATTR && Modified: head/sys/fs/nfsserver/nfs_nfsdsubs.c ============================================================================== --- head/sys/fs/nfsserver/nfs_nfsdsubs.c Thu May 14 21:27:03 2009 (r192120) +++ head/sys/fs/nfsserver/nfs_nfsdsubs.c Thu May 14 21:39:08 2009 (r192121) @@ -2019,3 +2019,25 @@ nfsd_init(void) NFSBZERO(nfs_v2pubfh, NFSX_V2FH); } +/* + * Check the v4 root exports. + * Return 0 if ok, 1 otherwise. + */ +int +nfsd_checkrootexp(struct nfsrv_descript *nd) +{ + + if ((nd->nd_flag & (ND_GSS | ND_EXAUTHSYS)) == ND_EXAUTHSYS) + return (0); + if ((nd->nd_flag & (ND_GSSINTEGRITY | ND_EXGSSINTEGRITY)) == + (ND_GSSINTEGRITY | ND_EXGSSINTEGRITY)) + return (0); + if ((nd->nd_flag & (ND_GSSPRIVACY | ND_EXGSSPRIVACY)) == + (ND_GSSPRIVACY | ND_EXGSSPRIVACY)) + return (0); + if ((nd->nd_flag & (ND_GSS | ND_GSSINTEGRITY | ND_GSSPRIVACY | + ND_EXGSS)) == (ND_GSS | ND_EXGSS)) + return (0); + return (1); +} +
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200905142139.n4ELd8Zw003155>