/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.plugin.nss; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.Property; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.RawConfiguration; import org.rhq.core.domain.measurement.AvailabilityType; import org.rhq.core.pluginapi.configuration.ResourceConfigurationFacet; import org.rhq.core.pluginapi.inventory.ResourceComponent; import org.rhq.core.pluginapi.inventory.ResourceContext; import org.rhq.core.pluginapi.operation.OperationFacet; import org.rhq.core.pluginapi.operation.OperationResult; import org.rhq.core.util.MessageDigestGenerator; /** * Allows the RHQ server to manage the Name Server Switch Configuration for a Linux Platform * * @author Adam Young */ public class NameServiceSwitchComponent implements ResourceComponent<NameServiceSwitchComponent>, OperationFacet, ResourceConfigurationFacet { /** * The standard location for the configuration file */ private static final String ETC_NSSWITCH_CONF = "/tmp/nsswitch.conf"; //private static final String ETC_NSSWITCH_CONF = "/etc/nsswitch.conf"; private final Log log = LogFactory.getLog(NameServiceSwitchComponent.class); /** * The set of Services supported by NSS. */ final static String[] SERVICES = { "compat", "db", "dns", "files", "hesiod", "nis", "nisplus" }; final static String[] DATABASES = { "aliases", /*Mail aliases, used by sendmail(8). Presently ignored.*/ "ethers", /*Ethernet numbers.*/ "group", /*Groups of users, used by getgrent(3) functions.*/ "hosts", /*Host names and numbers, used by gethostbyname(3) and similar functions.*/ "netgroup", /*Network wide list of hosts and users, used for access rules. C libraries before glibc 2.1 only support netgroups over NIS.*/ "networks", /*Network names and numbers, used by getnetent(3) functions.*/ "passwd", /*passwd User passwords, used by getpwent(3) functions.*/ "protocols", /* Network protocols, used by getprotoent(3) functions.*/ "publickey", /*Public and secret keys for Secure_RPC used by NFS and NIS+.*/ "rpc", /*Remote procedure call names and numbers, used by getrpcbyname(3) and similar functions.*/ "services" /*shadow Shadow user passwords, used by getspnam(3).*/}; /** * TODO, Add any custom services registered on the machine */ static Set<String> serviceSet = new HashSet<String>(Arrays.asList(SERVICES)); final static Pattern rulePattern = Pattern .compile("\\[!?(success|notfound|unavail|tryagain|SUCCESS|NOTFOUND|UNAVAIL|TRYAGAIN)=(return|continue|RETURN|CONTINUE)\\]"); private static Pattern linePattern = Pattern.compile("\\s*(\\p{Alpha}*):((?:\\s|\\p{Alpha}|\\[|\\]|\\=)*)"); /** * Represents the resource configuration of the custom product being managed. */ private Configuration resourceConfiguration; /** * All AMPS plugins are stateful - this context contains information that your resource component can use when * performing its processing. */ private ResourceContext resourceContext; /** * This is called when your component has been started with the given context. You normally initialize some internal * state of your component as well as attempt to make a stateful connection to your managed resource. * * @see ResourceComponent#start(ResourceContext) */ public void start(ResourceContext context) { resourceContext = context; } /** * This is called when the component is being stopped, usually due to the plugin container shutting down. You can * perform some cleanup here; though normally not much needs to be done here. * * @see ResourceComponent#stop() */ public void stop() { } /** * All resource components must be able to tell the plugin container if the managed resource is available or not. * This method is called by the plugin container when it needs to know if the managed resource is actually up and * available. * * @see ResourceComponent#getAvailability() */ public AvailabilityType getAvailability() { // TODO: here you normally make some type of connection attempt to the managed resource // to determine if it is really up and running. return AvailabilityType.UP; } /** * The plugin container will call this method when it wants to invoke an operation on your managed resource. Your * plugin will connect to the managed resource and invoke the analogous operation in your own custom way. * * @see OperationFacet#invokeOperation(String, Configuration) */ public OperationResult invokeOperation(String name, Configuration configuration) { return null; } static public String getContents(File aFile) { //...checks on aFile are elided StringBuilder contents = new StringBuilder(); try { //use buffering, reading one line at a time //FileReader always assumes default encoding is OK! BufferedReader input = new BufferedReader(new FileReader(aFile)); try { String line = null; //not declared within while loop /* * readLine is a bit quirky : * it returns the content of a line MINUS the newline. * it returns null only for the END of the stream. * it returns an empty String if two newlines appear in a row. */ while ((line = input.readLine()) != null) { contents.append(line); contents.append(System.getProperty("line.separator")); } } finally { input.close(); } } catch (IOException ex) { ex.printStackTrace(); } return contents.toString(); } public Set<RawConfiguration> loadRawConfigurations() { Set<RawConfiguration> raws = new HashSet<RawConfiguration>(); RawConfiguration raw = new RawConfiguration(); raw.setPath(ETC_NSSWITCH_CONF); String contents = getContents(new File(raw.getPath())); String sha256 = new MessageDigestGenerator(MessageDigestGenerator.SHA_256).calcDigestString(contents); raw.setContents(contents, sha256); raws.add(raw); return raws; } public Configuration loadStructuredConfiguration() { Configuration configuration = new Configuration(); for (RawConfiguration raw : loadRawConfigurations()) { mergeStructuredConfiguration(raw, configuration); } return configuration; } public RawConfiguration mergeRawConfiguration(Configuration from, RawConfiguration to) { // TODO Auto-generated method stub return null; } public void mergeStructuredConfiguration(RawConfiguration from, Configuration to) { ArrayList<Property> properties = new ArrayList<Property>(); to.addRawConfiguration(from); try { BufferedReader input = new BufferedReader(new StringReader(from.getContents())); String line = null; //not declared within while loop while ((line = input.readLine()) != null) { Matcher matcher = linePattern.matcher(line); if (matcher.matches()) { properties.add(new PropertySimple(matcher.group(1), matcher.group(2).trim())); } } } catch (IOException ex) { ex.printStackTrace(); } to.setProperties(properties); } public void persistRawConfiguration(RawConfiguration rawConfiguration) { } public void persistStructuredConfiguration(Configuration configuration) { // TODO Auto-generated method stub } public void validateRawConfiguration(RawConfiguration rawConfiguration) throws IllegalArgumentException { throw new IllegalArgumentException("bad config file"); } public void validateStructuredConfiguration(Configuration configuration) throws IllegalArgumentException { boolean success = true; for (Property prop : configuration.getProperties()) { success &= validateProperty(prop); } if (!success) { throw new IllegalArgumentException(); } } private static boolean validateProperty(Property prop) { String[] vals = ((PropertySimple) prop).getStringValue().split(" "); ArrayList<String> invalidServices = new ArrayList<String>(); ArrayList<String> invalidRules = new ArrayList<String>(); boolean success = true; for (String val : vals) { if (val.startsWith("[")) { if (!rulePattern.matcher(val).matches()) { invalidRules.add(val); success = false; } } else { if (!serviceSet.contains(val)) { invalidServices.add(val); success = false; } } } StringBuilder errorMessage = new StringBuilder(); if (!invalidServices.isEmpty()) { buildErrorMessage(invalidServices, errorMessage, "Service"); } if (!invalidRules.isEmpty()) { buildErrorMessage(invalidRules, errorMessage, "Rule"); } if (errorMessage.length() > 0) { prop.setErrorMessage(errorMessage.toString()); } return success; } private static void buildErrorMessage(ArrayList<String> errors, StringBuilder errorMessage, String type) { if (errors.size() == 1) { errorMessage.append(errors.get(0)); errorMessage.append(" is not a valid NSS " + type + "."); } else { int i = 0; for (String db : errors) { errorMessage.append(db); if (i < (errors.size() - 1)) { errorMessage.append(", "); } if (i == (errors.size() - 2)) { errorMessage.append("and "); } ++i; } errorMessage.append(" are not a valid NSS Services. "); } } public static void main(String[] args) { { String noMatch = "# abcd: val"; Matcher matcher = linePattern.matcher(noMatch); if (matcher.matches()) { System.exit(1); } } { String yesMatch = "bootparams: nisplus [NOTFOUND=return] files"; Matcher matcher = linePattern.matcher(yesMatch); if (matcher.matches()) { System.out.println("Group0=" + matcher.group(1)); System.out.println("Group1=" + matcher.group(2).trim()); } } { PropertySimple prop = new PropertySimple("passwd", "nisplus [NOTFOUND=return] files"); validateProperty(prop); if (prop.getErrorMessage() != null) { System.out.println("error message is '" + prop.getErrorMessage() + "'"); System.exit(1); } } { PropertySimple prop = new PropertySimple("passwd", "nisplus [NOTFOUND=return] files"); validateProperty(prop); if (prop.getErrorMessage() != null) { System.out.println("error message is '" + prop.getErrorMessage() + "'"); System.exit(1); } } { PropertySimple prop = new PropertySimple("passwd", "nisplus [UNUSED=return] [!NOWAY=return] files"); validateProperty(prop); if (prop.getErrorMessage() != null) { System.out.println("error message is '" + prop.getErrorMessage() + "'"); } else { System.exit(1); } } { PropertySimple prop = new PropertySimple("passwd", "nisplus nope neither garbage junk files"); validateProperty(prop); if (prop.getErrorMessage() != null) { System.out.println("error message is '" + prop.getErrorMessage() + "'"); } else { System.exit(1); } } { PropertySimple prop = new PropertySimple("passwd", "nisplus nope neither [UNUSED=return] [!NOWAY=return] garbage junk files"); validateProperty(prop); if (prop.getErrorMessage() != null) { System.out.println("error message is '" + prop.getErrorMessage() + "'"); } else { System.exit(1); } } } }