def ccs_init(): global monitor_prefs, realm registerMenu(MENU_TOP, "%HOSTNAME%") registerMenu(MENU_BOTTOM, "External Links") registerMenuItem(MENU_TOP, MENU_GROUP_HOME, "/", "CPE Homepage") registerMenuItem(MENU_TOP, MENU_GROUP_CONTACT, "/contact", \ "Contact Details") # Other modules can override these as necessary in ccs_init registerMenuItem(MENU_BOTTOM, MENU_GROUP_GENERAL, \ "http://www.crc.net.nz/", "CRCnet Homepage") registerMenuItem(MENU_BOTTOM, MENU_GROUP_GENERAL, \ "http://www.google.com/", "Google") resourcedir = config_get("www", "resourcedir", DEFAULT_RESOURCE_DIR) registerDir("/resources", resourcedir) # Initialise preferences store preffile = config_get("www", "preferences", DEFAULT_PREF_FILE) try: ensureFileExists(preffile) monitor_prefs = init_pref_store(preffile) except: log_fatal("Unable to initialise preference store: %s" % preffile, \ sys.exc_info()) realm = {"authenticator":loginForm, "users":{}, \ "default_username":ADMIN_USERNAME} adminpass = pref_get(None, "admin_password", monitor_prefs, \ DEFAULT_ADMIN_PASS) realm["users"][ADMIN_USERNAME] = adminpass registerRealm(CPE_ADMIN_REALM, realm)
def ccs_init(): global debug_snmp # Default SNMP Content Helpers # Implement the basics of the SNMPv2-MIB SNMPv2_MIB = (1,3,6,1,2,1,1) registerStaticOID(SNMPv2_MIB+(1,0), "CRCnet Monitor v%s (r%s), %s" % \ (ccsd_version, ccsd_revision, " ".join(os.uname())), "OctetString") # sysObjectID not implemented UPTIME_START = time.time() @registerDynamicOID(SNMPv2_MIB+(3,0)) def uptime(oid, getNext): if getNext: return None uptime = (time.time()-UPTIME_START)*100 return (oid, TimeTicks(uptime)) # sysContact not implemented registerStaticOID(SNMPv2_MIB+(5,0), socket.gethostname(), "OctetString") # sysLocation not implemented # sysServices not implemented # sysOR not implemented # Start the actual SNMP server try: debug_snmp = config_getboolean("snmp", "debug", False) port = int(config_get("snmp", "port", DEFAULT_SNMP_PORT)) AsyncSNMPServer(('', port)) log_info("SNMP Server Started") except: log_fatal("Could not initialise the SNMP server!", sys.exc_info())
def homepage(request, method): # MOTD at the top liveMOTD = config_getboolean("www", "liveMOTD", False) homepage = config_get("www", "motdHomepage", "http://www.crc.net.nz") if liveMOTD: output = """<div class="content" id="motd">""" output += "<h2>Loading Latest News...</h2><br />" output += "Please wait while the latest news is retrieved." else: output = """<div class="content">""" output += "<h2>Latest News</h2><br />" output += "To keep up to date with the latest news " output += "please visit the homepage at " output += """<a href="%s">%s</a>""" % (homepage, homepage) output += "</div>" # Try and load the status summary from the status module try: from crcnetd.modules.ccs_monitor_status import getStatusSummary status = getStatusSummary() except: log_warn("Could not retrieve status summary", sys.exc_info()) status = "CPE Status not available" # CPE Status output += """<div class="content"> <h2>CPE Status</h2><br /> %s </div> """ % status returnPage(request, "CPE Navigation", output, scripts=["/resources/homepage.js"])
def statusThread(): """Runs as a thread to keep the status information up to date""" global _statusInfo, _runStatus try: # Is status checking enabled enabled = config_getboolean("status", "enabled", True) if not enabled: log_info("Status checking disabled. Exiting status monitor thread") return _runStatus = True; # Setup the thread information ct = threading.currentThread() ct.setName("CCSD Status Monitor") # What interval shall we check hosts at interval = config_get("status", "interval", DEFAULT_CHECK_INTERVAL) # Initialise the host status information hosts = getHostList(ADMIN_SESSION_ID) for host in getHostList(ADMIN_SESSION_ID): if not host["host_active"]: continue name = host["host_name"] _statusInfo[name] = ccs_host_status(ADMIN_SESSION_ID, \ host["host_id"], interval) # Loop forever reading status as appropriate while _runStatus: # wait a bit before checking time.sleep(2) # Does the queue have entries if len(ccs_host_status.update_queue) <= 0: continue # Is the first entry valid if len(ccs_host_status.update_queue[0]) != 2: log_error("Invalid entry in status update queue! - %s" % ccs_host_status.update_queue[0]) ccs_host_status.update_queue.pop(0) continue # Check if it's ready to run if ccs_host_status.update_queue[0][0] > time.time(): continue # Read to run check = ccs_host_status.update_queue.pop(0) try: check[1].update() except: log_error("Failed to update status of %s" % \ check[1]._hostname, sys.exc_info()) # Regardless of what happened, check again sometime soon if it # is still in the list of hosts to check if check[1]._hostname in _statusInfo.keys(): check[1].requeue() except: log_error("Exception in status monitor thread!", sys.exc_info()) log_info("Exiting status monitor thread")
def ts_chargei(): ci_max = portname = config_get("status", "charge_i_max", DEFAULT_CI_MAX) for i in range(0,2): d=ts_readreg(TS_CHARGE_I_F) d*=66.667/32768 if d <= ci_max: return d*1000 return 0
def ts_arrayv(): av_max = portname = config_get("status", "array_v_max", DEFAULT_AV_MAX) for i in range(0,2): d=ts_readreg(TS_ARRAY_V_F) d*=139.15/32768 if d <= av_max: return d*1000 return 0
def init_serial(): """Initialises the serial port and connects to the tristar""" portname = config_get("status", "tristar_port", DEFAULT_TRISTAR_PORT) # Open the port p=[1, 0, 2301, 0, 13, 13] tty = SerialPort(portname, timeout=100, speed=9600, mode="232", params=p) return tty
def init_ca(): """Called during server startup to initialise the CA environment The CA must not be initialised until after the cfengine module so that the subversion repository is ready to use """ global certParams # Load the repository to check for a CA session_id = ADMIN_SESSION_ID session = getSession(session_id) changeset = session.changeset revision = ccs_revision(session, changeset); #If the CA does not exist, make it and add it to the repository if not revision.fileExists("ca/cacert.pem"): log_info("Creating CA for the certificates") siteName = config_get("network","site_name") if siteName == "": siteName = "CRCnet Default Site" wDir = revision.getWorkingDir() (fdi, fdo) = os.popen2("openssl req -new -x509 -nodes -keyout "\ "%s/ca/cakey.pem -out %s/ca/cacert.pem -days 3650 2>&1"\ % (wDir,wDir)) fdi.write("NZ\n") #country fdi.write(".\n") #ignore state of province fdi.write(".\n") #ignore location fdi.write("%s\n" % siteName) #Organisation fdi.write("ccsd\n") #Organisation Unit fdi.write("Certification Authority\n") #Common Name fdi.write(".\n") #ignore email fdo.close() fdi.close() time.sleep(1) revision.checkin("Added CA", ["%s/ca/cacert.pem" % (wDir),\ "%s/ca/cakey.pem" % (wDir)]) # Load the CA try: ca = ccs_ca() certParams = ca.getCAParameters() except ccs_ca_error: (type, value, tb) = sys.exc_info() log_fatal("CA: Unable to initialise: %s" % value, \ (type, value, tb)) # Ensure there is a server key, and a client key for the web interface # and the pxeboot scripts for name in ["server", "ccsweb", "pxe-scripts"]: if not ca.ensureCertificateExists(name): log_fatal("CA: %s is a required key. Exiting!" % name)
def returnPage(request, title, content, menuadd=None, scripts=[], styles=[]): """Returns a basic HTML page using the default template. title - The desired page title content - An HTML blob to populate the content area of the page menuadd - An HTML blob to append to the end of the generated menus The template must allow substitution of the tokens %TITLE%, %CONTENT%, %MENU%, %SCRIPTS%, %STYLES% in the appropriate places. """ # Get the template resourcedir = config_get("www", "resourcedir", DEFAULT_RESOURCE_DIR) tfile = "%s/page.html" % resourcedir try: fd = open(tfile, "r") template = fd.read() fd.close() except: log_error("Page template not available!", sys.exc_info()) # Return a very cruddy basic page template = "<html><head><title>%TITLE%</title></head>" \ "<body><h2>Menu</h2><br />%MENU%<br /><hr />" \ "<h2>%TITLE%</h2><br />%CONTENT%</body></html>" # Substitute as necessary menustr = buildMenu() if menuadd is not None: menustr += menuadd scriptstr = stylestr = "" for script in scripts: scriptstr += """<script type="text/javascript" src="%s">""" \ "</script>\n""" % script for style in styles: stylestr += """<link rel="stylesheet" type="text/css" src="%s""" \ " />\n""" % style footer = "Page generated at: %s" % time.ctime() ip = getIfaceIPForIP(request.client_address[0]) if ip == "": ip = request.headers.get("Host").strip() host = "http://%s" % ip output = template.replace("%SCRIPTS%", scriptstr).replace("%STYLES%", \ stylestr).replace("%TITLE%", title).replace("%MENU%", \ menustr).replace("%CONTENT%", content).replace("%FOOTER%", \ footer).replace("%HOST%", host) # Send it away request.send_response(200) request.end_headers() request.wfile.write(output) request.finish()
def addHostStatus(eventName, host_id, session_id, **params): """New host needs to be added to the monitor""" global _statusInfo, _runStatus #Only add if thread is running if _runStatus: host = ccs_host(session_id, host_id) name = host["host_name"] #Only add if host is active and not already in the list if host._properties["host_active"] and name not in _statusInfo: interval = config_get("status", "interval", DEFAULT_CHECK_INTERVAL) _statusInfo[name] = ccs_host_status(ADMIN_SESSION_ID, \ host_id, interval)
def contactpage(request, method): output = "" # Get the template resourcedir = config_get("www", "resourcedir", DEFAULT_RESOURCE_DIR) tfile = "%s/contact.html" % resourcedir try: fd = open(tfile, "r") details = fd.read() fd.close() except: log_error("Contact HTML not available!", sys.exc_info()) # Return a very cruddy basic page details = "No contact details available!" output += """<div class="content">%s</div>""" % details returnPage(request, "Contact Details", output)
def initConfFile(self): """Initialises the CA configuration file""" signdays = config_get("ca", "signdays", DEFAULT_SIGN_DAYS) site_name = config_get_required("network", "site_name") domain = config_get_required("network", "domain") fd = open("%s/ca.cnf" % self.rDir, "w") fd.write("""# # OpenSSL configuration file for the CRCnet Configuration System CA # This definition stops the following lines choking if HOME isn't # defined. HOME = . RANDFILE = $ENV::HOME/.rnd #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = $ENV::CCS_CA_DIR # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. new_certs_dir = $dir/certs # default place for new certs. certificate = $dir/cacert.pem # The CA certificate private_key = $dir/cakey.pem # The private key serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number crl = $dir/crl.pem # The current CRL RANDFILE = $dir/.rand # private random number file x509_extensions = usr_cert # The extentions to add to the cert name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options default_days = %s # how long to certify for default_crl_days = 30 # how long before next CRL default_md = sha1 # which md to use. preserve = no # keep passed DN ordering policy = policy_match # For the CA policy [ policy_match ] countryName = match stateOrProvinceName = optional localityName = optional organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional #################################################################### [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # Extensions to add to self signed certs string_mask = nombstr [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = NZ countryName_min = 2 countryName_max = 2 localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) 0.organizationName_default = %s organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = CRCnet Configuration System commonName = Common Name (eg, YOUR name) commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name # These extensions are added when 'ca' signs a request. [ usr_cert ] basicConstraints = CA:FALSE # nsCertType = server # nsCertType = client # This will be displayed in Netscape's comment listbox. nsComment = "Signed by the CRCnet Configuration System" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer nsRevocationUrl = https://%s/certs/crl.pem # Extensions to add to a certificate request [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment # Extensions for a typical CA [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always basicConstraints = CA:true # CRL extensions. [ crl_ext ] authorityKeyIdentifier = keyid:always,issuer:always """ % (signdays, site_name, domain)) fd.close() client.svn_client_add("%s/ca.cnf" % self.rDir, False, \ self.ctx, self.pool) log_info("CA: Initialised configuration file") return
def ifAccountThread(): """Runs as a thread to account from traffic on an interface""" global _accountingInfo, _runIfAccount, nas_id, nas_ip global radius_update_interval, radius_acct_server, radius_auth_server try: # Is interface accounting enabled enabled = config_getboolean("accounting", "enabled", True) if not enabled: log_info("Interface accounting disabled.") return _runIfAccount = True # What interval shall we check hosts at check_interval = config_get("accounting", "check_interval", DEFAULT_CHECK_INTERVAL) radius_update_interval = config_getint("accounting", "update_interval", DEFAULT_UPDATE_INTERVAL) # Initialise the interface list default_user_file = "%s/accounting_users" % os.path.dirname(DEFAULT_CONFFILE) user_file = config_get("accounting", "user_file", default_user_file) if not os.path.exists(user_file): log_error("Interface accounting disabled. No user file: %s" % user_file) _runIfAccount = False return # Initialise the RADIUS connection try: dummy0 = getInterfaces(returnOne="dummy0")[0] dummy0ip = dummy0["address"].split("/")[0] except: log_error("Could not determine host loopback address!", sys.exc_info()) dummy0ip = "127.0.0.1" acct_server = config_get("accounting", "acct_server", "radius") acct_secret = config_get_required("accounting", "acct_secret") auth_server = config_get("accounting", "auth_server", "radius") auth_secret = config_get_required("accounting", "auth_secret") nas_id = config_get("accounting", "nas_id", getFQDN()) nas_ip = config_get("accounting", "nas_ip", dummy0ip) radius_acct_server = Client(server=acct_server, secret=acct_secret, dict=Dictionary(RADIUS_DICTIONARY)) radius_auth_server = Client(server=auth_server, secret=auth_secret, dict=Dictionary(RADIUS_DICTIONARY)) # FreeRADIUS at least auths based on IP address, make sure our # packets come from the right place radius_acct_server.bind((nas_ip, 0)) radius_auth_server.bind((nas_ip, 0)) # Read and parse the user file parseUserFile(user_file) # Initialise interface state initialiseInterfaceState() # Loop forever reading byte counters as appropriate while _runIfAccount: # wait a bit before checking time.sleep(check_interval) # Send any queued packets processRADIUSQueue() # Try and re-authenticate any dead interfaces for ifname, iface in _accountingInfo.items(): if iface["authenticated"]: continue age = time.time() - iface["last_auth_check"] if age > radius_update_interval: doRADIUSAuthentication(ifname) # Update traffic details updateTrafficCounters() # Generate interim-updates processInterimUpdates() except: (etype, value, tb) = sys.exc_info() log_error("Exception in interface accounting thread! - %s" % value, (etype, value, tb)) log_info("Exiting interface accounting thread")
def getMOTD(request, method): """Returns HTML to display the Message of the Day""" tmpdir = config_get(None, "tmpdir", DEFAULT_TMPDIR) motdFile = "%s/motd" % tmpdir motdRefreshInterval = config_get("www", "motd_refresh", \ DEFAULT_MOTD_REFRESH) motdURL = config_get("www", "motdURL", DEFAULT_MOTD_URL) fetchMotd = config_getboolean("www", "fetch_motd", False) updateNote = "" try: mtime = os.stat(motdFile)[8] except: mtime = -1 if (mtime == -1 or time.time()-mtime > motdRefreshInterval or \ request.query.find("refreshMotd=true") != -1) and fetchMotd: # Get new MOTD try: o = urllib.URLopener() o.addheaders = [("User-agent", "crcnet-monitor/%s (r%s)" % \ (ccsd_version, ccsd_revision))] wfd = o.open(motdURL) fd = open(motdFile, "w") motd = wfd.read() fd.write(motd) fd.close wfd.close() mtime = time.time() except: log_error("Unable to fetch MOTD", sys.exc_info()) motd = "Unable to retrieve latest news." mtime = -1 else: try: fd = open(motdFile, "r") motd = fd.read() fd.close() except: motd = "No news available" # Calculate how long till next update if mtime != -1: updateAtSecs = (mtime + motdRefreshInterval) - time.time() updateAt = formatTime(updateAtSecs) retrieved = time.ctime(mtime) updateNote = "Retrieved at %s, next update in %s" % (retrieved, updateAt) # Generate the output output = """<h2>Latest News <span class="note">%s <a href="/?refreshMotd=true">[Refresh Now]</a> </span> </h2><br /> %s """ % (updateNote, motd.replace("\n", "<br />")) length = len(output) request.send_response(200) request.send_header("Length", length) request.end_headers() request.wfile.write(output) request.finish() return