/* * ==================== * 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://IdentityConnectors.dev.java.net/legal/license.txt * 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 identityconnectors/legal/license.txt. * 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.racf; import static org.identityconnectors.racf.RacfConstants.*; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.exceptions.AlreadyExistsException; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.exceptions.UnknownUidException; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.AttributeUtil; import org.identityconnectors.framework.common.objects.ConnectorObject; import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.PredefinedAttributes; import org.identityconnectors.framework.common.objects.Uid; import org.identityconnectors.parser.factory.OutputParser; import org.identityconnectors.parser.factory.OutputParserFactory; import org.identityconnectors.rw3270.RW3270Connection; import expect4j.Closure; import expect4j.ExpectState; import expect4j.matches.Match; import expect4j.matches.RegExpMatch; import expect4j.matches.TimeoutMatch; class CommandLineUtil { private static final String DEFAULT_PARSER_FACTORY = "org.identityconnectors.parser.factory.patternparser.MapTransformParserFactory"; private static final String OUTPUT_COMPLETE_PATTERN = "\\sREADY\\s{74}"; private static final String OUTPUT_COMPLETE = " READY"; private static final String OUTPUT_CONTINUING_PATTERN = "\\s[*]{3}\\s{76}"; private static final String OUTPUT_CONTINUING = " ***"; private static final String RACF = "RACF"; private static final String CATALOG = "CATALOG"; private static final String DELETE_SEGMENT = "DELETE SEGMENT"; private static final String NO_ENTRIES = "NO ENTRIES MEET SEARCH CRITERIA"; private static final String NAME_NOT_FOUND = "NAME NOT FOUND"; private static final String UNABLE_TO_LOCATE_USER = "UNABLE TO LOCATE USER"; private Map<String, OutputParser> _segmentParsers; private RacfConnector _connector; private boolean _debug = false; public CommandLineUtil(RacfConnector connector) { try { _connector = connector; // Create a map of segment names to parsers // String[] segmentNames = ((RacfConfiguration)_connector.getConfiguration()).getSegmentNames(); String[] segmentParsers = ((RacfConfiguration)_connector.getConfiguration()).getSegmentParsers(); _segmentParsers = new HashMap<String, OutputParser>(); String factoryName = ((RacfConfiguration)connector.getConfiguration()).getParserFactory(); if (StringUtil.isEmpty(factoryName)) factoryName = DEFAULT_PARSER_FACTORY; OutputParserFactory factory = (OutputParserFactory)Class.forName(factoryName).newInstance(); if (segmentNames!=null) for (int i=0; i<segmentNames.length; i++) { String name = segmentNames[i]; _segmentParsers.put(name, factory.createOutputParser(segmentParsers[i])); } } catch (Exception e) { throw ConnectorException.wrap(e); } } private Uid createOrUpdateViaCommandLine(ObjectClass objectClass, String name, CharArrayBuffer command) { checkCommand(command); return new Uid(name); } private void checkCommand(String command) { CharArrayBuffer buffer = new CharArrayBuffer(); buffer.append(command); try { checkCommand(buffer); } finally { buffer.clear(); } } private void checkCommand(CharArrayBuffer command) { String output = getCommandOutput(command); if (output.trim().length()>0) { throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.ERROR_IN_COMMAND, new String(command.getArray()), output)); } } public String getCommandOutput(String command) { CharArrayBuffer buffer = new CharArrayBuffer(); buffer.append(command); try { return getCommandOutput(buffer); } finally { buffer.clear(); } } public String getCommandOutput(CharArrayBuffer buffer) { char[] command = buffer.getArray(); RW3270Connection connection = null; RacfConfiguration configuration = (RacfConfiguration)_connector.getConfiguration(); ConnectionPool connectionPool = null; try { connection = ConnectionPool.getConnectionFromPool(configuration); connectionPool = ConnectionPool.getConnectionPool(configuration); //TODO:remove next line //connection = connectionPool.getConnection(); connection.clearAndUnlock(); connection.resetStandardOutput(); System.out.println("execute:"+new String(command)); connection.send(command); connection.send("[enter]"); waitFor(connection, (configuration).getCommandTimeout()); String output = _buffer.toString(); // Strip command from start, if present // int index = indexOf(output,command); if (index>-1) { // Round up to line length // index += command.length; index += connection.getWidth(); index -= index%connection.getWidth(); output = output.substring(index); } // Remove OUTPUT_COMPLETE from end of output // output = output.substring(0, output.lastIndexOf(OUTPUT_COMPLETE)); output = output.replaceAll("(.{"+connection.getWidth()+"})", "$1\n"); //System.out.println("output:'"+output+"'"); connectionPool.returnConnection(connection); connection = null; return output; } catch (ErrorRecoveredException e) { // Connection can return to pool, since it is in a good state // connectionPool.returnConnection(connection); connection = null; throw ConnectorException.wrap(e); } catch (Exception e) { // Connection state may be unhealthy // if (connection!=null) connectionPool.returnBrokenConnection(connection); throw ConnectorException.wrap(e); } finally { Arrays.fill(command, 0, command.length, ' '); } } /** * See if a substring, defined by a character array, is contained in a string * @param string * @param substring * @return */ private int indexOf(String string, char[] substring) { for (int i=string.length()-substring.length-1; i>-1; i--) if (isAt(string, i, substring)) return i; return -1; } private boolean isAt(String string, int start, char[] substring) { for (int i=0; i<substring.length; i++) { if (string.charAt(start+i)!=substring[i]) return false; } return true; } /** * Given a set of Attributes, produce a partial TSO command line * to apply the values, including segment names. * * @param attrs * @return */ private char[] mapAttributesToString(Map<String, Attribute> attributes) { Name name = (Name)attributes.remove(Name.NAME); Uid uid = (Uid)attributes.remove(Uid.NAME); Attribute expired = attributes.remove(ATTR_CL_EXPIRED); Attribute enabled = attributes.remove(ATTR_CL_ENABLED); Attribute enableDate = attributes.remove(ATTR_CL_RESUME_DATE); Attribute disableDate = attributes.remove(ATTR_CL_REVOKE_DATE); Attribute attributesAttribute = attributes.remove(ATTR_CL_ATTRIBUTES); _connector.throwErrorIfNull(attributes, "NETVIEW*CTL"); _connector.throwErrorIfNull(attributes, "NETVIEW*MSGRECVR"); _connector.throwErrorIfNull(attributes, "NETVIEW*NGMFADMN"); _connector.throwErrorIfNull(attributes, "CICS*OPPRTY"); _connector.throwErrorIfNull(attributes, "CICS*TIMEOUT"); _connector.throwErrorIfNull(attributes, "CICS*XRFSOFF"); _connector.throwErrorIfNull(attributes, "TSO*SIZE"); _connector.throwErrorIfNull(attributes, "TSO*MAXSIZE"); _connector.throwErrorIfNull(attributes, "TSO*USERDATA"); _connector.throwErrorIfNullOrEmpty(name); _connector.throwErrorIfNullOrEmpty(uid); _connector.throwErrorIfNullOrEmpty(expired); _connector.throwErrorIfNullOrEmpty(enabled); _connector.throwErrorIfNullOrEmpty(enableDate); _connector.throwErrorIfNullOrEmpty(disableDate); _connector.throwErrorIfNullOrEmpty(attributesAttribute); // Build up a map containing the segment attribute values // Map<String, Map<String, char[]>> attributeValues = new HashMap<String, Map<String,char[]>>(); for (Map.Entry<String, Attribute> entry : attributes.entrySet()) { String[] attributeName = entry.getKey().split(RacfConnector.SEPARATOR_REGEX); if (!attributeValues.containsKey(attributeName[0])) { attributeValues.put(attributeName[0], new HashMap<String, char[]>()); } Map<String, char[]> map = attributeValues.get(attributeName[0]); map.put(attributeName[1], getAsStringValue(attributeName[0], attributeName[1], entry.getValue())); } // Build the attributes portion of the command // CharArrayBuffer commandAttributes = new CharArrayBuffer(); for (Map.Entry<String, Map<String, char[]>> segment : attributeValues.entrySet()) { if (segment.getValue().containsKey(DELETE_SEGMENT)) { commandAttributes.append(" NO"+segment.getKey()); } else { // We ignore CATALOG, since it isn't a real segment // if (!CATALOG.equalsIgnoreCase(segment.getKey())) { if (!RACF.equalsIgnoreCase(segment.getKey())) commandAttributes.append(" "+segment.getKey()+"("); for (Map.Entry<String, char[]> entry : segment.getValue().entrySet()) { char[] value = entry.getValue(); if (value==null) { commandAttributes.append(" NO"+entry.getKey()); } else { commandAttributes.append(" "+entry.getKey()+"("); commandAttributes.append(value); commandAttributes.append(")"); } } if (!RACF.equalsIgnoreCase(segment.getKey())) commandAttributes.append(")"); } } } // The various ATTRIBUTES are specified individually on the command line, // not as part of a larger value // TODO: docs list "UAUDIT", but our RACF doesn't support this List<String> possibleAttributes = new LinkedList<String>(RacfConnector.POSSIBLE_ATTRIBUTES); if (attributesAttribute!=null) { for (Object attributeValue : attributesAttribute.getValue()) { commandAttributes.append(" "+attributeValue); possibleAttributes.remove(attributeValue); } for (String attributeValue : possibleAttributes) { commandAttributes.append(" NO"+attributeValue); } } if (expired!=null) { if (AttributeUtil.getBooleanValue(expired)) commandAttributes.append(" EXPIRED"); else commandAttributes.append(" NOEXPIRED"); } if (enabled!=null) { if (AttributeUtil.getBooleanValue(enabled)) commandAttributes.append(" RESUME"); else commandAttributes.append(" REVOKE"); } if (enableDate!=null) { if (enableDate.getValue()==null) commandAttributes.append(" NORESUME"); else commandAttributes.append(" RESUME("+AttributeUtil.getStringValue(enableDate)+")"); } if (disableDate!=null) { if (disableDate.getValue()==null) commandAttributes.append(" NOREVOKE"); else commandAttributes.append(" REVOKE("+AttributeUtil.getStringValue(disableDate)+")"); } char[] result = commandAttributes.getArray(); commandAttributes.clear(); return result; } private char[] getAsStringValue(String segmentName, String attributeName, Attribute attribute) { if (_connector.isNullOrEmpty(attribute)) return null; List<Object> values = attribute.getValue(); if (values.size()==1 && values.get(0) instanceof GuardedString) { GuardedString currentGS = (GuardedString)values.get(0); GuardedStringAccessor accessor = new GuardedStringAccessor(); currentGS.access(accessor); char[] currentArray = accessor.getArray(); return currentArray; } else { if (values.size()==1 && values.get(0) instanceof Boolean) { if ((Boolean)values.get(0)) return "YES".toCharArray(); else return "NO".toCharArray(); } else { StringBuffer buffer = new StringBuffer(); for (Object value : values) { value = computeValue(value.toString(), segmentName, attributeName); buffer.append(" "+value); } return buffer.substring(1).toCharArray(); } } } private String computeValue(String value, String segmentName, String attributeName) { // DATA and IC must always be quoted, other values must be quoted // if they contain special characters // boolean quoteNeeded = "DATA".equalsIgnoreCase(attributeName) || "IC".equalsIgnoreCase(attributeName); if (!quoteNeeded) { for (char character : new char[] {'(', ')', ' ', ',', ';', '\''}) for (char valueChar : value.toCharArray()) quoteNeeded = quoteNeeded || character==valueChar; } if (quoteNeeded) { StringBuffer buffer = new StringBuffer(); buffer.append("'"); buffer.append(value.replaceAll("'", "''")); buffer.append("'"); return buffer.toString(); } else { return value; } } public Uid createViaCommandLine(ObjectClass objectClass, Set<Attribute> attrs, OperationOptions options) { Map<String, Attribute> attributes = new HashMap<String, Attribute>(AttributeUtil.toMap(attrs)); String name = ((Name)attributes.get(Name.NAME)).getNameValue(); RacfConfiguration configuration = (RacfConfiguration)_connector.getConfiguration(); if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { Attribute groups = attributes.remove(ATTR_CL_GROUPS); Attribute owners = attributes.remove(ATTR_CL_GROUP_CONN_OWNERS); Attribute expired = attributes.remove(ATTR_CL_EXPIRED); Attribute password = attributes.get(ATTR_CL_PASSWORD); _connector.throwErrorIfNull(groups); _connector.throwErrorIfNullOrEmpty(expired); _connector.throwErrorIfNullOrEmpty(password); _connector.checkConnectionConsistency(groups, owners); if (expired!=null && password==null) throw new ConnectorException((configuration).getMessage(RacfMessages.EXPIRED_NO_PASSWORD)); if (userExists(name)) throw new AlreadyExistsException(); validateCatalogAttributes(attributes); // Some attributes cannot be specified during create, only modify. // Save these off to the side. // Set<Attribute> changes = new HashSet<Attribute>(); Attribute enable = attributes.remove(ATTR_CL_ENABLED); Attribute enableDate = attributes.remove(ATTR_CL_RESUME_DATE); Attribute disableDate = attributes.remove(ATTR_CL_REVOKE_DATE); if (expired!=null) { changes.add(expired); changes.add(password); } if (enable!=null) changes.add(enable); if (enableDate!=null) changes.add(enableDate); if (disableDate!=null) changes.add(disableDate); // Create the user // CharArrayBuffer buffer = new CharArrayBuffer(); buffer.append("ADDUSER "); buffer.append(name); buffer.append(mapAttributesToString(attributes)); Uid uid = createOrUpdateViaCommandLine(objectClass, name, buffer); setCatalogAttributes(attributes); buffer.clear(); if (groups!=null) { List<Object> groupsValue = groups.getValue(); List<Object> ownersValue = owners==null?null:owners.getValue(); for (int i=0; i<groupsValue.size(); i++) { createConnection(name, (String)groupsValue.get(i), (String)(ownersValue==null?null:ownersValue.get(i))); } } // Now, process the deferred attributes // if (changes.size()>0) { changes.add(uid); updateViaCommandLine(objectClass, changes, options); } return uid; } else if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { Attribute accounts = attributes.remove(ATTR_CL_MEMBERS); Attribute owners = attributes.remove(ATTR_CL_GROUP_CONN_OWNERS); _connector.throwErrorIfNull(accounts); _connector.checkConnectionConsistency(accounts, owners); if (groupExists(name)) throw new AlreadyExistsException(); CharArrayBuffer buffer = new CharArrayBuffer(); buffer.append("ADDGROUP "); buffer.append(name); buffer.append(mapAttributesToString(attributes)); Uid uid = createOrUpdateViaCommandLine(objectClass, name, buffer); buffer.clear(); if (accounts!=null) { List<Object> accountsValue = accounts.getValue(); List<Object> ownersValue = owners==null?null:owners.getValue(); for (int i=0; i<accountsValue.size(); i++) { createConnection((String)accountsValue.get(i), name, (String)(ownersValue==null?null:ownersValue.get(i))); } } return uid; } else if (objectClass.is(RacfConnector.RACF_CONNECTION_NAME)) { String[] info = RacfConnector.extractRacfIdAndGroupIdFromLdapId(name); String user = info[0]; String group = info[1]; Attribute owner = AttributeUtil.find(ATTR_LDAP_OWNER, attrs); createConnection(user, group, (String)(owner==null?null:AttributeUtil.getStringValue(owner))); return new Uid(name); } else { throw new ConnectorException((configuration).getMessage(RacfMessages.UNSUPPORTED_OBJECT_CLASS, objectClass)); } } private void createConnection(String user, String group, String owner) { String command = "CONNECT "+user+" GROUP("+group+")"; if (owner!=null) command += " OWNER("+owner+")"; checkCommand(command); } public void deleteViaCommandLine(ObjectClass objectClass, Uid uid) { RacfConfiguration configuration = (RacfConfiguration)_connector.getConfiguration(); String uidString = uid.getUidValue(); if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { String command = "DELUSER "+uidString; try { checkCommand(command); } catch (ConnectorException e) { if (e.toString().contains("INVALID USERID")) throw new UnknownUidException(); if (!userExists(uidString)) throw new UnknownUidException(); throw e; } } else if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { String command = "DELGROUP "+uidString; // To make this more efficient, we just issue the command. // If it fails, we check to see if // . group does not exist // . group has members // Each of which will generate a nice error. Otherwise, throw a // generic error // try { checkCommand(command); } catch (ConnectorException e) { if (e.toString().contains("INVALID GROUP")) throw new UnknownUidException(); int memberCount = memberCount(uidString); if (memberCount>0) throw new ConnectorException(configuration.getMessage(RacfMessages.GROUP_NOT_EMPTY)); throw e; } } else if (objectClass.is(RacfConnector.RACF_CONNECTION_NAME)) { String[] info = RacfConnector.extractRacfIdAndGroupIdFromLdapId(uidString); String user = info[0]; String group = info[1]; String command = "REMOVE "+user+" GROUP("+group+")"; checkCommand(command); } else { throw new ConnectorException(configuration.getMessage(RacfMessages.UNKNOWN_UID_TYPE, uidString)); } } private boolean groupExists(String name) { return objectExists(name, "GROUP"); } private boolean userExists(String name) { return objectExists(name, "USER"); } private boolean objectExists(String name, String type) { validateName(name, ((RacfConfiguration)_connector.getConfiguration())); String command = "SEARCH CLASS("+type+") FILTER("+name+")"; String objects = getCommandOutput(command); return !(objects.contains(NO_ENTRIES)); } private int memberCount(String name) { String output = getCommandOutput("LISTGRP "+name); boolean notFound = (output.toUpperCase().contains(NAME_NOT_FOUND)); if (notFound) throw new UnknownUidException(); try { OutputParser transform = _segmentParsers.get("GROUP.RACF"); if (transform==null) throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.UNKNOWN_SEGMENT, ATTR_CL_MEMBERS)); Map<String, Object> attributes = (Map<String, Object>)transform.parse(output); List<Object> members = (List<Object>)attributes.get(ATTR_CL_MEMBERS); if (members==null) return 0; return members.size(); } catch (Exception e) { throw ConnectorException.wrap(e); } } static private Pattern _namePattern = Pattern.compile("[a-zA-Z0-9*%@]+"); static void validateName(Attribute attribute, RacfConfiguration config) { if (!attribute.is(Name.NAME)) return; String name = ((Name)attribute).getNameValue(); validateName(name, config); } static void validateName(String name, RacfConfiguration config) { if (name.length()>8) throw new ConnectorException(config.getMessage(RacfMessages.BAD_NAME_FILTER, name)); if (!_namePattern.matcher(name).matches()) throw new ConnectorException(config.getMessage(RacfMessages.BAD_NAME_FILTER, name)); } public List<String> getMembersOfGroupViaCommandLine(String group) { validateName(group, ((RacfConfiguration)_connector.getConfiguration())); String command = "LISTGRP "+group; String output = getCommandOutput(command); try { OutputParser transform = _segmentParsers.get("GROUP.RACF"); if (transform==null) throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.UNKNOWN_SEGMENT, ATTR_CL_MEMBERS)); Map<String, Object> attributes = transform.parse(output); List<Object> members = (List<Object>)attributes.get(ATTR_CL_MEMBERS); List<String> membersAsString = new LinkedList<String>(); if (members!=null) { for (Object member : members) membersAsString.add((String)member); } return membersAsString; } catch (Exception e) { throw ConnectorException.wrap(e); } } public List<String> getGroupsForUserViaCommandLine(String user) { validateName(user, ((RacfConfiguration)_connector.getConfiguration())); String command = "LISTUSER "+user; String output = getCommandOutput(command); OutputParser transform = _segmentParsers.get("ACCOUNT.RACF"); if (transform==null) throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.UNKNOWN_SEGMENT, ATTR_CL_GROUPS)); try { Map<String, Object> map = transform.parse(output); List<String> groups = (List<String>)map.get(ATTR_CL_GROUPS); String defaultGroup = (String)map.get(ATTR_CL_DFLTGRP); List<String> groupsAsString = new LinkedList<String>(); groupsAsString.add(defaultGroup); for (Object group : groups) if (!group.equals(defaultGroup)) groupsAsString.add((String)group); return groupsAsString; } catch (Exception e) { throw ConnectorException.wrap(e); } } Pattern _errorMessage = Pattern.compile("[^\\s]{9}"); public List<String> getUsersViaCommandLine(String query) { List<String> result = new LinkedList<String>(getObjectsViaCommandLine("USER", query)); // We eliminate certain users // for (String user : new String[] {"irrcerta", "irrmulti", "irrsitec"}) result.remove(user); return result; } public List<String> getGroupsViaCommandLine(String query) { return getObjectsViaCommandLine("GROUP", query); } private List<String> getObjectsViaCommandLine(String className, String query) { String command = null; if (query!=null && query.length()>0) command = "SEARCH CLASS("+className+") FILTER("+query+")"; else command = "SEARCH CLASS("+className+")"; String objects = getCommandOutput(command); if (objects.contains(NO_ENTRIES)) { return new LinkedList<String>(); } // Error messages all start with a 9 character error code, // and groups/users are at most 8 characters long. This allow us to // determine if there are any error messages in the text // Matcher matcher = _errorMessage.matcher(objects); if (matcher.find()) { String error = objects.substring(matcher.start()).trim(); throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.ERROR_IN_GET_GROUPS, error)); } else { String[] objectsArray = objects.trim().split("\\s+"); for (int i=0; i<objectsArray.length; i++) objectsArray[i] = objectsArray[i]; return Arrays.asList(objectsArray); } } public Uid updateViaCommandLine(ObjectClass objectClass, Set<Attribute> attrs, OperationOptions options) { Map<String, Attribute> attributes = new HashMap<String, Attribute>(AttributeUtil.toMap(attrs)); Uid uid = (Uid)attributes.get(Uid.NAME); String name = uid.getUidValue(); if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { Attribute groups = attributes.remove(ATTR_CL_GROUPS); Attribute groupOwners = attributes.remove(ATTR_CL_GROUP_CONN_OWNERS); Attribute expired = attributes.get(ATTR_CL_EXPIRED); Attribute password = attributes.get(ATTR_CL_PASSWORD); _connector.throwErrorIfNull(groups); _connector.throwErrorIfNull(groupOwners); // RACF makes it difficult to specify ENABLE_DATE/DISABLE_DATE // except in its own command // Attribute enable = attributes.get(ATTR_CL_ENABLED); Attribute enableDate = attributes.remove(ATTR_CL_RESUME_DATE); Attribute disableDate = attributes.remove(ATTR_CL_REVOKE_DATE); if (enableDate!=null && disableDate!=null) attributes.remove(ATTR_CL_ENABLED); if (expired!=null && password==null) throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.EXPIRED_NO_PASSWORD)); if (!userExists(name)) throw new UnknownUidException(); validateCatalogAttributes(attributes); setCatalogAttributes(attributes); CharArrayBuffer buffer = new CharArrayBuffer(); buffer.append("ALTUSER "); buffer.append(name); char[] attributeString = mapAttributesToString(attributes); buffer.append(attributeString); try { if (groups!=null) _connector.setGroupMembershipsForUser(name, groups, groupOwners); if (attributeString.length>0) uid = createOrUpdateViaCommandLine(objectClass, name, buffer); } finally { buffer.clear(); } if (enableDate!=null || disableDate!=null) { // If we are given a ENABLE_DATE/DISABLE_DATE, but not an ENABLE status // we have to fetch ENABLE status, since we will alter it as part of // setting ENABLE_DATE/DISABLE_DATE // if (enable==null) { Map<String, Object> map = new HashMap<String, Object>(); map.put(OperationOptions.OP_ATTRIBUTES_TO_GET, new String[] { OperationalAttributes.ENABLE_NAME }); OperationOptions operationOptions = new OperationOptions(map); LocalHandler handler = new LocalHandler(); _connector.executeQuery(ObjectClass.ACCOUNT, name, handler, operationOptions); ConnectorObject object = handler.iterator().next(); enable = object.getAttributeByName(OperationalAttributes.ENABLE_NAME); } Boolean currentEnableState = enable==null?null:AttributeUtil.getBooleanValue(enable); Boolean lastEnableState = currentEnableState; if (enableDate!=null) { lastEnableState = Boolean.FALSE; buffer = new CharArrayBuffer(); buffer.append("ALTUSER "); buffer.append(name); buffer.append(" REVOKE"); Map<String, Attribute> enableDateAttributes = new HashMap<String, Attribute>(); enableDateAttributes.put(enableDate.getName(), enableDate); attributeString = mapAttributesToString(enableDateAttributes); buffer.append(attributeString); createOrUpdateViaCommandLine(objectClass, name, buffer); } if (disableDate!=null) { lastEnableState = Boolean.TRUE; buffer = new CharArrayBuffer(); buffer.append("ALTUSER "); buffer.append(name); buffer.append(" RESUME"); Map<String, Attribute> disableDateAttributes = new HashMap<String, Attribute>(); disableDateAttributes.put(disableDate.getName(), disableDate); attributeString = mapAttributesToString(disableDateAttributes); buffer.append(attributeString); createOrUpdateViaCommandLine(objectClass, name, buffer); } if (!lastEnableState.equals(currentEnableState)) { buffer = new CharArrayBuffer(); buffer.append("ALTUSER "); buffer.append(name); if (currentEnableState) buffer.append(" RESUME"); else buffer.append(" REVOKE"); createOrUpdateViaCommandLine(objectClass, name, buffer); } } return uid; } else if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { Attribute members = attributes.remove(ATTR_CL_MEMBERS); Attribute groupOwners = attributes.remove(ATTR_CL_GROUP_CONN_OWNERS); _connector.throwErrorIfNull(members); _connector.throwErrorIfNull(groupOwners); if (!groupExists(name)) throw new UnknownUidException(); CharArrayBuffer buffer = new CharArrayBuffer(); buffer.append("ALTGROUP "); buffer.append(name); char[] attributeString = mapAttributesToString(attributes); buffer.append(attributeString); try { if (members!=null) _connector.setGroupMembershipsForGroups(name, members, groupOwners); if (attributeString.length>0) return createOrUpdateViaCommandLine(objectClass, name, buffer); else return uid; } finally { buffer.clear(); } } else { throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.UNSUPPORTED_OBJECT_CLASS, objectClass)); } } private void setCatalogAttributes(Map<String, Attribute> attributes) { if (hasSingleStringValue(attributes.get(ATTR_CL_CATALOG_ALIAS))) { String alias = AttributeUtil.getStringValue(attributes.get(ATTR_CL_CATALOG_ALIAS)); String masterCat = AttributeUtil.getStringValue(attributes.get(ATTR_CL_MASTER_CATALOG)); String userCat = AttributeUtil.getStringValue(attributes.get(ATTR_CL_USER_CATALOG)); String command = "DEFINE ALIAS (NAME('"+alias+"') RELATE('"+userCat+"')) CATALOG('"+masterCat+"')"; checkCommand(getCommandOutput(command)); } } private void getCatalogAttributes(String identifier, Map<String, Object> attributesFromCommandLine) { String command = "LISTC ENT('"+identifier+"') ALL"; String output = getCommandOutput(command); OutputParser transform = _segmentParsers.get("ACCOUNT."+CATALOG); try { attributesFromCommandLine.putAll(transform.parse(output)); } catch (Exception e) { throw ConnectorException.wrap(e); } } private boolean hasSingleStringValue(Attribute attribute) { if (attribute==null) return false; List<Object> values = attribute.getValue(); if (values==null || values.size()!=1) return false; return (values.get(0) instanceof String); } private void validateCatalogAttributes(Map<String, Attribute> attributes) { boolean alias = hasSingleStringValue(attributes.get(ATTR_CL_CATALOG_ALIAS)); boolean masterCat = hasSingleStringValue(attributes.get(ATTR_CL_MASTER_CATALOG)); boolean userCat = hasSingleStringValue(attributes.get(ATTR_CL_USER_CATALOG)); // All must be either present or missing // if ((alias||masterCat||userCat)!=(alias&&masterCat&&userCat)) { throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.INCONSISTENT_CATALOG_ARGS)); } } public Map<String, Object> getAttributesFromCommandLine(ObjectClass objectClass, String racfName, Set<String> attributesToGet) { String objectClassPrefix = null; String listCommand = null; if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { objectClassPrefix = "GROUP."; listCommand = "LISTGRP"; } else if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { objectClassPrefix = "ACCOUNT."; listCommand = "LISTUSER"; } else { throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.UNSUPPORTED_OBJECT_CLASS)); } // Determine the set of segment names, if any // We use a TreeSet to force an ordering // Set<String> segmentsNeeded = new TreeSet<String>(); if (attributesToGet!=null) { for (String attributeToGet : attributesToGet) { int index = attributeToGet.indexOf(RacfConnector.SEPARATOR); if (index!=-1) { String prefix = attributeToGet.substring(0, index); segmentsNeeded.add(prefix); if (!_segmentParsers.containsKey(objectClassPrefix+prefix)) { throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.UNKNOWN_SEGMENT, attributeToGet)); } } } } // If we are asking for segment information, ensure that command-line login // information was specified // if (segmentsNeeded.size()>0 && ConnectionPool.getConnectionPool((RacfConfiguration)_connector.getConfiguration()).isEmpty()) throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.ATTRS_NO_CL)); Map<String, Object> attributesFromCommandLine = new HashMap<String, Object>(); if (segmentsNeeded.size()>0) { boolean racfNeeded = segmentsNeeded.remove(RACF); boolean catalogNeeded = segmentsNeeded.remove(CATALOG); StringBuffer buffer = new StringBuffer(); buffer.append(listCommand+" "+racfName); if (!racfNeeded) buffer.append(" NORACF"); for (String segment : segmentsNeeded) buffer.append(" "+segment); String output = getCommandOutput(buffer.toString())+"\n"; // Split out the various segments // StringBuffer segmentPatternString = new StringBuffer(); if (racfNeeded) segmentPatternString.append("(.+?)"); for (String segment : segmentsNeeded) { segmentPatternString.append("(NO )?"+segment.toUpperCase()+" INFORMATION (.+?)"); } Pattern segmentsPattern = Pattern.compile(segmentPatternString.toString()+"$", Pattern.DOTALL); Matcher segmentsMatcher = segmentsPattern.matcher(output); if (segmentsMatcher.find()) { // Deal with RACF first // int offset = 0; if (racfNeeded) { OutputParser transform = _segmentParsers.get(objectClassPrefix+"RACF"); try { attributesFromCommandLine.putAll(transform.parse(segmentsMatcher.group(1))); } catch (Exception e) { if (_debug) System.out.println(_buffer2.toString().replaceAll("(.{80})", "$1\n")); throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.UNPARSEABLE_RESPONSE, "LISTUSER", output)); } offset = 1; } // Parse the other segments, and add the attributes to the set of // attributes received // int i=0; for (String segment : segmentsNeeded) { String noValue = segmentsMatcher.group(2*i+offset+1); String segmentValue = segmentsMatcher.group(2*i+offset+2); if (StringUtil.isBlank(noValue)) { OutputParser transform = _segmentParsers.get(objectClassPrefix+segment); attributesFromCommandLine.putAll(transform.parse(segmentValue)); } i++; } } else if (output.toUpperCase().contains(UNABLE_TO_LOCATE_USER)) { throw new UnknownUidException(); } else { if (_debug) System.out.println(_buffer2.toString().replaceAll("(.{80})", "$1\n")); throw new ConnectorException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.UNPARSEABLE_RESPONSE, "LISTUSER", output)); } } // Remap ACCOUNT attributes as needed // if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { if (attributesToGet.contains(ATTR_CL_CATALOG_ALIAS) || attributesToGet.contains(ATTR_CL_USER_CATALOG) || attributesToGet.contains(ATTR_CL_MASTER_CATALOG)) { getCatalogAttributes(racfName, attributesFromCommandLine); } // __ENABLE__ is indicated by the REVOKED ATTRIBUTE // We also remove REVOKED from attribute list, since we display it separately // if (attributesFromCommandLine.containsKey(ATTR_CL_ATTRIBUTES)) { List<Object> value = (List<Object>)attributesFromCommandLine.get(ATTR_CL_ATTRIBUTES); boolean revoked = value.remove("REVOKED"); attributesFromCommandLine.put(OperationalAttributes.ENABLE_NAME, !revoked); } // Last Access date must be converted // if (attributesFromCommandLine.containsKey(ATTR_CL_LAST_ACCESS)) { Object value = attributesFromCommandLine.get(ATTR_CL_LAST_ACCESS); Long converted = _connector.convertFromRacfTimestamp(value); attributesFromCommandLine.put(PredefinedAttributes.LAST_LOGIN_DATE_NAME, converted); } // password change date must be converted // if (attributesFromCommandLine.containsKey(ATTR_CL_PASSDATE)) { Object value = attributesFromCommandLine.get(ATTR_CL_PASSDATE); Long converted = _connector.convertFromRacfTimestamp(value); attributesFromCommandLine.put(PredefinedAttributes.LAST_PASSWORD_CHANGE_DATE_NAME, converted); // password change date is 00.000 if expired // Boolean expired = "00.000".equals(value); attributesFromCommandLine.put(OperationalAttributes.PASSWORD_EXPIRED_NAME, expired); } // Revoke date must be converted // long now = new Date().getTime(); if (attributesFromCommandLine.containsKey(ATTR_CL_REVOKE_DATE)) { Object value = attributesFromCommandLine.get(ATTR_CL_REVOKE_DATE); Long converted = _connector.convertFromResumeRevokeFormat(value); if (converted==null || converted<now) attributesFromCommandLine.put(OperationalAttributes.DISABLE_DATE_NAME, null); else attributesFromCommandLine.put(OperationalAttributes.DISABLE_DATE_NAME, converted); } // Resume date must be converted // if (attributesFromCommandLine.containsKey(ATTR_CL_RESUME_DATE)) { Object value = attributesFromCommandLine.get(ATTR_CL_RESUME_DATE); Long converted = _connector.convertFromResumeRevokeFormat(value); if (converted==null || converted<now) attributesFromCommandLine.put(OperationalAttributes.ENABLE_DATE_NAME, null); else attributesFromCommandLine.put(OperationalAttributes.ENABLE_DATE_NAME, converted); } // Groups must be filled in if null // if (!attributesFromCommandLine.containsKey(ATTR_CL_GROUPS)) { attributesFromCommandLine.put(ATTR_CL_GROUPS, new LinkedList<Object>()); } // Group Owners must be filled in if null // if (!attributesFromCommandLine.containsKey(ATTR_CL_GROUP_CONN_OWNERS)) { attributesFromCommandLine.put(ATTR_CL_GROUP_CONN_OWNERS, new LinkedList<Object>()); } } // Remap GROUP attributes as needed // if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { if (attributesFromCommandLine.containsKey(ATTR_CL_SUPGROUP)) { Object value = attributesFromCommandLine.get(ATTR_CL_SUPGROUP); if ("NONE".equals(value)) attributesFromCommandLine.put(ATTR_CL_SUPGROUP, null); } // Groups must be filled in if null // if (!attributesFromCommandLine.containsKey(ATTR_CL_GROUPS)) { attributesFromCommandLine.put(ATTR_CL_GROUPS, new LinkedList<Object>()); } // Members must be filled in if null // if (!attributesFromCommandLine.containsKey(ATTR_CL_MEMBERS)) { attributesFromCommandLine.put(ATTR_CL_MEMBERS, new LinkedList<Object>()); } // Group Owners must be filled in if null // if (!attributesFromCommandLine.containsKey(ATTR_CL_GROUP_CONN_OWNERS)) { attributesFromCommandLine.put(ATTR_CL_GROUP_CONN_OWNERS, new LinkedList<Object>()); } } // If we didn't fetch RACF segment, fill in name // if (!attributesFromCommandLine.containsKey(ATTR_CL_USERID)) attributesFromCommandLine.put(ATTR_CL_USERID, racfName); return attributesFromCommandLine; } private StringBuffer _buffer = new StringBuffer(); private StringBuffer _buffer2 = new StringBuffer(); private boolean _timedOut = false; private void appendScreen(RW3270Connection rw3270Connection, String display) { int index = display.lastIndexOf(OUTPUT_COMPLETE); if (index>-1) { _buffer.append(display.substring(0, Math.min(display.length(), index+rw3270Connection.getWidth()))); } else { _buffer.append(display); } } private void handleScreenOfOutput(RW3270Connection rw3270Connection, ExpectState state) throws InterruptedException { // If we are at commandComplete, we're done // Object commandComplete = state.getVar("commandComplete"); if (Boolean.TRUE.equals(commandComplete)) { return; } // If we found an error earlier, we must just return // Object errorDetected = state.getVar("errorDetected"); if (errorDetected!=null) throw new ErrorRecoveredException(((RacfConfiguration)_connector.getConfiguration()).getMessage(RacfMessages.ERROR_IN_RACF_COMMAND, errorDetected.toString().trim())); // Get the screen contents // rw3270Connection.waitForUnlock(); String display = rw3270Connection.getDisplay(); // If display ends with "***", we may have // READY // *** if (removeTrailingSpaces(display).endsWith(OUTPUT_CONTINUING)) { String display2 = display.substring(0, display.lastIndexOf(OUTPUT_CONTINUING)); if (removeTrailingSpaces(display2).endsWith(OUTPUT_COMPLETE)) { // We have // READY // *** // So, make sure we don't double save state.addVar("commandComplete", Boolean.TRUE); } // We have // *** // So, shorten the display, and send [enter], and continue // _buffer2.append(display2.toString()); _buffer2.append("-0------------------------------------------------------------------------------"); appendScreen(rw3270Connection, display2); rw3270Connection.sendEnter(); state.exp_continue(); return; } // In this case, we should end with READY // if (removeTrailingSpaces(display).endsWith(OUTPUT_COMPLETE)) { _buffer2.append(display.toString()); _buffer2.append("-1------------------------------------------------------------------------------"); appendScreen(rw3270Connection, display); } else { // Nothing special, so keep going state.exp_continue(); } } private void waitFor(final RW3270Connection rw3270Connection, Integer timeout) { _buffer.setLength(0); _buffer2.setLength(0); try { _timedOut = false; List<Match> matches = new LinkedList<Match>(); // Match the continue expression // matches.add(new RegExpMatch(OUTPUT_CONTINUING_PATTERN, new Closure() { public void run(ExpectState state) throws Exception { handleScreenOfOutput(rw3270Connection, state); return; } })); // Match the command complete expression // matches.add(new RegExpMatch(OUTPUT_COMPLETE_PATTERN, new Closure() { public void run(ExpectState state) throws Exception { handleScreenOfOutput(rw3270Connection, state); return; } })); // Match the error expression, so // send the abort command // continue execution, to see if we can recover // matches.add(new RegExpMatch("IKJ\\d+\\w REENTER THIS OPERAND", new Closure() { public void run(ExpectState state) throws Exception { state.addVar("errorDetected", state.getBuffer()); rw3270Connection.waitForUnlock(); rw3270Connection.sendPAKeys(1); state.exp_continue(); } })); matches.add(new RegExpMatch("IKJ\\d+\\w ENTER (\\w\\s)+-", new Closure() { public void run(ExpectState state) throws Exception { state.addVar("errorDetected", state.getBuffer()); rw3270Connection.waitForUnlock(); rw3270Connection.sendPAKeys(1); state.exp_continue(); } })); if (timeout != null) matches.add(new TimeoutMatch(timeout, new Closure() { public void run(ExpectState state) throws Exception { _timedOut = true; } })); rw3270Connection.waitFor(matches.toArray(new Match[0])); } catch (Exception e) { throw ConnectorException.wrap(e); } if (_timedOut) { throw new ConnectorException(_connector.getConfiguration().getConnectorMessages().format( "IsAlive", "timed out waiting for ''{0}'':''{1}''", OUTPUT_COMPLETE, _buffer.toString())); } } private String removeTrailingSpaces(String string) { if (StringUtil.isBlank(string)) return ""; StringBuffer buffer = new StringBuffer(); buffer.append(string); while (buffer.charAt(buffer.length()-1)==' ' || buffer.charAt(buffer.length()-1)=='|') buffer.setLength(buffer.length()-1); return buffer.toString(); } private static class CharArrayBuffer { private char[] _array; private int _position; public CharArrayBuffer() { _array = new char[1024]; _position = 0; } public void append(String string ) { append(string.toCharArray()); } public void append(char character) { append(new char[] { character }); } public void append(char[] string) { if (_position+string.length> _array.length) { char[] oldArray = _array; _array = new char[_array.length+1024]; System.arraycopy(oldArray, 0, _array, 0, _position); Arrays.fill(oldArray, 0, oldArray.length, ' '); } System.arraycopy(string, 0, _array, _position, string.length); _position += string.length; } public void clear() { Arrays.fill(_array, 0, _array.length, ' '); } public char[] getArray() { char[] result = new char[_position]; System.arraycopy(_array, 0, result, 0, _position); return result; } } }