Package rekall :: Module config
[frames] | no frames]

Source Code for Module rekall.config

  1  #!/usr/bin/python 
  2   
  3  # Rekall 
  4  # Copyright (C) 2012 Michael Cohen <scudette@gmail.com> 
  5  # Copyright 2013 Google Inc. All Rights Reserved. 
  6  # 
  7  # This program is free software; you can redistribute it and/or modify 
  8  # it under the terms of the GNU General Public License as published by 
  9  # the Free Software Foundation; either version 2 of the License, or (at 
 10  # your option) any later version. 
 11  # 
 12  # This program is distributed in the hope that it will be useful, but 
 13  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 15  # General Public License for more details. 
 16  # 
 17  # You should have received a copy of the GNU General Public License 
 18  # along with this program; if not, write to the Free Software 
 19  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 20  # 
 21   
 22  """This is the Rekall configuration system. 
 23   
 24  Rekall maintains a persistent file with global settings in the user's home 
 25  directory. This makes it easy for users to retain commonly used Rekall 
 26  parameters. 
 27   
 28  Note that the configuration file is only used in interactive mode. When used as 
 29  a library the configuration file has no effect. 
 30  """ 
 31   
 32  __author__ = "Michael Cohen <scudette@gmail.com>" 
 33   
 34  import collections 
 35  import logging 
 36  import os 
 37  import sys 
 38  import tempfile 
 39  import yaml 
 40   
 41  from rekall import constants 
 42   
 43   
44 -class CommandMetadata(object):
45 """A class that carried a plugin's configuration. 46 47 A plugin is responsible for declaring its metadata by calling this 48 configuration object's methods from the args() class method. 49 50 There are two things that plugin must declare: 51 52 add_*_arg(): Calling these functions declares an argument for this 53 plugin. See the documentation for that method for details. 54 55 add_metadata(): This method provides additional metadata about this plugin. 56 """ 57
58 - def __init__(self, plugin_cls=None):
59 self.args = collections.OrderedDict() 60 self.requirements = set() 61 self.plugin_cls = plugin_cls 62 if plugin_cls: 63 plugin_cls.args(self) 64 65 self.description = (plugin_cls.__doc__ or 66 plugin_cls.__init__.__doc__ or "")
67
68 - def set_description(self, description):
70
71 - def add_positional_arg(self, name, type="string"):
72 """Declare a positional arg.""" 73 self.args[name] = dict(type=type)
74
75 - def add_argument(self, short_opt, long_opt=None, **options):
76 """Add a new argument to the command. 77 78 This method is used in the args() class method to add a new command line 79 arg to the plugin. It is similar to the argparse add_argument() method 80 but it adds a type parameter which conveys higher level information 81 about the argument. Currently supported types: 82 83 - ArrayIntParser: A list of integers (possibly encoded as hex strings). 84 - ArrayStringParser: A list of strings. 85 - Float: A float. 86 - IntParser: An integer (possibly encoded as a hex string). 87 - Boolean: A flag - true/false. 88 - ChoiceArray: A comma separated list of strings which must be from the 89 choices parameter. 90 """ 91 if "action" in options: 92 raise RuntimeError("Action keyword is deprecated.") 93 94 if not isinstance(options.get("type", ""), str): 95 raise RuntimeError("Type must be a string.") 96 97 # Is this a positional arg? 98 positional = options.pop("positional", False) 99 100 # For now we support option names with leading --. 101 if long_opt is None: 102 long_opt = short_opt 103 short_opt = "" 104 105 if long_opt.startswith("-"): 106 long_opt = long_opt.lstrip("-") 107 short_opt = short_opt.lstrip("-") 108 positional = False 109 110 name = long_opt 111 options["short_opt"] = short_opt 112 options["positional"] = positional 113 options["name"] = name 114 115 self.args[name] = options
116
117 - def add_requirement(self, requirement):
118 """Add a requirement for this plugin. 119 120 Currently supported requirements: 121 - profile: A profile must exist for this plugin to run. 122 123 - physical_address_space: A Physical Address Space (i.e. an image file) 124 must exist for this plugin to work. 125 """ 126 self.requirements.add(requirement)
127
128 - def Metadata(self):
129 return dict(requirements=list(self.requirements), 130 arguments=self.args.values(), name=self.plugin_cls.name, 131 description=self.description)
132
133 - def ApplyDefaults(self, args):
134 """Update args with the defaults. 135 136 If an option in args is None, we update it with the default value for 137 this option. 138 """ 139 for name, options in self.args.iteritems(): 140 if options.get("dest") == "SUPPRESS": 141 continue 142 143 name = name.replace("-", "_") 144 try: 145 if args[name] is None: 146 args[name] = options.get("default") 147 except KeyError: 148 pass 149 150 return args
151 152
153 -def GetHomeDir(session):
154 return ( 155 session.GetParameter("home", cached=False) or 156 os.environ.get("HOME") or # Unix 157 os.environ.get("USERPROFILE") or # Windows 158 tempfile.gettempdir() or # Fallback tmp dir. 159 ".")
160 161 162 # This is the configuration file template which will be created if the user does 163 # not have an existing file. The aim is not to exhaustively list all possible 164 # options, rather to ensure that reasonable defaults are specified initially. 165 DEFAULT_CONFIGURATION = dict( 166 repository_path=constants.PROFILE_REPOSITORIES, 167 168 # This is the path of the cache directory - given relative to the config 169 # file (or it can be specified as an absolute path). 170 cache_dir=".rekall_cache", 171 ) 172 173 # Global options control the framework's own flags. They are not associated with 174 # any one plugin. 175 OPTIONS = CommandMetadata() 176 177
178 -def GetConfigFile(session):
179 """Gets the configuration stored in the config file. 180 181 Searches for the config file in reasonable locations. 182 183 Return: 184 configuration stored in the config file. If the file is not found, returns 185 an empty configuration. 186 """ 187 search_path = [ 188 # Next to the main binary (in case of pyinstaller - rekall.exe). 189 os.path.join(os.path.dirname(sys.executable), ".rekallrc"), 190 ".rekallrc", # Current directory. 191 os.path.join(GetHomeDir(session), ".rekallrc"), # Home directory overrides system. 192 "/etc/rekallrc", 193 ] 194 195 for path in search_path: 196 try: 197 with open(path, "rb") as fd: 198 result = yaml.safe_load(fd) 199 logging.debug("Loaded configuration from %s", path) 200 201 # Allow the config file to update the 202 # environment. This is handy in standalone deployment 203 # where one can update %HOME% and ensure Rekall does 204 # not touch the drive. 205 os.environ.update(result.get("environment", {})) 206 207 return result 208 209 except (IOError, ValueError): 210 pass 211 212 return {}
213 214
215 -def CreateDefaultConfigFile(session):
216 """Creates a default config file.""" 217 homedir = GetHomeDir(session) 218 if homedir: 219 try: 220 filename = "%s/.rekallrc" % homedir 221 with open(filename, "wb") as fd: 222 yaml.dump(DEFAULT_CONFIGURATION, fd) 223 224 logging.info("Created new configuration file %s", filename) 225 cache_dir = os.path.join( 226 homedir, DEFAULT_CONFIGURATION["cache_dir"]) 227 228 os.makedirs(cache_dir) 229 logging.info("Created new cache directory %s", cache_dir) 230 231 return DEFAULT_CONFIGURATION 232 except (IOError, OSError): 233 pass 234 235 # Can not write it anywhere but at least we start with something sensible. 236 return DEFAULT_CONFIGURATION
237 238
239 -def MergeConfigOptions(state, session):
240 """Read the config file and apply the config options to the session.""" 241 config_data = GetConfigFile(session) 242 # An empty configuration file - we try to initialize a new one. 243 if not config_data: 244 config_data = CreateDefaultConfigFile(session) 245 246 # First apply the defaults: 247 for name, options in OPTIONS.args.iteritems(): 248 if name not in config_data: 249 config_data[name] = options.get("default") 250 251 for k, v in config_data.items(): 252 state.Set(k, v)
253 254
255 -def RemoveGlobalOptions(state):
256 """Remove all global options from state dictionary.""" 257 state.pop("SUPPRESS", None) 258 259 for name in OPTIONS.args: 260 state.pop(name, None) 261 262 return state
263 264
265 -def DeclareOption(*args, **kwargs):
266 """Declare a config option for command line and config file.""" 267 # Options can not be positional! 268 kwargs["positional"] = False 269 default = kwargs.get("default") 270 if default is not None and isinstance(default, str): 271 kwargs["default"] = unicode(default) 272 273 OPTIONS.add_argument(*args, **kwargs)
274