/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== */ package org.identityconnectors.solaris.operation.search; import static org.identityconnectors.solaris.attr.NativeAttribute.COMMENT; import static org.identityconnectors.solaris.attr.NativeAttribute.DAYS_BEFORE_TO_WARN; import static org.identityconnectors.solaris.attr.NativeAttribute.DIR; import static org.identityconnectors.solaris.attr.NativeAttribute.GROUPS_SEC; import static org.identityconnectors.solaris.attr.NativeAttribute.GROUP_PRIM; import static org.identityconnectors.solaris.attr.NativeAttribute.ID; import static org.identityconnectors.solaris.attr.NativeAttribute.LOCK; import static org.identityconnectors.solaris.attr.NativeAttribute.MAX_DAYS_BETWEEN_CHNG; import static org.identityconnectors.solaris.attr.NativeAttribute.MIN_DAYS_BETWEEN_CHNG; import static org.identityconnectors.solaris.attr.NativeAttribute.NAME; import static org.identityconnectors.solaris.attr.NativeAttribute.PWSTAT; import static org.identityconnectors.solaris.attr.NativeAttribute.SHELL; import static org.identityconnectors.solaris.attr.NativeAttribute.USER_EXPIRE; import static org.identityconnectors.solaris.attr.NativeAttribute.USER_INACTIVE; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.identityconnectors.common.logging.Log; import org.identityconnectors.solaris.SolarisConnection; import org.identityconnectors.solaris.attr.NativeAttribute; public final class LoginsCommand { private static final Log logger = Log.getLog(LoginsCommand.class); private LoginsCommand() { } /** * a hard-coded set of constants used provided by Logins command. DO NOT * CHANGE */ private static final Set<NativeAttribute> ATTRIBUTE_SET; static { ATTRIBUTE_SET = EnumSet.of(COMMENT, DAYS_BEFORE_TO_WARN, DIR, GROUPS_SEC, GROUP_PRIM, LOCK, MAX_DAYS_BETWEEN_CHNG, MIN_DAYS_BETWEEN_CHNG, PWSTAT, SHELL, ID, USER_EXPIRE, USER_INACTIVE); /* * NativeAttribute.NAME is left out from the 'set' on purpose. The * reason is that the name is already known to the issuer of logins * command. */ } /** * get the attributes from Logins command for the given username. * * @param username * @param conn * @return the SolarisEntry initialized with fetched attributes or null if * the user was not found. */ public static SolarisEntry getAttributesFor(String username, SolarisConnection conn) { SolarisEntry entry = null; final String cmd = conn.buildCommand(true, "logins -oxma -l ", username); String out = conn.executeCommand(cmd); if (out.endsWith("was not found")) { return null; } entry = getEntry(out, username); return entry; } /* * IMPLEMENTATION NOTE: the logins command provides a fixed set of {@link * NativeAttribute}-s. If the implementation is changed, don't forget to * update the list of acquired attributes: {@link LoginsCmd#set}. */ public static SolarisEntry getEntry(String accountLine, String username) { final SolarisEntry.Builder bldr = new SolarisEntry.Builder(username); /* tokens delimited by ":" */ final String[] tokens = accountLine.split(":", -1); final Iterator<String> tokenIt = Arrays.asList(tokens).iterator(); /* * The logins result is colon delimited and looks like this: * * name:uid:group_1:groupnum_1:comment: [group_i:groupnum_i:]* * dir:shell:pwstat:pwlastchange: * mindaysbetweenchange:maxdaysbetweenchange:daysbeforetowarn * * the []* part can be repeated 0 or more times if the user has * additional groups. */ /* NAME */ final String foundUser = tokenIt.next(); if (!username.equals(foundUser)) { logger.warn("The fetched username differs from what was expected: fetched = '" + foundUser + "', expected = '" + username + "'."); return null; } bldr.addAttr(NAME, username); /* USER UID */ bldr.addAttr(ID, Integer.valueOf(tokenIt.next())); /* PRIMARY GROUP NAME */ bldr.addAttr(GROUP_PRIM, tokenIt.next()); /* PRIMARY GROUP GID - skip */ tokenIt.next(); bldr.addAttr(COMMENT, tokenIt.next()); /* SECONDARY GROUPS */ /** minimal number of tokens in the output of logins command */ final int minTokens = 14; final int totalTokens = tokens.length; if (totalTokens < minTokens) { throw new RuntimeException("Error: Missing tokens in output for user '" + username + "'" + ", accountLine: <" + accountLine + ">"); } final int numSecondaryGroups = (totalTokens - minTokens) / 2; final List<String> secondaryGroupNames = new ArrayList<String>(numSecondaryGroups); for (int i = 0; i < numSecondaryGroups; i++) { // store secondary group name secondaryGroupNames.add(tokenIt.next()); // ignore secondary group GID tokenIt.next(); } bldr.addAttr(GROUPS_SEC, secondaryGroupNames); bldr.addAttr(DIR, tokenIt.next()); bldr.addAttr(SHELL, tokenIt.next()); /* PWSTAT + PASSWD_LOCK */ final String pwstat = tokenIt.next(); boolean isPwStat = false; boolean isLock = false; if ("PS".equals(pwstat)) { isPwStat = true; } if ("LK".equals(pwstat)) { isLock = true; } bldr.addAttr(PWSTAT, isPwStat); bldr.addAttr(LOCK, isLock); /* PASSWD CHANGE - skip */ tokenIt.next(); bldr.addAttr(MIN_DAYS_BETWEEN_CHNG, Integer.valueOf(tokenIt.next())); bldr.addAttr(MAX_DAYS_BETWEEN_CHNG, Integer.valueOf(tokenIt.next())); bldr.addAttr(DAYS_BEFORE_TO_WARN, Integer.valueOf(tokenIt.next())); /* USER INACTIVE */ Integer userInactive = Integer.valueOf(tokenIt.next()); if (userInactive.equals(-1)) { // This is set to not expire and security modules may // not even be installed on the host so reset this to null. userInactive = null; } bldr.addAttr(USER_INACTIVE, userInactive); /* USER EXPIRE */ String userExpire = tokenIt.next(); if (userExpire.equals("0") || userExpire.equals("000000")) { // This is set to not expire and security modules may // not even be installed on the host so reset this to null. userExpire = null; } bldr.addAttr(USER_EXPIRE, userExpire); return bldr.build(); } /** * @param attr * the attribute in question. * @return true if the attribute is provided by {@link LoginsCommand}. */ private static boolean isProvided(NativeAttribute attr) { return ATTRIBUTE_SET.contains(attr); } /** * evaluate if logins command is required for retrieval of given attributes. * * @param attrs * attributes * @return true if {@link LoginsCommand} is required to be called, as it * yields at least one of attributes on the list. */ public static boolean isLoginsRequired(Set<NativeAttribute> attrs) { for (NativeAttribute nativeAttribute : attrs) { if (LoginsCommand.isProvided(nativeAttribute)) { return true; } } return false; } }