]> git.ipfire.org Git - oddments/fireinfo.git/blobdiff - fireinfo/system.py
Add some new "secret_id" and make it readable from file.
[oddments/fireinfo.git] / fireinfo / system.py
index 6a19d98322785e3f6a7b8c023e42deffe6fa462d..a4fff57e765e96f96ef88a003f308c4e2897ad8b 100644 (file)
 #!/usr/bin/python
 
-
+import hashlib
 import json
 import os
 import string
 
+import _fireinfo
+
 import cpu
 import device
+import hypervisor
+
+PROFILE_VERSION = 0
+
+SYS_CLASS_DMI = "/sys/class/dmi/id"
+SECRET_ID_FILE = "/etc/fireinfo-id"
+
+class Singleton(type):
+       def __init__(cls, name, bases, dict):
+               super(Singleton, cls).__init__(name, bases, dict)
+               cls.instance = None
+
+       def __call__(cls, *args, **kw):
+               if cls.instance is None:
+                       cls.instance = super(Singleton, cls).__call__(*args, **kw)
+
+               return cls.instance
+
+
+def read_from_file(filename):
+       """
+               Read all data from filename.
+       """
+       if not os.path.exists(filename):
+               return
+
+       try:
+               with open(filename) as f:
+                       return f.read().strip()
+       except IOError:
+               pass
 
 class System(object):
+       __metaclass__ = Singleton
 
        def __init__(self):
                # find all devices
                self.devices = []
                self.scan()
                self.cpu = cpu.CPU()
-       
+               self.hypervisor = hypervisor.Hypervisor()
+
        def profile(self):
-               p = {
-                       "public_id" : self.public_id,
-                       "private_id" : self.private_id,
+               p = {}
+               p["system"] = {
+                       # System information
+                       "model"  : self.model,
+                       "vendor" : self.vendor,
+
+                       # Indicator if the system is running in a
+                       # virtual environment.
+                       "virtual" : self.virtual,
                        
-                       "arch" : self.arch,
+                       # System language
                        "language" : self.language,
+
+                       # Release information
                        "release" : self.release,
+                       "kernel_release" : self.kernel_release,
+
                        "memory" : self.memory,
-                       "kernel" : self.kernel,
                        "root_size" : self.root_size,
-                       "devices" : [],
-                       
-                       }
-                       
+               }
+
+               p["devices"] = []
                for device in self.devices:
-                       p["devices"].append({
+                       d = {
                                "subsystem" : device.subsystem.lower(), 
                                "vendor" : device.vendor.lower(), 
                                "model" : device.model.lower(), 
-                               "deviceclass" : device.deviceclass
-                       })
-               
+                               "deviceclass" : device.deviceclass,
+                               "driver" : device.driver,
+                       }
+
+                       # PCI devices provide subsystem information, USB don't.
+                       if d["subsystem"] == "pci":
+                               d["sub_model"] = device.sub_model
+                               d["sub_vendor"] = device.sub_vendor
+
+                       p["devices"].append(d)
+
                p["cpu"] = {
+                       "arch" : self.arch,
                        "vendor" : self.cpu.vendor,
                        "model" : self.cpu.model,
+                       "model_string" : self.cpu.model_string,
                        "stepping" : self.cpu.stepping,
                        "flags" : self.cpu.flags,
                        "bogomips" : self.cpu.bogomips,
                        "speed" : self.cpu.speed,
-                       "modes" : self.cpu.modes,
-                       "hypervisor" : self.cpu.hypervisor,
-                       "virtype" : self.cpu.virtype,
                        "family" : self.cpu.family,
                        "count" : self.cpu.count                                
                }
-                       
-               return json.dumps(p)
+
+               # Only append hypervisor information if we are virtualized.
+               if self.virtual:
+                       p["hypervisor"] = {
+                               "type"   : self.hypervisor.type,
+                               "vendor" : self.hypervisor.vendor,
+                       }
+
+               return {
+                       # Profile version
+                       "profile_version" : PROFILE_VERSION,
+
+                       # Identification and authorization codes
+                       "public_id" : self.public_id,
+                       "private_id" : self.private_id,
+
+                       # Actual profile data
+                       "profile" : p,
+               }
                                
                
        @property
@@ -62,15 +129,80 @@ class System(object):
 
        @property
        def public_id(self):
-               return "0"*40
-       
+               """
+                       This returns a globally (hopefully) ID to identify the host
+                       later (by request) in the database.
+               """
+               public_id = self.secret_id
+               if not public_id:
+                       return "0" * 40
+
+               return hashlib.sha1(public_id).hexdigest()
+
        @property
        def private_id(self):
-               return "1"*40
-       
+               """
+                       The private ID is built out of the _unique_id and used to
+                       permit a host to do changes on the database.
+
+                       No one could ever guess this without access to the host.
+               """
+               private_id = ""
+               for i in reversed(self.secret_id):
+                       private_id += i
+
+               if not private_id:
+                       return "0" * 40
+
+               return hashlib.sha1(private_id).hexdigest()
+
+       @property
+       def secret_id(self):
+               """
+                       Read a "secret" ID from a file if available
+                       or calculate it from the hardware.
+               """
+               if os.path.exists(SECRET_ID_FILE):
+                       return read_from_file(SECRET_ID_FILE)
+
+               return hashlib.sha1(self._unique_id).hexdigest()
+
+       @property
+       def _unique_id(self):
+               """
+                       This is a helper ID which is generated out of some hardware information
+                       that is considered to be constant over a PC's lifetime.
+
+                       None of the data here is ever sent to the server.
+               """
+               ids = []
+
+               # Virtual machines (for example) and some boards have a UUID
+               # which is globally unique.
+               for file in ("product_uuid", "product_serial", "chassis_serial"):
+                       id = read_from_file(os.path.join(SYS_CLASS_DMI, file)) or ""
+                       ids.append(id)
+
+               # Use serial number from root disk (if available)
+               root_disk_serial = self.root_disk_serial
+               if root_disk_serial:
+                       ids.append(root_disk_serial)
+
+               # As last resort, we use the UUID from pakfire.
+               if not ids:
+                       id = read_from_file("/opt/pakfire/db/uuid") or ""
+                       ids.append(id)
+
+               return "#".join(ids)
+
        @property
        def language(self):
-               with open("/var/ipfire/main/settings", "r") as f:
+               # Return "unknown" if settings file does not exist.
+               filename = "/var/ipfire/main/settings"
+               if not os.path.exists(filename):
+                       return "unknown"
+
+               with open(filename, "r") as f:
                        for line in f.readlines():
                                key, val = line.split("=", 1)
                                if key=="LANGUAGE":
@@ -78,17 +210,40 @@ class System(object):
 
        @property
        def release(self):
-               with open("/etc/system-release", "r") as f:
-                       return f.read().strip()
-               
+               return read_from_file("/etc/system-release")
+
+       @property
+       def bios_vendor(self):
+               return read_from_file("/sys/class/dmi/id/bios_vendor")
+
+       @property
+       def vendor(self):
+               ret = None
+               for file in ("chassis_vendor", "board_vendor", "sys_vendor",):
+                       ret = read_from_file(os.path.join(SYS_CLASS_DMI, file))
+                       if ret:
+                               break
+
+               return ret
+
+       @property
+       def model(self):
+               ret = None
+               for file in ("chassis_model", "board_model", "product_name",):
+                       ret = read_from_file(os.path.join(SYS_CLASS_DMI, file))
+                       if ret:
+                               break
+
+               return ret
+
        @property
        def memory(self):
                with open("/proc/meminfo", "r") as f:
                        firstline = f.readline().strip()
-                       return firstline.split()[1]
+                       return int(firstline.split()[1])
 
        @property
-       def kernel(self):
+       def kernel_release(self):
                return os.uname()[2]
 
        @property
@@ -109,7 +264,15 @@ class System(object):
                        return
                with open(path, "r") as f:
                        return int(f.readline())*512/1024
-                                       
+
+       @property
+       def root_disk_serial(self):
+               serial = _fireinfo.get_harddisk_serial("/dev/%s" % self.root_disk)
+
+               if serial:
+                       # Strip all spaces
+                       return serial.strip()
+
        def scan(self):
                toscan = (("/sys/bus/pci/devices", device.PCIDevice),
                ("/sys/bus/usb/devices", device.USBDevice))
@@ -117,8 +280,13 @@ class System(object):
                        dirlist = os.listdir(path)
                        for dir in dirlist:
                                self.devices.append(cls(os.path.join(path, dir)))
-                               
-                               
+
+       @property
+       def virtual(self):
+               """
+                       Say if the host is running in a virtual environment.
+               """
+               return self.hypervisor.virtual
 
                
 
@@ -127,14 +295,10 @@ if __name__ == "__main__":
        print s.arch
        print s.language
        print s.release
+       print s.bios_vendor
        print s.memory
        print s.kernel
        print s.root_disk
        print s.root_size
        print "------------\n", s.devices, "\n------------\n"
-       print s.profile()
-       
-       import urllib2
-       import urllib
-       r = urllib2.Request("http://192.168.10.101:9001/send/%s" %s.public_id, data = urllib.urlencode({"profile" : s.profile()}))
-       urllib2.urlopen(r)
\ No newline at end of file
+       print json.dumps(s.profile(), sort_keys=True, indent=4)