def render(self, request): session = getSession(ADMIN_SESSION_ID) host_name = os.path.basename(request.path) #XXX: Verify MAC format here log_debug("Received hostlookup request for %s" % host_name) # Lookup mac sql = "SELECT h.host_name, h.ip_address, am.asset_id, am.mac FROM " \ "host h LEFT JOIN asset_macs am ON h.asset_id=" \ "am.asset_id WHERE h.host_name=%s LIMIT 1" try: res = session.query(sql, (host_name.strip())) except: (type, value, tb) = sys.exc_info() log_warn("Could not lookup host!", sys.exc_info()) request.setResponseCode(500, "Database query failed - %s" % \ value) request.finish() return server.NOT_DONE_YET if len(res)!=1: request.setResponseCode(404, "Specified Host not found!") request.finish() return server.NOT_DONE_YET # Write out info about the host rs = "FOUND=1\nASSET_ID=%s\nHOST_NAME=%s\nHOST_IP=%s\nMAC=%s\n" % \ (res[0]["asset_id"], res[0]["host_name"], \ res[0]["ip_address"], res[0]["mac"]) return rs
def __init__(self, session_id=None): # Setup parameters expected by the base class if session_id is None: session_id = ADMIN_SESSION_ID self.mParentSession = getSession(session_id) self.mChangeset = self.mParentSession.changeset # Call base constructor ccs_revision.__init__(self, self.mParentSession, self.mChangeset, \ False) # Setup a working directory for this revision self.rDir = mkdtemp("", "ccsd") # Checkout the current configuration HEAD to this directory try: rev = core.svn_opt_revision_t() rev.kind = core.svn_opt_revision_head client.svn_client_checkout("%s/ca" % self.svnroot, self.rDir, \ rev, True, self.ctx, self.pool) self.mCurRev = rev except core.SubversionException: # CA not initialised raise ccs_ca_error("infrastructure not found in repository!") # Check basic repository structure if self.mParentSession is not None and self.mChangeset is not None: self.checkRepoStructure() # Start with no errors self.mErrors = {}
def __init__(self, session_id, host_id): """Initialises a new class for a specified host. The specified session must be valid and have appropriate access to the database for the tasks you intend to perform with the class. All database access / configuration manipulation triggered by this instance will pass through the specified session. """ self._errMsg = "" self._commit = 0 self._csInit = "" session = getSession(session_id) if session is None: raise ccs_host_error("Invalid session id") self._session_id = session_id # See if the specified host id makes sense sql = "SELECT * FROM host WHERE host_id=%s" res = session.query(sql, (host_id)) if len(res) < 1: raise ccs_host_error("Invalid host. Unable to retrieve " "details") # Store details self.host_id = host_id self._properties = res[0]
def doRollover(type): """Handles resetting customer caps when their usage rolls over""" session = getSession(ADMIN_SESSION_ID) sql = "SELECT * FROM rollover WHERE cap_period=%s" res = session.query(sql, (type)) for ro in res: try: # Update the broadband_account table to reset the cap sql = "UPDATE broadband_account SET plan_start=CURRENT_TIMESTAMP, "\ "cap_at_mb=%s, used_mb=0, radius_plan=%s WHERE login_id=%s" session.execute(sql, (ro["included_mb"], ro["radius_plan"], \ ro["login_id"])) except: log_error("Failed to reset cap and state for contact #%s" % \ ro["login_id"], sys.exc_info()) #Reset the warning sent flags try: sql = "UPDATE broadband_account SET warning_sent=false where exists " \ "(select billing_plan.plan_id, billing_plan.cap_period from " \ "billing_plan where broadband_account.plan_id = " \ "billing_plan.plan_id AND billing_plan.cap_period=%s)" session.execute(sql, (type)) except: log_error("Failed to reset warning_sent field")
def forgotPassword(username): """Emails the user a new password when they have forgotten it""" session = getSession(ADMIN_SESSION_ID) username, domain = username.split('@') contact = session.query("SELECT l.*, a.admin_id, a.email, fl.enabled AS flstate FROM " \ "(logins l INNER JOIN admins a ON a.login_id=l.login_id) LEFT JOIN first_login fl ON l.login_id=" \ "fl.login_id WHERE username=%s AND domain=%s", (username, domain)) if len(contact) != 1: log_warn("New pasword request for unknown user: %s@%s" % (username, domain)) return 0 # create a password plain = createPassword(8) salt = createSalt() passwd = crypt.crypt(plain, salt) # Update the password session.execute("UPDATE logins SET passwd=%s, enabled=1 WHERE " \ "login_id=%s", (passwd, contact[0]['login_id'])) if contact[0]["flstate"] is None or contact[0]["flstate"]=="": sql = "INSERT INTO first_login (login_id, enabled) VALUES " \ "(%s, %s)" session.execute(sql, (contact[0]["login_id"], contact[0]["enabled"])) # Send email try: _sendPasswordEmail(contact[0]["email"], plain) except: log_warn("Unable to send forgotten password notification!", \ sys.exc_info()) log_info("New password emailed to %s@%s" % (username, domain)) return 0
def __init__(self, session_id, site_id): """Initialises a new class for a specified site. The specified session must be valid and have appropriate access to the database for the tasks you intend to perform with the class. All database access / configuration manipulation triggered by this instance will pass through the specified session. """ self._errMsg = "" self._commit = 0 self._csInit = "" session = getSession(session_id) if session is None: raise ccs_site_error("Invalid session specified!") self._session_id = session_id # See if the specified site_id id makes sense try: sql = "SELECT * FROM site WHERE site_id=%s" res = session.query(sql, (site_id)) except: raise ccs_site_error("Unable to retrieve site details!") # Store details self.site_id = site_id self._properties = res[0]
def __init__(self, session_id, link_id): """Initialises a new class for a specified link. Parent must be a reference to the core crcnetd class. The specified session must be valid and have appropriate access to the database for the tasks you intend to perform with the class. All database access / configuration manipulation triggered by this instance will pass through the specified session. """ self._errMsg = "" self._commit = 0 self._csInit = "" session = getSession(session_id) if session is None: raise ccs_interface_error("Invalid session id") self._session_id = session_id # See if the specified link id makes sense sql = "SELECT * FROM link WHERE link_id=%s" res = session.query(sql, (link_id)) if len(res) != 1: # DB Query failed raise ccs_link_error("Invalid link. Unable to retrieve " "details") # Store details self._properties = res[0] self.link_id = link_id
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 checkUsage(*args, **kwargs): """Check customer data usage. Perform cap actions and warnings as necessary""" session = getSession(ADMIN_SESSION_ID) #Do warnings first to ensure that a warning email in generated sql = "SELECT cs.*, c.* FROM broadband_account cs, customers c WHERE " \ "cs.login_id=c.login_id AND cs.used_mb > cs.cap_at_mb*0.8 " \ "AND cs.warning_sent=false AND cs.charged=true" res = session.query(sql, ()) for state in res: doCapWarn(state) #Do any cap ations required sql = "SELECT cs.*, c.* FROM broadband_account cs, v_customers c WHERE " \ "cs.login_id=c.login_id AND cs.used_mb > cs.cap_at_mb" res = session.query(sql, ()) for state in res: doCapAction(state)
def __init__(self, session_id, service_id): """Initialises a new class for a specified service. The specified session must be valid and have appropriate access to the database for the tasks you intend to perform with the class. All database access / configuration manipulation triggered by this instance will pass through the specified session. """ self._errMsg = "" self._commit = 0 self._csInit = "" self._service = {} self._structure = {} session = getSession(session_id) if session is None: raise ccs_service_error("Invalid session id") self._session_id = session_id # See if the specified service id makes sense sql = "SELECT * FROM service WHERE service_id=%s" res = session.query(sql, (service_id)) if len(res) < 1: raise ccs_service_error("Invalid service. Unable to retrieve " "details") # Store details if not hasattr(self, "service_id"): self.service_id = service_id if not hasattr(self, "serviceName"): self.serviceName = res[0]["service_name"] self._service = res[0] self._properties = {} # Read service property structure & data res = session.query("SELECT * FROM service_prop WHERE service_id=%s", \ (self.service_id)) for prop in res: prop["value"] = None prop["network_value"] = None self._properties[prop["prop_name"]] = prop
def doCapWarn(customer): session = getSession(ADMIN_SESSION_ID) sql = "SELECT cap_action, cap_param, cap_price, description FROM " \ "billing_plan WHERE plan_id=%s" res = session.query(sql, (customer["plan_id"])) sql = "UPDATE broadband_account SET warning_sent='t' WHERE " \ "login_id=%s" session.execute(sql,(customer["login_id"])) subject = "Data Cap Notice: 80% used" if res[0]["cap_action"] == "throttle": sendCapEmail(customer, subject, "throttle-warn") elif res[0]["cap_action"] == "purchase": customer["purchase_mb"] = res[0]["cap_param"] customer["purchase_price"] = "$%.2f" % (res[0]["cap_price"]*1.125) sendCapEmail(customer, subject, "purchase-warn") else: log_error("Unknown cap action '%s' for contact '%s'" % \ (res[0]["cap_action"], customer['username']))
def __init__(self, session_id, host_id, interval=-1): """Initialises a new class for a specified host. The specified session must be valid and have appropriate access to the database. """ self.lock = threading.RLock() session = getSession(session_id) if session is None: raise ccs_host_error("Invalid session id") self._session_id = session_id # See if the specified host id makes sense self.host = ccs_host(session_id, host_id) self._hostname = self.host["host_name"] self._ip = self.host["ip_address"] # Initialise the status information self.reachable = False self.operatingRevision = -1 self.operatingRevisionStatus = STATUS_UNKNOWN self.operatingRevisionText = "Unknown" self.operatingRevisionHint = "Status not yet fetched" self.currentLoad = (-1,-1,-1) self.uptime = 0 self.infoUpdatedAt = 0 self.lastCheckedAt = time.time() self.interval = interval self.attempt = 0 # Retrieve locally available information self.activeRevision = getActiveRevision(self._hostname) self.generatedRevision = getGeneratedRevision(self._hostname) # Will this object be updated? if interval != -1: ccs_host_status.queue(time.time(), self) log_info("Initialised host status monitor for %s" % self._hostname)
def doBilling(as_at=None): """Called on the monthly event to charge the customer for their plan The monthly event is called on the last day of the month, so we add one to the current date to report the month that we're billing the customer for. """ session = getSession(ADMIN_SESSION_ID) print "DOING BILLING" # Generate a billing record for each plan that is active in the system sql = "SELECT * FROM rollover" res = session.query(sql, ()) n = timedelta(1) # Select all unbilled records up till NOW or the time specified, # date the invoice one day earlier if as_at is None: datestr = datetime.strftime(datetime.now() + n, "%b %Y") db_time = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S") else: datestr = datetime.fromtimestamp(as_at).strftime("%b %Y") db_time = datetime.fromtimestamp(as_at).strftime("%Y-%m-%d %H:%M:%S") for ro in res: if ro['charged'] == 'f': continue sql = "INSERT INTO billing_record (customer_id, record_date, " \ "description, amount, quantity, record_type) VALUES ((select customer_id from customers where login_id=%s), %s, " \ "%s, %s, 1, 'Plan');" desc = "%s: %s" % (datestr, ro["description"]) try: session.execute(sql, (ro["login_id"], db_time, desc, ro["price"])) except: log_error("Failed to generate billing record for contact #%s" % \ ro["login_id"], sys.exc_info())
def registerService(res_class, **kwargs): """Function to register a service The service must be derived from ccsd_service. The service must have at least one private member called serviceName that provides a textual description of the service. Other private members as described in the ccsd_service class must also be present. """ session = getSession(ADMIN_SESSION_ID) try: name = res_class.serviceName if name in _services: raise ccs_service_error("Service name already registed!") # Check it is a child of ccsd_service if ccsd_service not in inspect.getmro(res_class): raise ccs_service_error("Invalid service class type!") # Check it has an entry in the service table res = session.query("SELECT * FROM service WHERE service_name=%s", \ (name)) if len(res) != 1: # Call the services setup function res_class.initialiseService() else: # Store the service ID in the class for reference res_class.service_id = res[0]["service_id"] # Register the service log_info("Registering service '%s'." % name) _services[name] = res_class except: log_error("Failed to register service!", sys.exc_info())
def doCapAction(customer): """Called when a customer has exceeded their cap. Rate limits or purchases more data for the customer, based on their chosen plan. """ session = getSession(ADMIN_SESSION_ID) sql = "SELECT cap_action, cap_param, cap_price, description FROM " \ "billing_plan WHERE plan_id=%s" res = session.query(sql, (customer["plan_id"])) used_mb = int(customer["used_mb"]) subject = "Data Cap Notice: %.1fGB used." % (used_mb/1024.0) if res[0]["cap_action"] == "throttle": # Customer has chosen to be throttled current_plan = customer["radius_plan"] if current_plan == res[0]["cap_param"]: # Already throttled return else: # Throttle and notify sql = "UPDATE broadband_account SET radius_plan=%s WHERE " \ "login_id=%s" session.execute(sql,(res[0]["cap_param"], customer["login_id"])) log_info("Moved '%s' to plan %s (throttled)" % \ (customer["username"], res[0]["description"])) # Tell the customer sendCapEmail(customer, subject, "throttle") elif res[0]["cap_action"] == "purchase": # Customer has chosen to purchase more data cap_at_mb = customer["cap_at_mb"] used_mb = customer["used_mb"] # If the customer is leeching at full rate, it's possible that we may # have to purchase multiple extra blocks of usage to get their cap # back above their current data usage. extra_mb = 0 extra_price = 0 while cap_at_mb < used_mb: try: sql = "UPDATE broadband_account SET cap_at_mb=cap_at_mb+%s " \ "WHERE login_id=%s" session.execute(sql,(res[0]["cap_param"], customer["login_id"])) sql = "INSERT INTO billing_record (customer_id, record_date, " \ "description, amount, quantity,record_type) VALUES (%%s, " \ "CURRENT_TIMESTAMP, '%sMB data pack', %%s, 1, 'Data')" % \ (res[0]["cap_param"]) session.execute(sql, (customer["customer_id"], res[0]["cap_price"])) cap_at_mb += res[0]["cap_param"] log_info("Purchased %sMB block for '%s'. New cap: %sMB." % \ (res[0]["cap_param"], customer["username"], cap_at_mb)) extra_mb += res[0]["cap_param"] extra_price += res[0]["cap_price"] except: log_error("Failed to purchase new block for '%s'!" % \ customer["username"], sys.exc_info()) break # Tell the customer customer["purchased_mb"] = extra_mb customer["purchased_price"] = "$%.2f" % (extra_price*1.125) sendCapEmail(customer, subject, "purchase") else: log_error("Unknown cap action '%s' for contact '%s'" % \ (res[0]["cap_action"], customer['username']))
def validateLink(session_id, details, isAdd=False): """Validates that the specified link details are correct""" # Link ID if "link_id" in details.keys(): validateLinkId(session_id, details["link_id"]) elif not isAdd: raise ccs_link_error("Link ID must be specified!") # Required props if isAdd: rprops = ["description", "type", "mode", "class_id", "network_address", "shared_network"] for prop in rprops: if prop not in details.keys(): raise ccs_link_error("%s is a required link property!" % prop) # Check values if "description" in details.keys(): if len(details["description"]) <= 0: raise ccs_link_error("Description must be specified!") if len(details["description"]) > 250: raise ccs_link_error("Description must be less than 250 chars!") if "mode" in details.keys() and "type" in details.keys(): validateLinkMode(session_id, details["type"], details["mode"]) if "class_id" in details.keys(): validateLinkClassId(session_id, details["class_id"]) if "type" in details.keys() and details["type"] in WIRELESS_LINK_TYPES: if "essid" in details.keys(): if len(details["essid"]) <= 0: raise ccs_link_error("ESSID must be specified!") if len(details["essid"]) > 64: raise ccs_link_error("ESSID must be less than 64 chars!") essid_valid = re.compile("[a-zA-z0-9\-]*") if essid_valid.match(details["essid"]) == None: raise ccs_link_error("Invalid character in ESSID!") if "channel" in details.keys(): try: c = int(details["channel"]) if c == 0: raise ccs_link_error("Channel must not be 0!") except: raise ccs_link_error("Invalid channel specified!") if "key" in details.keys() and details["key"] != "": if len(details["key"]) != 32: raise ccs_link_error("Key must be 32 characters!") key_valid = re.compile("[a-zA-z0-9]*") if key_valid.match(details["key"]) == None: raise ccs_link_error("Invalid character in key!") if "network_address" in details.keys() and len(details["network_address"]) > 0: validateCIDR(details["network_address"]) session = getSession(session_id) if session is None: raise ccs_interface_error("Invalid session id") # Must ensure that no two links can have same ip range sql = "SELECT link_id FROM link WHERE network_address=%s" p = [details["network_address"]] if "link_id" in details.keys(): sql += " AND link_id != %s" p.append(details["link_id"]) res = session.query(sql, p) if len(res) != 0: raise ccs_link_error("Network address already in used!") if "shared_network" in details.keys(): if details["shared_network"] not in ["t", "f"]: raise ccs_link_error("Shared Network flag must be 't' or 'f'!") # All OK return