def check_published_manifest(service, dbn, manifest_name): """ Used for checking that a manifest is already published in the install service specified. Checks to make sure manifest exists in the install service's DB, and that the manifest also exists in the install service's published files area. Args: service - service object for service dbn - dbn object of install service to check against. manifest_name - name of manifest to check. Postconditions: None Returns: True if manifest exists in install service False if manifest does not exist. """ # Check if manifest exists in the service's criteria DB. if AIdb.sanitizeSQL(manifest_name) not in AIdb.getManNames(dbn.getQueue()): print( _("Error: install service does not contain the specified " "manifest: %s") % manifest_name) return False # Check if manifest file exists in the service's published area. published_path = os.path.join(service.manifest_dir, manifest_name) if not os.path.exists(published_path): print( _("Error: manifest missing from published area: %s") % published_path) return False return True
def check_published_manifest(service, dbn, manifest_name): """ Used for checking that a manifest is already published in the install service specified. Checks to make sure manifest exists in the install service's DB, and that the manifest also exists in the install service's published files area. Args: service - service object for service dbn - dbn object of install service to check against. manifest_name - name of manifest to check. Postconditions: None Returns: True if manifest exists in install service False if manifest does not exist. """ # Check if manifest exists in the service's criteria DB. if AIdb.sanitizeSQL(manifest_name) not in AIdb.getManNames(dbn.getQueue()): print(_("Error: install service does not contain the specified " "manifest: %s") % manifest_name) return False # Check if manifest file exists in the service's published area. published_path = os.path.join(service.manifest_dir, manifest_name) if not os.path.exists(published_path): print(_("Error: manifest missing from published area: %s") % published_path) return False return True
def is_name_in_table(name, queue, table): ''' Determine if profile already registered for service and same basename Args: name - profile name queue - database queue for profiles table - profile table name Returns True if any records are found, False if no records found ''' query_str = "SELECT * FROM %s WHERE name='%s'" % \ (table, AIdb.sanitizeSQL(name)) query = AIdb.DBrequest(query_str) queue.put(query) query.waitAns() return len(query.getResponse()) > 0
def is_name_in_table(name, queue, table): ''' Determine if profile already registered for service and same basename Args: name - profile name queue - database queue for profiles table - profile table name Returns True if any records are found, False if no records found ''' query_str = "SELECT * FROM %s WHERE name='%s'" % \ (table, AIdb.sanitizeSQL(name)) query = AIdb.DBrequest(query_str) queue.put(query) query.waitAns() return len(query.getResponse()) > 0
def format_value(crit, value): """ Format's a value (for use with set_criteria()) based on its criteria type. Args: crit - the criteria name. value - the value to format. Returns: Formatted value for (used by set_criteria()) to use in a string to query the install service's DB. """ # For the value "unbounded", we store this as "NULL" in the DB. if value == "unbounded": return "NULL" else: formatted_val = "'" + AIdb.sanitizeSQL(str(value).upper()) + "'" # If its the "mac" criteria, must add a special hex operand if crit == "mac": return "x" + formatted_val return formatted_val
def delete_manifest_from_db(db, manifest_instance, service_name, data_loc): """ Remove manifest from DB """ instance = manifest_instance[1] # check to see that the manifest is found in the database (as entered) if manifest_instance[0] not in AIdb.getManNames(db.getQueue()): # since all manifest names have to have .xml appended try adding that if manifest_instance[0] + '.xml' in AIdb.getManNames(db.getQueue()): man_name = manifest_instance[0] + '.xml' else: raise SystemExit(_("Error:\tManifest %s not found in database!" % manifest_instance[0])) else: man_name = manifest_instance[0] service = AIService(service_name) # Do not delete if this manifest is set up as the default. if man_name == service.get_default_manifest(): raise ValueError(_("Error:\tCannot delete default manifest %s.") % man_name) # if we do not have an instance remove the entire manifest if instance is None: # remove manifest from database query = AIdb.DBrequest("DELETE FROM manifests WHERE name = '%s'" % AIdb.sanitizeSQL(man_name), commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # clean up file on file system try: os.remove(os.path.join(service.manifest_dir, man_name)) except OSError: print >> sys.stderr, _("Warning:\tUnable to find file %s for " + "removal!") % man_name # we are removing a specific instance else: # check that the instance number is within bounds for that manifest # (0..numInstances) if instance > AIdb.numInstances(man_name, db.getQueue()) or \ instance < 0: raise SystemExit(_("Error:\tManifest %(name)s has %(num)i " "instances" % {'name': man_name, 'num': AIdb.numInstances(man_name, db.getQueue())})) # remove instance from database query = ("DELETE FROM manifests WHERE name = '%s' AND " "instance = '%i'") % (AIdb.sanitizeSQL(man_name), instance) query = AIdb.DBrequest(query, commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # We may need to reshuffle manifests to prevent gaps in instance # numbering as the DB routines expect instances to be contiguous and # increasing. We may have removed an instance with instances numbered # above thus leaving a gap. # get the number of instances with a larger instance for num in range(instance, AIdb.numInstances(man_name, db.getQueue()) + 1): # now decrement the instance number query = ("UPDATE manifests SET instance = '%i' WHERE " "name = '%s' ") % (num - 1, AIdb.sanitizeSQL(man_name)) query += "AND instance = '%i'" % num query = AIdb.DBrequest(query, commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # remove file if manifest is no longer in database if man_name not in AIdb.getManNames(db.getQueue()): try: os.remove(os.path.join(service.manifest_dir, man_name)) except OSError: print >> sys.stderr, _("Warning: Unable to find file %s for " + "removal!") % man_name
def send_manifest(form_data, port=0, servicename=None, protocolversion=COMPATIBILITY_VERSION, no_default=False): '''Replies to the client with matching service for a service. Args form_data - the postData passed in from the client request port - the port of the old client servicename - the name of the service being used protocolversion - the version of the AI service RE: handshake no_default - boolean flag to signify whether or not we should hand back the default manifest and profiles if one cannot be matched based on the client criteria. Returns None Raises None ''' # figure out the appropriate path for the AI database, # and get service name if necessary. # currently service information is stored in a port directory. # When the cherrypy webserver new service directories should be # separated via service-name only. Old services will still use # port numbers as the separation mechanism. path = None found_servicename = None service = None port = str(port) if servicename: service = AIService(servicename) path = service.database_path else: for name in config.get_all_service_names(): if config.get_service_port(name) == port: found_servicename = name service = AIService(name) path = service.database_path break # Check to insure that a valid path was found if not path or not os.path.exists(path): print 'Content-Type: text/html' # HTML is following print # blank line, end of headers if servicename: print '<pre><b>Error</b>:unable to find<i>', servicename + '</i>.' else: print '<pre><b>Error</b>:unable to find<i>', port + '</i>.' print 'Available services are:<p><ol><i>' hostname = socket.gethostname() for name in config.get_all_service_names(): port = config.get_service_port(name) sys.stdout.write( '<a href="http://%s:%d/cgi-bin/' 'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' % (hostname, port, VERSION, name, name)) print '</i></ol>Please select a service from the above list.' return if found_servicename: servicename = found_servicename # load to the AI database aisql = AIdb.DB(path) aisql.verifyDBStructure() # convert the form data into a criteria dictionary criteria = dict() orig_data = form_data while form_data: try: [key_value, form_data] = form_data.split(';', 1) except (ValueError, NameError, TypeError, KeyError): key_value = form_data form_data = '' try: [key, value] = key_value.split('=') criteria[key] = value except (ValueError, NameError, TypeError, KeyError): criteria = dict() # Generate templating dictionary from criteria template_dict = dict() for crit in criteria: template_dict["AI_" + crit.upper()] = \ AIdb.formatValue(crit, criteria[crit], units=False) # find the appropriate manifest try: manifest = AIdb.findManifest(criteria, aisql) except StandardError as err: print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre><b>Error</b>:findManifest criteria<br>' print err, '<br>' print '<ol>servicename =', servicename print 'port =', port print 'path =', path print 'form_data =', orig_data print 'criteria =', criteria print 'servicename found by port =', found_servicename, '</ol>' print '</pre>' return # check if findManifest() returned a number equal to 0 # (means we got no manifests back -- thus we serve the default if desired) if manifest is None and not no_default: manifest = service.get_default_manifest() # if we have a manifest to return, prepare its return if manifest is not None: try: # construct the fully qualified filename filename = os.path.abspath( os.path.join(service.manifest_dir, manifest)) # open and read the manifest with open(filename, 'rb') as mfp: manifest_str = mfp.read() # maintain compability with older AI client if servicename is None or \ float(protocolversion) < float(PROFILES_VERSION): content_type = mimetypes.types_map.get('.xml', 'text/plain') print 'Content-Length:', len( manifest_str) # Length of the file print 'Content-Type:', content_type # XML is following print # blank line, end of headers print manifest_str logging.info('Manifest sent from %s.' % filename) return except OSError as err: print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre>' # report the internal error to error_log and requesting client sys.stderr.write(_('error:manifest (%s) %s\n') % \ (str(manifest), err)) sys.stdout.write(_('error:manifest (%s) %s\n') % \ (str(manifest), err)) print '</pre>' return # get AI service image path service = AIService(servicename) image_dir = service.image.path # construct object to contain MIME multipart message outermime = MIMEMultipart() client_msg = list() # accumulate message output for AI client # If we have a manifest, attach it to the return message if manifest is not None: # add manifest as attachment msg = MIMEText(manifest_str, 'xml') # indicate manifest using special name msg.add_header('Content-Disposition', 'attachment', filename=sc.AI_MANIFEST_ATTACHMENT_NAME) outermime.attach(msg) # add manifest as an attachment # search for any profiles matching client criteria # formulate database query to profiles table q_str = "SELECT DISTINCT name, file FROM " + \ AIdb.PROFILES_TABLE + " WHERE " nvpairs = list() # accumulate criteria values from post-data # for all AI client criteria for crit in AIdb.getCriteria(aisql.getQueue(), table=AIdb.PROFILES_TABLE, onlyUsed=False): if crit not in criteria: msgtxt = _("Warning: client criteria \"%s\" not provided in " "request. Setting value to NULL for profile lookup.") \ % crit client_msg += [msgtxt] logging.warn(msgtxt) # fetch only global profiles destined for all clients if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): nvpairs += ["MIN" + crit + " IS NULL"] nvpairs += ["MAX" + crit + " IS NULL"] else: nvpairs += [crit + " IS NULL"] continue # prepare criteria value to add to query envval = AIdb.sanitizeSQL(criteria[crit]) if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): # If no default profiles are requested, then we mustn't allow # this criteria to be NULL. It must match the client's given # value for this criteria. if no_default: if crit == "mac": nvpairs += ["(HEX(MIN" + crit + ")<=HEX(X'" + envval + \ "'))"] nvpairs += ["(HEX(MAX" + crit + ")>=HEX(X'" + envval + \ "'))"] else: nvpairs += ["(MIN" + crit + "<='" + envval + "')"] nvpairs += ["(MAX" + crit + ">='" + envval + "')"] else: if crit == "mac": nvpairs += [ "(MIN" + crit + " IS NULL OR " "HEX(MIN" + crit + ")<=HEX(X'" + envval + "'))" ] nvpairs += [ "(MAX" + crit + " IS NULL OR HEX(MAX" + crit + ")>=HEX(X'" + envval + "'))" ] else: nvpairs += [ "(MIN" + crit + " IS NULL OR MIN" + crit + "<='" + envval + "')" ] nvpairs += [ "(MAX" + crit + " IS NULL OR MAX" + crit + ">='" + envval + "')" ] else: # If no default profiles are requested, then we mustn't allow # this criteria to be NULL. It must match the client's given # value for this criteria. # # Also, since this is a non-range criteria, the value stored # in the DB may be a whitespace separated list of single # values. We use a special user-defined function in the # determine if the given criteria is in that textual list. if no_default: nvpairs += ["(is_in_list('" + crit + "', '" + envval + \ "', " + crit + ", 'None') == 1)"] else: nvpairs += ["(" + crit + " IS NULL OR is_in_list('" + crit + \ "', '" + envval + "', " + crit + ", 'None') == 1)"] if len(nvpairs) > 0: q_str += " AND ".join(nvpairs) # issue database query logging.info("Profile query: " + q_str) query = AIdb.DBrequest(q_str) aisql.getQueue().put(query) query.waitAns() if query.getResponse() is None or len(query.getResponse()) == 0: msgtxt = _("No profiles found.") client_msg += [msgtxt] logging.info(msgtxt) else: for row in query.getResponse(): profpath = row['file'] profname = row['name'] if profname is None: # should not happen profname = 'unnamed' try: if profpath is None: msgtxt = "Database record error - profile path is " \ "empty." client_msg += [msgtxt] logging.error(msgtxt) continue msgtxt = _('Processing profile %s') % profname client_msg += [msgtxt] logging.info(msgtxt) with open(profpath, 'r') as pfp: raw_profile = pfp.read() # do any template variable replacement {{AI_xxx}} tmpl_profile = sc.perform_templating( raw_profile, template_dict) # precautionary validation of profile, logging only sc.validate_profile_string(tmpl_profile, image_dir, dtd_validation=True, warn_if_dtd_missing=True) except IOError as err: msgtxt = _("Error: I/O error: ") + str(err) client_msg += [msgtxt] logging.error(msgtxt) continue except OSError: msgtxt = _("Error: OS error on profile ") + profpath client_msg += [msgtxt] logging.error(msgtxt) continue except KeyError: msgtxt = _('Error: could not find criteria to substitute ' 'in template: ') + profpath client_msg += [msgtxt] logging.error(msgtxt) logging.error('Profile with template substitution error:' + raw_profile) continue except lxml.etree.XMLSyntaxError as err: # log validation error and proceed msgtxt = _( 'Warning: syntax error found in profile: ') \ + profpath client_msg += [msgtxt] logging.error(msgtxt) for error in err.error_log: msgtxt = _('Error: ') + error.message client_msg += [msgtxt] logging.error(msgtxt) logging.info([ _('Profile failing validation: ') + lxml.etree.tostring(root) ]) # build MIME message and attach to outer MIME message msg = MIMEText(tmpl_profile, 'xml') # indicate in header that this is an attachment msg.add_header('Content-Disposition', 'attachment', filename=profname) # attach this profile to the manifest and any other profiles outermime.attach(msg) msgtxt = _('Parsed and loaded profile: ') + profname client_msg += [msgtxt] logging.info(msgtxt) # any profiles and AI manifest have been attached to MIME message # specially format list of messages for display on AI client console if client_msg: outtxt = '' for msgtxt in client_msg: msgtxt = _('SC profile locator:') + msgtxt outtxt += str(msgtxt) + '\n' # add AI client console messages as single plain text attachment msg = MIMEText(outtxt, 'plain') # create MIME message outermime.attach(msg) # attach MIME message to response print outermime.as_string() # send MIME-formatted message
def insert_SQL(files): """ Ensures all data is properly sanitized and formatted, then inserts it into the database Args: None Returns: None """ query = "INSERT INTO manifests VALUES(" # add the manifest name to the query string query += "'" + AIdb.sanitizeSQL(files.manifest_name) + "'," # check to see if manifest name is already in database (affects instance # number) if AIdb.sanitizeSQL(files.manifest_name) in \ AIdb.getManNames(files.database.getQueue()): # database already has this manifest name get the number of # instances instance = AIdb.numInstances(AIdb.sanitizeSQL(files.manifest_name), files.database.getQueue()) # this a new manifest else: instance = 0 # actually add the instance to the query string query += str(instance) + "," # we need to fill in the criteria or NULLs for each criteria the database # supports (so iterate over each criteria) for crit in AIdb.getCriteria(files.database.getQueue(), onlyUsed=False, strip=False): # for range values trigger on the MAX criteria (skip the MIN's # arbitrary as we handle rows in one pass) if crit.startswith('MIN'): continue # get the values from the manifest values = files.criteria[crit.replace('MAX', '', 1)] # If the critera manifest didn't specify this criteria, fill in NULLs if values is None: # use the criteria name to determine if this is a range if crit.startswith('MAX'): query += "NULL,NULL," # this is a single value else: query += "NULL," # Else if this is a value criteria (not a range), insert the value # as a space-separated list of values which will account for the case # where a list of values have been given. elif not crit.startswith('MAX'): # Join the values of the list with a space separator. query += "'" + AIdb.sanitizeSQL(" ".join(values)) + "'," # else values is a range else: for value in values: # translate "unbounded" to a database NULL if value == "unbounded": query += "NULL," # we need to deal with mac addresses specially being # hexadecimal elif crit.endswith("mac"): # need to insert with hex operand x'<val>' # use an upper case string for hex values query += "x'" + AIdb.sanitizeSQL(str(value).upper()) + \ "'," else: query += AIdb.sanitizeSQL(str(value).upper()) + "," # strip trailing comma and close parentheses query = query[:-1] + ")" # update the database query = AIdb.DBrequest(query, commit=True) files.database.getQueue().put(query) query.waitAns() # in case there's an error call the response function (which will print the # error) query.getResponse()
def set_criteria(criteria, iname, dbn, table, append=False): """ Set a manifest's record in the criteria database with the criteria provided. If append is True -- append ones that aren't already set for the manifest, and replace ones that are. if append is False -- completely remove all criteria already set for the manifest, and use only the criteria specified. """ # Build a list of criteria nvpairs to update nvpairs = list() # we need to fill in the criteria or NULLs for each criteria the database # supports (so iterate over each criteria) for crit in AIdb.getCriteria(dbn.getQueue(), table=table, onlyUsed=False, strip=True): # Determine if this crit is a range criteria or not. is_range_crit = AIdb.isRangeCriteria(dbn.getQueue(), crit) # Get the value from the manifest values = criteria[crit] # the criteria manifest didn't specify this criteria if values is None: # If we not appending criteria, then we must write in NULLs # for this criteria since we're removing all criteria not # specified. if not append: # if the criteria we're processing is a range criteria, fill in # NULL for two columns, MINcrit and MAXcrit if is_range_crit: nvpairs.append("MIN" + crit + "=NULL") nvpairs.append("MAX" + crit + "=NULL") # this is a single value else: nvpairs.append(crit + "=NULL") # Else if this is a value criteria (not a range), insert the # value as a space-separated list of values in case a list of # values have been given. elif not is_range_crit: nvstr = crit + "='" + AIdb.sanitizeSQL(" ".join(values)) + "'" nvpairs.append(nvstr) # Else the values are a list this is a range criteria else: # Set the MIN column for this range criteria nvpairs.append("MIN" + crit + "=" + AIdb.format_value(crit, values[0])) # Set the MAX column for this range criteria nvpairs.append("MAX" + crit + "=" + AIdb.format_value(crit, values[1])) query = "UPDATE " + table + " SET " + ",".join(nvpairs) + \ " WHERE name='" + iname + "'" # update the DB query = AIdb.DBrequest(query, commit=True) dbn.getQueue().put(query) query.waitAns() # in case there's an error call the response function (which # will print the error) query.getResponse()
def insert_SQL(files): """ Ensures all data is properly sanitized and formatted, then inserts it into the database Args: None Returns: None """ query = "INSERT INTO manifests VALUES(" # add the manifest name to the query string query += "'" + AIdb.sanitizeSQL(files.manifest_name) + "'," # check to see if manifest name is already in database (affects instance # number) if AIdb.sanitizeSQL(files.manifest_name) in \ AIdb.getManNames(files.database.getQueue()): # database already has this manifest name get the number of # instances instance = AIdb.numInstances(AIdb.sanitizeSQL(files.manifest_name), files.database.getQueue()) # this a new manifest else: instance = 0 # actually add the instance to the query string query += str(instance) + "," # we need to fill in the criteria or NULLs for each criteria the database # supports (so iterate over each criteria) for crit in AIdb.getCriteria(files.database.getQueue(), onlyUsed=False, strip=False): # for range values trigger on the MAX criteria (skip the MIN's # arbitrary as we handle rows in one pass) if crit.startswith('MIN'): continue # get the values from the manifest values = files.criteria[crit.replace('MAX', '', 1)] # If the critera manifest didn't specify this criteria, fill in NULLs if values is None: # use the criteria name to determine if this is a range if crit.startswith('MAX'): query += "NULL,NULL," # this is a single value else: query += "NULL," # Else if this is a value criteria (not a range), insert the value # as a space-separated list of values which will account for the case # where a list of values have been given. elif not crit.startswith('MAX'): # Join the values of the list with a space separator. query += "'" + AIdb.sanitizeSQL(" ".join(values)) + "'," # else values is a range else: for value in values: # translate "unbounded" to a database NULL if value == "unbounded": query += "NULL," # we need to deal with mac addresses specially being # hexadecimal elif crit.endswith("mac"): # need to insert with hex operand x'<val>' # use an upper case string for hex values query += "x'" + AIdb.sanitizeSQL(str(value).upper()) + \ "'," else: query += AIdb.sanitizeSQL(str(value).upper()) + "," # strip trailing comma and close parentheses query = query[:-1] + ")" # update the database query = AIdb.DBrequest(query, commit=True) files.database.getQueue().put(query) query.waitAns() # in case there's an error call the response function (which will print the # error) query.getResponse()
def set_criteria(criteria, iname, dbn, table, append=False): """ Set a manifest's record in the criteria database with the criteria provided. If append is True -- append ones that aren't already set for the manifest, and replace ones that are. if append is False -- completely remove all criteria already set for the manifest, and use only the criteria specified. """ # Build a list of criteria nvpairs to update nvpairs = list() # we need to fill in the criteria or NULLs for each criteria the database # supports (so iterate over each criteria) for crit in AIdb.getCriteria(dbn.getQueue(), table=table, onlyUsed=False, strip=True): # Determine if this crit is a range criteria or not. is_range_crit = AIdb.isRangeCriteria(dbn.getQueue(), crit) # Get the value from the manifest values = criteria[crit] # the criteria manifest didn't specify this criteria if values is None: # If we not appending criteria, then we must write in NULLs # for this criteria since we're removing all criteria not # specified. if not append: # if the criteria we're processing is a range criteria, fill in # NULL for two columns, MINcrit and MAXcrit if is_range_crit: nvpairs.append("MIN" + crit + "=NULL") nvpairs.append("MAX" + crit + "=NULL") # this is a single value else: nvpairs.append(crit + "=NULL") # Else if this is a value criteria (not a range), insert the # value as a space-separated list of values in case a list of # values have been given. elif not is_range_crit: nvstr = crit + "='" + AIdb.sanitizeSQL(" ".join(values)) + "'" nvpairs.append(nvstr) # Else the values are a list this is a range criteria else: # Set the MIN column for this range criteria nvpairs.append("MIN" + crit + "=" + AIdb.format_value(crit, values[0])) # Set the MAX column for this range criteria nvpairs.append("MAX" + crit + "=" + AIdb.format_value(crit, values[1])) query = "UPDATE " + table + " SET " + ",".join(nvpairs) + \ " WHERE name='" + iname + "'" # update the DB query = AIdb.DBrequest(query, commit=True) dbn.getQueue().put(query) query.waitAns() # in case there's an error call the response function (which # will print the error) query.getResponse()
def send_manifest(form_data, port=0, servicename=None, protocolversion=COMPATIBILITY_VERSION, no_default=False): '''Replies to the client with matching service for a service. Args form_data - the postData passed in from the client request port - the port of the old client servicename - the name of the service being used protocolversion - the version of the AI service RE: handshake no_default - boolean flag to signify whether or not we should hand back the default manifest and profiles if one cannot be matched based on the client criteria. Returns None Raises None ''' # figure out the appropriate path for the AI database, # and get service name if necessary. # currently service information is stored in a port directory. # When the cherrypy webserver new service directories should be # separated via service-name only. Old services will still use # port numbers as the separation mechanism. path = None found_servicename = None service = None port = str(port) if servicename: service = AIService(servicename) path = service.database_path else: for name in config.get_all_service_names(): if config.get_service_port(name) == port: found_servicename = name service = AIService(name) path = service.database_path break # Check to insure that a valid path was found if not path or not os.path.exists(path): print 'Content-Type: text/html' # HTML is following print # blank line, end of headers if servicename: print '<pre><b>Error</b>:unable to find<i>', servicename + '</i>.' else: print '<pre><b>Error</b>:unable to find<i>', port + '</i>.' print 'Available services are:<p><ol><i>' hostname = socket.gethostname() for name in config.get_all_service_names(): port = config.get_service_port(name) sys.stdout.write('<a href="http://%s:%d/cgi-bin/' 'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' % (hostname, port, VERSION, name, name)) print '</i></ol>Please select a service from the above list.' return if found_servicename: servicename = found_servicename # load to the AI database aisql = AIdb.DB(path) aisql.verifyDBStructure() # convert the form data into a criteria dictionary criteria = dict() orig_data = form_data while form_data: try: [key_value, form_data] = form_data.split(';', 1) except (ValueError, NameError, TypeError, KeyError): key_value = form_data form_data = '' try: [key, value] = key_value.split('=') criteria[key] = value except (ValueError, NameError, TypeError, KeyError): criteria = dict() # Generate templating dictionary from criteria template_dict = dict() for crit in criteria: template_dict["AI_" + crit.upper()] = \ AIdb.formatValue(crit, criteria[crit], units=False) # find the appropriate manifest try: manifest = AIdb.findManifest(criteria, aisql) except StandardError as err: print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre><b>Error</b>:findManifest criteria<br>' print err, '<br>' print '<ol>servicename =', servicename print 'port =', port print 'path =', path print 'form_data =', orig_data print 'criteria =', criteria print 'servicename found by port =', found_servicename, '</ol>' print '</pre>' return # check if findManifest() returned a number equal to 0 # (means we got no manifests back -- thus we serve the default if desired) if manifest is None and not no_default: manifest = service.get_default_manifest() # if we have a manifest to return, prepare its return if manifest is not None: try: # construct the fully qualified filename filename = os.path.abspath(os.path.join(service.manifest_dir, manifest)) # open and read the manifest with open(filename, 'rb') as mfp: manifest_str = mfp.read() # maintain compability with older AI client if servicename is None or \ float(protocolversion) < float(PROFILES_VERSION): content_type = mimetypes.types_map.get('.xml', 'text/plain') print 'Content-Length:', len(manifest_str) # Length of the file print 'Content-Type:', content_type # XML is following print # blank line, end of headers print manifest_str logging.info('Manifest sent from %s.' % filename) return except OSError as err: print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre>' # report the internal error to error_log and requesting client sys.stderr.write(_('error:manifest (%s) %s\n') % \ (str(manifest), err)) sys.stdout.write(_('error:manifest (%s) %s\n') % \ (str(manifest), err)) print '</pre>' return # get AI service image path service = AIService(servicename) image_dir = service.image.path # construct object to contain MIME multipart message outermime = MIMEMultipart() client_msg = list() # accumulate message output for AI client # If we have a manifest, attach it to the return message if manifest is not None: # add manifest as attachment msg = MIMEText(manifest_str, 'xml') # indicate manifest using special name msg.add_header('Content-Disposition', 'attachment', filename=sc.AI_MANIFEST_ATTACHMENT_NAME) outermime.attach(msg) # add manifest as an attachment # search for any profiles matching client criteria # formulate database query to profiles table q_str = "SELECT DISTINCT name, file FROM " + \ AIdb.PROFILES_TABLE + " WHERE " nvpairs = list() # accumulate criteria values from post-data # for all AI client criteria for crit in AIdb.getCriteria(aisql.getQueue(), table=AIdb.PROFILES_TABLE, onlyUsed=False): if crit not in criteria: msgtxt = _("Warning: client criteria \"%s\" not provided in " "request. Setting value to NULL for profile lookup.") \ % crit client_msg += [msgtxt] logging.warn(msgtxt) # fetch only global profiles destined for all clients if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): nvpairs += ["MIN" + crit + " IS NULL"] nvpairs += ["MAX" + crit + " IS NULL"] else: nvpairs += [crit + " IS NULL"] continue # prepare criteria value to add to query envval = AIdb.sanitizeSQL(criteria[crit]) if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): # If no default profiles are requested, then we mustn't allow # this criteria to be NULL. It must match the client's given # value for this criteria. if no_default: if crit == "mac": nvpairs += ["(HEX(MIN" + crit + ")<=HEX(X'" + envval + \ "'))"] nvpairs += ["(HEX(MAX" + crit + ")>=HEX(X'" + envval + \ "'))"] else: nvpairs += ["(MIN" + crit + "<='" + envval + "')"] nvpairs += ["(MAX" + crit + ">='" + envval + "')"] else: if crit == "mac": nvpairs += ["(MIN" + crit + " IS NULL OR " "HEX(MIN" + crit + ")<=HEX(X'" + envval + "'))"] nvpairs += ["(MAX" + crit + " IS NULL OR HEX(MAX" + crit + ")>=HEX(X'" + envval + "'))"] else: nvpairs += ["(MIN" + crit + " IS NULL OR MIN" + crit + "<='" + envval + "')"] nvpairs += ["(MAX" + crit + " IS NULL OR MAX" + crit + ">='" + envval + "')"] else: # If no default profiles are requested, then we mustn't allow # this criteria to be NULL. It must match the client's given # value for this criteria. # # Also, since this is a non-range criteria, the value stored # in the DB may be a whitespace separated list of single # values. We use a special user-defined function in the # determine if the given criteria is in that textual list. if no_default: nvpairs += ["(is_in_list('" + crit + "', '" + envval + \ "', " + crit + ", 'None') == 1)"] else: nvpairs += ["(" + crit + " IS NULL OR is_in_list('" + crit + \ "', '" + envval + "', " + crit + ", 'None') == 1)"] if len(nvpairs) > 0: q_str += " AND ".join(nvpairs) # issue database query logging.info("Profile query: " + q_str) query = AIdb.DBrequest(q_str) aisql.getQueue().put(query) query.waitAns() if query.getResponse() is None or len(query.getResponse()) == 0: msgtxt = _("No profiles found.") client_msg += [msgtxt] logging.info(msgtxt) else: for row in query.getResponse(): profpath = row['file'] profname = row['name'] if profname is None: # should not happen profname = 'unnamed' try: if profpath is None: msgtxt = "Database record error - profile path is " \ "empty." client_msg += [msgtxt] logging.error(msgtxt) continue msgtxt = _('Processing profile %s') % profname client_msg += [msgtxt] logging.info(msgtxt) with open(profpath, 'r') as pfp: raw_profile = pfp.read() # do any template variable replacement {{AI_xxx}} tmpl_profile = sc.perform_templating(raw_profile, template_dict) # precautionary validation of profile, logging only sc.validate_profile_string(tmpl_profile, image_dir, dtd_validation=True, warn_if_dtd_missing=True) except IOError as err: msgtxt = _("Error: I/O error: ") + str(err) client_msg += [msgtxt] logging.error(msgtxt) continue except OSError: msgtxt = _("Error: OS error on profile ") + profpath client_msg += [msgtxt] logging.error(msgtxt) continue except KeyError: msgtxt = _('Error: could not find criteria to substitute ' 'in template: ') + profpath client_msg += [msgtxt] logging.error(msgtxt) logging.error('Profile with template substitution error:' + raw_profile) continue except lxml.etree.XMLSyntaxError as err: # log validation error and proceed msgtxt = _( 'Warning: syntax error found in profile: ') \ + profpath client_msg += [msgtxt] logging.error(msgtxt) for error in err.error_log: msgtxt = _('Error: ') + error.message client_msg += [msgtxt] logging.error(msgtxt) logging.info([_('Profile failing validation: ') + lxml.etree.tostring(root)]) # build MIME message and attach to outer MIME message msg = MIMEText(tmpl_profile, 'xml') # indicate in header that this is an attachment msg.add_header('Content-Disposition', 'attachment', filename=profname) # attach this profile to the manifest and any other profiles outermime.attach(msg) msgtxt = _('Parsed and loaded profile: ') + profname client_msg += [msgtxt] logging.info(msgtxt) # any profiles and AI manifest have been attached to MIME message # specially format list of messages for display on AI client console if client_msg: outtxt = '' for msgtxt in client_msg: msgtxt = _('SC profile locator:') + msgtxt outtxt += str(msgtxt) + '\n' # add AI client console messages as single plain text attachment msg = MIMEText(outtxt, 'plain') # create MIME message outermime.attach(msg) # attach MIME message to response print outermime.as_string() # send MIME-formatted message
def set_criteria(criteria, manifest_name, db, append=False): """ Set a manifest's record in the criteria database with the criteria provided. If append is True -- append ones that aren't already set for the manifest, and replace ones that are. if append is False -- completely remove all criteria already set for the manifest, and use only the criteria specified. """ # Build a list of criteria nvpairs to update nvpairs = list() # we need to fill in the criteria or NULLs for each criteria the database # supports (so iterate over each criteria) for crit in AIdb.getCriteria(db.getQueue(), onlyUsed=False, strip=True): # Get the value from the manifest values = criteria[crit] # the critera manifest didn't specify this criteria if values is None: # If we not appending criteria, then we must write in NULLs # for this criteria since we're removing all criteria not # specified. if not append: # if the criteria we're processing is a range criteria, fill in # NULL for two columns, MINcrit and MAXcrit if AIdb.isRangeCriteria(db.getQueue(), crit): nvpairs.append("MIN" + crit + "=NULL") nvpairs.append("MAX" + crit + "=NULL") # this is a single value else: nvpairs.append(crit + "=NULL") # this is a single criteria (not a range) elif isinstance(values, basestring): # translate "unbounded" to a database NULL if values == "unbounded": nvstr = crit + "=NULL" else: # use lower case for text strings nvstr = crit + "='" + AIdb.sanitizeSQL(str(values).lower()) \ + "'" nvpairs.append(nvstr) # Else the values are a list this is a range criteria else: # Set the MIN column for this range criteria nvpairs.append("MIN" + crit + "=" + format_value(crit, values[0])) # Set the MAX column for this range criteria nvpairs.append("MAX" + crit + "=" + format_value(crit, values[1])) query = "UPDATE manifests SET " + ",".join(nvpairs) + \ " WHERE name='" + manifest_name + "'" # update the DB query = AIdb.DBrequest(query, commit=True) db.getQueue().put(query) query.waitAns() # in case there's an error call the response function (which # will print the error) query.getResponse()