package org.dcache.gplazma.plugins; import com.google.common.base.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchResult; import java.security.Principal; import java.util.HashSet; import java.util.Properties; import java.util.Set; import org.dcache.auth.GidPrincipal; import org.dcache.auth.GroupNamePrincipal; import org.dcache.auth.UidPrincipal; import org.dcache.auth.UserNamePrincipal; import org.dcache.auth.attributes.HomeDirectory; import org.dcache.auth.attributes.RootDirectory; import org.dcache.gplazma.AuthenticationException; import org.dcache.gplazma.NoSuchPrincipalException; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Predicates.instanceOf; import static com.google.common.collect.Iterables.find; import static org.dcache.gplazma.util.Preconditions.checkAuthentication; /** * gPlazma plug-in which uses NIS (YP) server to provide requested information. * * Can be combined with other map/auth plugins: * <pre> * auth optional x509 * auth optional voms * map optional gridmap * map optional krb5 * map requisite nis * identity requisite nis * session requisite nis * </pre> * * @since 2.1 */ public class Nis implements GPlazmaIdentityPlugin, GPlazmaSessionPlugin, GPlazmaMappingPlugin{ private static final Logger _log = LoggerFactory.getLogger(Nis.class); /* * Attibute names used by NIS. * See: http://www.ietf.org/rfc/rfc2307.txt */ private static final String GID_NUMBER_ATTRIBUTE = "gidNumber"; private static final String HOME_DIR_ATTRIBUTE = "homeDirectory"; private static final String UID_NUMBER_ATTRIBUTE = "uidNumber"; private static final String COMMON_NAME_ATTRIBUTE = "cn"; private static final String USER_ID_ATTRIBUTE = "uid"; private static final String MEMBER_UID_ATTRIBUTE = "memberUid"; /* * NIS map file names */ private static final String NISMAP_GROUP_BY_GID = "system/group.bygid"; private static final String NISMAP_PASSWORD_BY_NAME = "system/passwd.byname"; private static final String NISMAP_GROUP_BY_NAME = "system/group.byname"; private static final String NISMAP_PASSWORD_BY_UID = "system/passwd.byuid"; /* * dcache config variables */ private static final String SERVER = "gplazma.nis.server"; private static final String DOMAIN = "gplazma.nis.domain"; private final InitialDirContext _ctx; /** * Create a NIS identity plugin. * @throws NamingException */ public Nis(Properties properties) throws NamingException { String server = properties.getProperty(SERVER); String domain = properties.getProperty(DOMAIN); checkArgument(!Strings.isNullOrEmpty(server), SERVER + " must be specified."); checkArgument(!Strings.isNullOrEmpty(domain), DOMAIN + " must be specified."); Properties env = new Properties(); env.put(DirContext.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.nis.NISCtxFactory"); env.put(DirContext.PROVIDER_URL, String.format("nis://%s/%s", server, domain)); _ctx = new InitialDirContext(env); } @Override public void map(Set<Principal> principals) throws AuthenticationException { boolean mapped; Principal principal = find(principals, instanceOf(UserNamePrincipal.class), null); checkAuthentication(principal != null, "no username principal"); try { Attributes userAttr = _ctx.getAttributes(NISMAP_PASSWORD_BY_NAME + "/" + principal.getName()); principals.add(new UidPrincipal((String) userAttr.get(UID_NUMBER_ATTRIBUTE).get())); principals.add(new GidPrincipal((String) userAttr.get(GID_NUMBER_ATTRIBUTE).get(), true)); NamingEnumeration<SearchResult> groupResult = _ctx.search(NISMAP_GROUP_BY_NAME, new BasicAttributes(MEMBER_UID_ATTRIBUTE, principal.getName())); mapped = true; while (groupResult.hasMore()) { SearchResult result = groupResult.next(); principals.add( new GidPrincipal((String) result.getAttributes().get(GID_NUMBER_ATTRIBUTE).get(), false)); } } catch (NamingException e) { _log.debug("Failed to get mapping: {}", e.toString()); throw new AuthenticationException("no mapping: " + e.getMessage(), e); } checkAuthentication(mapped, "no matching principal"); } @Override public Principal map(Principal principal) throws NoSuchPrincipalException { String name = principal.getName(); try { if (principal instanceof UserNamePrincipal) { Attributes userAttr = _ctx.getAttributes(NISMAP_PASSWORD_BY_NAME + "/" + name); return new UidPrincipal((String) userAttr.get(UID_NUMBER_ATTRIBUTE).get()); } else if (principal instanceof GroupNamePrincipal) { Attributes groupAttr = _ctx.getAttributes(NISMAP_GROUP_BY_NAME + "/" + name); return new GidPrincipal((String) groupAttr.get(GID_NUMBER_ATTRIBUTE).get(), false); } } catch (NamingException e) { _log.debug("Failed to get mapping: {}", e.toString()); } throw new NoSuchPrincipalException(principal); } @Override public Set<Principal> reverseMap(Principal principal) throws NoSuchPrincipalException { String id = principal.getName(); try { Set<Principal> principals = new HashSet<>(); if (principal instanceof GidPrincipal) { NamingEnumeration<SearchResult> ne = _ctx.search(NISMAP_GROUP_BY_GID, new BasicAttributes(GID_NUMBER_ATTRIBUTE, id)); while (ne.hasMore()) { SearchResult result = ne.next(); String name = (String) result.getAttributes().get(COMMON_NAME_ATTRIBUTE).get(); principals.add(new GroupNamePrincipal(name)); } } else if (principal instanceof UidPrincipal) { NamingEnumeration<SearchResult> ne = _ctx.search(NISMAP_PASSWORD_BY_UID, new BasicAttributes(UID_NUMBER_ATTRIBUTE, id)); while (ne.hasMore()) { SearchResult result = ne.next(); String name = (String) result.getAttributes().get(USER_ID_ATTRIBUTE).get(); principals.add(new UserNamePrincipal(name)); } } return principals; } catch (NamingException e) { _log.debug("Failed to get reverse mapping: {}", e.toString()); } throw new NoSuchPrincipalException(principal); } @Override public void session(Set<Principal> authorizedPrincipals, Set<Object> attrib) throws AuthenticationException { Principal principal = find(authorizedPrincipals, instanceOf(UserNamePrincipal.class), null); checkAuthentication(principal != null, "no username principal"); try { Attributes userAttr = _ctx.getAttributes(NISMAP_PASSWORD_BY_NAME + "/" + principal.getName()); attrib.add(new HomeDirectory((String) userAttr.get(HOME_DIR_ATTRIBUTE).get())); attrib.add(new RootDirectory("/")); } catch (NamingException e) { throw new AuthenticationException("no mapping: " + e.getMessage(), e); } } }