From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 27392 invoked by alias); 26 Jun 2009 10:43:07 -0000 Received: (qmail 27385 invoked by alias); 26 Jun 2009 10:43:06 -0000 X-SWARE-Spam-Status: No, hits=0.2 required=5.0 tests=AWL,BAYES_00,J_CHICKENPOX_26,J_CHICKENPOX_33,J_CHICKENPOX_34,J_CHICKENPOX_36,J_CHICKENPOX_44,J_CHICKENPOX_46,J_CHICKENPOX_47,J_CHICKENPOX_48,SPF_HELO_PASS X-Spam-Status: No, hits=0.2 required=5.0 tests=AWL,BAYES_00,J_CHICKENPOX_26,J_CHICKENPOX_33,J_CHICKENPOX_34,J_CHICKENPOX_36,J_CHICKENPOX_44,J_CHICKENPOX_46,J_CHICKENPOX_47,J_CHICKENPOX_48,SPF_HELO_PASS X-Spam-Check-By: sourceware.org X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on bastion2.fedora.phx.redhat.com Subject: cluster: RHEL5 - Resolves: #498983 - fence_apc_snmp affected by symlink attack vulnerabilities To: cluster-cvs-relay@redhat.com X-Project: Cluster Project X-Git-Module: cluster.git X-Git-Refname: refs/heads/RHEL5 X-Git-Reftype: branch X-Git-Oldrev: 6fbdf268d92e2d3567f452c9e92e3898b58564ac X-Git-Newrev: fcbbc9d89f025bf4d0090becdb8494800af19209 From: =?utf-8?q?Marek_Gr=C3=A1c?= Message-Id: <20090626104237.4813B120380@lists.fedorahosted.org> Date: Fri, 26 Jun 2009 10:43:00 -0000 X-Scanned-By: MIMEDefang 2.58 on 172.16.52.254 Mailing-List: contact cluster-cvs-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Post: List-Help: , Sender: cluster-cvs-owner@sourceware.org X-SW-Source: 2009-q2/txt/msg00769.txt.bz2 Gitweb: http://git.fedorahosted.org/git/cluster.git?p=cluster.git;a=commitdiff;h=fcbbc9d89f025bf4d0090becdb8494800af19209 Commit: fcbbc9d89f025bf4d0090becdb8494800af19209 Parent: 6fbdf268d92e2d3567f452c9e92e3898b58564ac Author: Marek 'marx' Grac AuthorDate: Fri Jun 26 12:37:35 2009 +0200 Committer: Marek 'marx' Grac CommitterDate: Fri Jun 26 12:37:35 2009 +0200 Resolves: #498983 - fence_apc_snmp affected by symlink attack vulnerabilities New fence agent based on snmp fencing library was used. Agent was already deployed in STABLE3 branch. --- fence/agents/apc_snmp/fence_apc_snmp.py | 624 +++++++++---------------------- 1 files changed, 177 insertions(+), 447 deletions(-) diff --git a/fence/agents/apc_snmp/fence_apc_snmp.py b/fence/agents/apc_snmp/fence_apc_snmp.py old mode 100755 new mode 100644 index fb79054..df9ecea --- a/fence/agents/apc_snmp/fence_apc_snmp.py +++ b/fence/agents/apc_snmp/fence_apc_snmp.py @@ -1,462 +1,192 @@ #!/usr/bin/python -############################################################################# -############################################################################# -## -## Copyright (C) Sistina Software, Inc. 1997-2003 All rights reserved. -## Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. -## -## This copyrighted material is made available to anyone wishing to use, -## modify, copy, or redistribute it subject to the terms and conditions -## of the GNU General Public License v.2. -## -############################################################################# -## This APC Fence script uses snmp to control the APC power -## switch. This script requires that net-snmp-utils be installed -## on all nodes in the cluster, and that the powernet369.mib file be -## located in /usr/share/snmp/mibs/ -############################################################################# -############################################################################# - - - -import getopt, sys -import os -import datetime -import select -import signal -from glob import glob +# The Following agent has been tested on: +# - APC Switched Rack PDU (MB:v3.7.0 PF:v2.7.0 PN:apc_hw02_aos_270.bin AF1:v2.7.3 AN1:apc_hw02_aos_270.bin +# AF1:v2.7.3 AN1:apc_hw02_rpdu_273.bin MN:AP7930 HR:B2) - SNMP v1 +# - APC Web/SNMP Management Card (MB:v3.8.6 PF:v3.5.8 PN:apc_hw02_aos_358.bin AF1:v3.5.7 AN1:apc_hw02_aos_358.bin +# AF1:v3.5.7 AN1:apc_hw02_rpdu_357.bin MN:AP7900 HR:B2) - SNMP v1 and v3 (noAuthNoPrivacy,authNoPrivacy, authPrivacy) +# - APC Switched Rack PDU (MB:v3.7.0 PF:v2.7.0 PN:apc_hw02_aos_270.bin AF1:v2.7.3 AN1:apc_hw02_rpdu_273.bin +# MN:AP7951 HR:B2) - SNMP v1 + +import sys, re, pexpect +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing_snmp import * #BEGIN_VERSION_GENERATION -FENCE_RELEASE_NAME="" +RELEASE_VERSION="APC SNMP fence agent" REDHAT_COPYRIGHT="" BUILD_DATE="" #END_VERSION_GENERATION -POWER_ON="outletOn" -POWER_OFF="outletOff" -POWER_REBOOT="outletReboot" - - -# oid defining fence device -oid_sysObjectID = '.1.3.6.1.2.1.1.2.0' - -class SNMP: - def __init__(self, params): - self.hostname = params['ipaddr'] - self.udpport = params['udpport'] - self.community = params['community'] - - def get(self, oid): - args = ['/usr/bin/snmpget'] - args.append('-Oqn') - args.append('-v') - args.append('1') - args.append('-c') - args.append(self.community) - args.append('-m') - args.append('ALL') - args.append(self.hostname + ':' + self.udpport) - args.append(oid) - strr, code = execWithCaptureStatus("/usr/bin/snmpget", args) - if code: - raise Exception, 'snmpget failed' - l = strr.strip().split() - return l[0], ' '.join(l[1:]) - - def set_int(self, oid, value): - args = ['/usr/bin/snmpset'] - args.append('-Oqn') - args.append('-v') - args.append('1') - args.append('-c') - args.append(self.community) - args.append('-m') - args.append('ALL') - args.append(self.hostname + ':' + self.udpport) - args.append(oid) - args.append('i') - args.append(str(value)) - strr,code = execWithCaptureStatus("/usr/bin/snmpset", args) - if code: - raise Exception, 'snmpset failed' - - def walk(self, oid): - args = ['/usr/bin/snmpwalk'] - args.append('-Oqn') - args.append('-v') - args.append('1') - args.append('-c') - args.append(self.community) - args.append('-m') - args.append('ALL') - args.append(self.hostname + ':' + self.udpport) - args.append(oid) - strr,code = execWithCaptureStatus("/usr/bin/snmpwalk", args) - if code: - raise Exception, 'snmpwalk failed' - lines = strr.strip().splitlines() - ret = [] - for line in lines: - l = line.strip().split() - ret.append((l[0], ' '.join(l[1:]).strip('"'))) - return ret - - - -class FenceAgent: - - def __init__(self, params): - self.snmp = SNMP(params) - - def resolve_outlet(self): - raise Exception, 'resolve_outlet() not implemented' - - def status(self): - oid = self.status_oid % self.resolve_outlet() - dummy, stat = self.snmp.get(oid) - if stat == self.state_on or stat == "outletStatusOn": - return 'on' - elif stat == self.state_off or stat == "outletStatusOff": - return 'off' - else: - raise Exception, 'invalid status ' + stat - - def power_off(self): - oid = self.control_oid % self.resolve_outlet() - self.snmp.set_int(oid, self.turn_off) - - def power_on(self): - oid = self.control_oid % self.resolve_outlet() - self.snmp.set_int(oid, self.turn_on) - - - - - - - - -class MasterSwitch(FenceAgent): - - def __init__(self, params): - FenceAgent.__init__(self, params) - - self.status_oid = '.1.3.6.1.4.1.318.1.1.12.3.5.1.1.4.%s' - self.control_oid = '.1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%s' - self.outlet_table_oid = '.1.3.6.1.4.1.318.1.1.12.3.5.1.1.2' - - self.state_on = '1' - self.state_off = '2' - - self.turn_on = '1' - self.turn_off = '2' - - self.port = params['port'] - - def resolve_outlet(self): - outlet = None - try: - outlet = str(int(self.port)) - except: - table = self.snmp.walk(self.outlet_table_oid) - for row in table: - if row[1] == self.port: - t = row[0].strip().split('.') - outlet = t[len(t)-1] - if outlet == None: - raise Exception, 'unable to resolve ' + self.port - else: - self.port = outlet - return outlet - - -class MasterSwitchPlus(FenceAgent): - def __init__(self, params): - FenceAgent.__init__(self, params) - - self.status_oid = '.1.3.6.1.4.1.318.1.1.6.7.1.1.5.%s.1.%s' - self.control_oid = '.1.3.6.1.4.1.318.1.1.6.5.1.1.5.%s.1.%s' - self.outlet_table_oid = '.1.3.6.1.4.1.318.1.1.6.7.1.1.4' - - self.state_on = '1' - self.state_off = '2' - - self.turn_on = '1' - self.turn_off = '3' - - try: - self.switch = params['switch'] - except: - self.switch = '' - self.port = params['port'] - - def resolve_outlet(self): - switch = None - outlet = None - try: - switch = str(int(self.switch)) - outlet = str(int(self.port)) - except: - table = self.snmp.walk(self.outlet_table_oid) - for row in table: - if row[1] == self.port: - t = row[0].strip().split('.') - outlet = t[len(t)-1] - switch = t[len(t)-3] - if outlet == None: - raise Exception, 'unable to resolve ' + self.port - else: - self.switch = switch - self.port = outlet - return (switch, outlet) - - - - - - -def usage(): - print "Usage:" - print "" - print "Options:" - print " -h Usage" - print " -a IP address or hostname of fence device" - print " -u UDP port to use (default 161)" - print " -c SNMP community (default 'private')" - print " -n Outlet name/number to act on" - print " -o Action: Reboot (default), On, Off and Status" - print " -v Verbose mode - write to file" - print " -V Version" - - sys.exit(0) - - - -file_log = None -def set_logging(verbose, verbose_filename): - global file_log - if verbose: - file_log = open(verbose_filename, 'a') - file_log.write('\n----------- ') - file_log.write(datetime.datetime.today().ctime()) - file_log.write(' -----------\n') -def log(msg, error=False): - global file_log - if msg.rfind('\n') != len(msg)-1: - msg += '\n' - if file_log != None: - file_log.write(msg) - if error: - o = sys.stderr +### CONSTANTS ### +# oid defining fence device +OID_SYS_OBJECT_ID='.1.3.6.1.2.1.1.2.0' + +### GLOBAL VARIABLES ### +# Device - see ApcRPDU, ApcMSP, ApcMS +device=None + +# Port ID +port_id=None +# Switch ID +switch_id=None + +# Classes describing Device params +class ApcRPDU: + # Rack PDU + status_oid= '.1.3.6.1.4.1.318.1.1.12.3.5.1.1.4.%d' + control_oid= '.1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%d' + outlet_table_oid='.1.3.6.1.4.1.318.1.1.12.3.5.1.1.2' + ident_str="APC rPDU" + state_on=1 + state_off=2 + turn_on=1 + turn_off=2 + has_switches=False + +class ApcMSP: + # Master Switch+ + status_oid= '.1.3.6.1.4.1.318.1.1.6.7.1.1.5.%d.1.%d' + control_oid= '.1.3.6.1.4.1.318.1.1.6.5.1.1.5.%d.1.%d' + outlet_table_oid='.1.3.6.1.4.1.318.1.1.6.7.1.1.4' + ident_str="APC Master Switch+" + state_on=1 + state_off=2 + turn_on=1 + turn_off=3 + has_switches=True + +class ApcMS: + # Master Switch - seems oldest, but supported on every APC PDU + status_oid= '.1.3.6.1.4.1.318.1.1.4.4.2.1.3.%d' + control_oid= '.1.3.6.1.4.1.318.1.1.4.4.2.1.3.%d' + outlet_table_oid='.1.3.6.1.4.1.318.1.1.4.4.2.1.4' + ident_str="APC Master Switch (fallback)" + state_on=1 + state_off=2 + turn_on=1 + turn_off=2 + has_switches=False + +### FUNCTIONS ### +def apc_set_device(conn,options): + global device + + agents_dir={'.1.3.6.1.4.1.318.1.3.4.5':ApcRPDU, + '.1.3.6.1.4.1.318.1.3.4.4':ApcMSP, + None:ApcMS} + + # First resolve type of APC + apc_type=conn.walk(OID_SYS_OBJECT_ID) + + if (not ((len(apc_type)==1) and (agents_dir.has_key(apc_type[0][1])))): + apc_type=[[None,None]] + + device=agents_dir[apc_type[0][1]] + + conn.log_command("Trying %s"%(device.ident_str)) + +def apc_resolv_port_id(conn,options): + global port_id,switch_id,device + + if (device==None): + apc_set_device(conn,options) + + # Now we resolv port_id/switch_id + if ((options["-n"].isdigit()) and ((not device.has_switches) or (options["-s"].isdigit()))): + port_id=int(options["-n"]) + + if (device.has_switches): + switch_id=int(options["-s"]) else: - o = sys.stdout - o.write(msg) + table=conn.walk(device.outlet_table_oid,30) + for x in table: + if (x[1].strip('"')==options["-n"]): + t=x[0].split('.') + if (device.has_switches): + port_id=int(t[len(t)-1]) + switch_id=int(t[len(t)-3]) + else: + port_id=int(t[len(t)-1]) + if (port_id==None): + fail_usage("Can't find port with name %s!"%(options["-n"])) +def get_power_status(conn,options): + global port_id,switch_id,device + + if (port_id==None): + apc_resolv_port_id(conn,options) + + oid=((device.has_switches) and device.status_oid%(switch_id,port_id) or device.status_oid%(port_id)) + + (oid,status)=conn.get(oid) + return (status==str(device.state_on) and "on" or "off") + +def set_power_status(conn, options): + global port_id,switch_id,device + + if (port_id==None): + apc_resolv_port_id(conn,options) + + oid=((device.has_switches) and device.control_oid%(switch_id,port_id) or device.control_oid%(port_id)) + + conn.set(oid,(options["-o"]=="on" and device.turn_on or device.turn_off)) + + +def get_outlets_status(conn, options): + global device + + result={} + + if (device==None): + apc_set_device(conn,options) + + res_ports=conn.walk(device.outlet_table_oid,30) + + for x in res_ports: + t=x[0].split('.') + + port_num=((device.has_switches) and "%s:%s"%(t[len(t)-3],t[len(t)-1]) or "%s"%(t[len(t)-1])) + + port_name=x[1].strip('"') + port_status="" + result[port_num]=(port_name,port_status) + + return result + +# Define new options +def apc_snmp_define_defaults(): + all_opt["snmp_version"]["default"]="1" + all_opt["community"]["default"]="private" + +# Main agent method def main(): - try: - main2() - return 0 - except Exception, e: - log(str(e), True) - sys.exit(1) -def main2(): - - agents_dir = {'.1.3.6.1.4.1.318.1.3.4.5' : MasterSwitch, - '.1.3.6.1.4.1.318.1.3.4.4' : MasterSwitchPlus} - - verbose = False - params = {} - - if len(sys.argv) > 1: - try: - opts, args = getopt.getopt(sys.argv[1:], "ha:u:c:n:o:v:V", ["help", "output="]) - except getopt.GetoptError: - usage() - sys.exit(2) - - for o, a in opts: - o = o.strip() - a = a.strip() - if o == "-v": - verbose = True - verbose_filename = a - if o == "-V": - print "%s\n" % FENCE_RELEASE_NAME - print "%s\n" % REDHAT_COPYRIGHT - print "%s\n" % BUILD_DATE - sys.exit(0) - if o in ("-h", "--help"): - usage() - sys.exit(0) - if o == "-a": - params['ipaddr'] = a - if o == "-u": - params['udpport'] = a - if o == "-c": - params['community'] = a - if o == "-n": - switch = '' - port = a - if ':' in port: - idx = port.find(':') - switch = port[:idx] - port = port[idx+1:] - params['switch'] = switch - params['port'] = port - if o == "-o": - params['option'] = a.lower() - - else: #Get opts from stdin - for line in sys.stdin: - val = line.strip().split("=") - if len(val) == 2: - o = val[0].strip().lower() - a = val[1].strip() - if o == 'verbose': - if a.lower() == 'on' or a.lower() == 'true' or a == '1': - verbose = True - else: - params[o] = a - - - set_logging(verbose, verbose_filename) - - - ### validation ### - - try: - if params['ipaddr'] == '': - raise Exception, 'missing ipadddr' - except: - log("FENCE: Missing ipaddr param for fence_apc_snmp...exiting", True) - sys.exit(1) - if 'udpport' not in params: - params['udpport'] = '161' - try: - t = int(params['udpport']) - if t >= 65536 or t < 0: - raise Exception, 'invalid udpport' - except: - log("FENCE: Invalid udpport for fence_apc_snmp...exiting", True) - sys.exit(1) - if 'community' not in params: - params['community'] = 'private' - try: - port = params['port'] - if len(port) == 0: - raise Exception, 'missing port' - except: - log("FENCE: Missing port param for fence_apc_snmp...exiting", True) - sys.exit(1) - if 'switch' not in params: - params['switch'] = '' - try: - act = params['option'].lower() - if act in ['on', 'off', 'reboot', 'status']: - params['option'] = act - else: - usage() - sys.exit(3) - except: - params['option'] = 'reboot' - - ### End of validation ### - - if verbose: - log('called with ' + str(params)) - - agent = None - dummy, sys_id = SNMP(params).get(oid_sysObjectID) - if sys_id not in agents_dir: - log('Fence device with \'oid_sysObjectID=' + sys_id + '\' is not supported', True) - sys.exit(1) - agent = agents_dir[sys_id](params) - - if params['option'] == 'status': - log('Outlet "%s" - %s is %s' % (params['port'], - str(agent.resolve_outlet()), - agent.status())) - elif params['option'] == 'on': - agent.power_on() - if agent.status() != 'on': - raise Exception, 'Error turning outlet on' - elif params['option'] == 'off': - agent.power_off() - if agent.status() != 'off': - raise Exception, 'Error turning outlet off' - elif params['option'] == 'reboot': - agent.power_off() - if agent.status() != 'off': - raise Exception, 'Error turning outlet off' - agent.power_on() - if agent.status() != 'on': - raise Exception, 'Error turning outlet on' - else: - print 'nothing to do' - sys.exit(1) - pass - - - -def execWithCaptureStatus(command, argv, searchPath = 0, root = '/', stdin = 0, - catchfd = 1, closefd = -1): - - if not os.access (root + command, os.X_OK): - raise Exception, command + " cannot be run" - - (read, write) = os.pipe() - - childpid = os.fork() - if (not childpid): - if (root and root != '/'): os.chroot (root) - if isinstance(catchfd, tuple): - for fd in catchfd: - os.dup2(write, fd) - else: - os.dup2(write, catchfd) - os.close(write) - os.close(read) - - if closefd != -1: - os.close(closefd) - - if stdin: - os.dup2(stdin, 0) - os.close(stdin) - - if (searchPath): - os.execvp(command, argv) - else: - os.execv(command, argv) - - sys.exit(1) - - os.close(write) - - rc = "" - s = "1" - while (s): - select.select([read], [], []) - s = os.read(read, 1000) - rc = rc + s - - os.close(read) - - try: - (pid, status) = os.waitpid(childpid, 0) - except OSError, (errno, msg): - print __name__, "waitpid:", msg - - if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0): - status = os.WEXITSTATUS(status) - else: - status = -1 - - return (rc, status) + device_opt = [ "help", "version", "agent", "quiet", "verbose", "debug", + "action", "ipaddr", "login", "passwd", "passwd_script", + "test", "port", "no_login", "no_password", + "snmp_version", "community", "snmp_auth_prot", "snmp_sec_level", + "snmp_priv_prot", "snmp_priv_passwd", "snmp_priv_passwd_script", + "udpport" ] + + apc_snmp_define_defaults() + + options=check_input(device_opt,process_input(device_opt)) + + ## Support for -n [switch]:[plug] notation that was used before + if ((options.has_key("-n")) and (-1 != options["-n"].find(":"))): + (switch, plug) = options["-n"].split(":", 1) + if ((switch.isdigit()) and (plug.isdigit())): + options["-s"] = switch + options["-n"] = plug + + if (not (options.has_key("-s"))): + options["-s"]="1" + + # Operate the fencing device + fence_action(FencingSnmp(options), options, set_power_status, get_power_status) if __name__ == "__main__": - ret = main() - sys.exit(ret) + main()