Package config_common :: Module repository
[hide private]
[frames] | no frames]

Source Code for Module config_common.repository

  1  # 
  2  # Copyright (c) 2008--2016 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15   
 16  import re 
 17  import os 
 18  import pwd 
 19  import grp 
 20  import sys 
 21  import stat 
 22  import base64 
 23  from config_common import cfg_exceptions 
 24  from config_common import local_config 
 25  from config_common import utils 
 26  from config_common.rhn_log import log_debug 
 27  from rhn import rpclib 
 28  Output = rpclib.transports.Output 
 29   
 30  try: # python2 
 31      PY3 = False 
 32      import xmlrpclib 
 33  except ImportError: # python3 
 34      PY3 = True 
 35      import xmlrpc.client as xmlrpclib 
 36      basestring = (str, bytes) 
 37   
 38  from spacewalk.common.usix import raise_with_tb 
 39   
 40  try: 
 41      from selinux import lgetfilecon 
 42  except: 
 43      # on rhel4 we do not support selinux 
44 - def lgetfilecon(path):
45 return [0, '']
46 47 #6/29/05 rpc_wrapper implements the failover logic. 48 from config_common import rpc_wrapper 49 rpclib = rpc_wrapper 50 51
52 -def deci_to_octal(number):
53 """convert a normal decimal int to another int representing the octal value""" 54 # python 2.4/2.6: oct(420) -> '0644' 55 # python 3.4: oct(420) -> '0o644' 56 return int(oct(number).replace("o", ""))
57 58
59 -class Repository:
60 _uid_cache = {} 61 _gid_cache = {} 62 _local_config = local_config 63
64 - def __init__(self):
65 self.default_delimiters = None 66 self.maximum_file_size = None
67 68 # Helpers 69 70 # Unless overridden in a subclass, per-file delimiters are the same as the 71 # global delimiters
72 - def get_file_delimiters(self, file):
73 "returns the default delimiters for this file" 74 return self.get_default_delimiters()
75
76 - def get_default_delimiters(self):
77 "returns the default delimiters" 78 if self.default_delimiters is None: 79 self.default_delimiters = self._get_default_delimiters() 80 return self.default_delimiters
81
82 - def _get_default_delimiters(self):
83 raise NotImplementedError
84
85 - def get_maximum_file_size(self):
86 "returns the maximum file size" 87 if self.maximum_file_size is None: 88 self.maximum_file_size = self._get_maximum_file_size() 89 return self.maximum_file_size
90
91 - def _get_maximum_file_size(self):
92 "To be overwritten in subclasses" 93 return 1024
94
95 - def make_stat_info(self, path, file_stat):
96 # Returns the stat information as required by the API 97 ret = {} 98 fields = { 99 'mode' : stat.ST_MODE, 100 'user' : stat.ST_UID, 101 'group' : stat.ST_GID, 102 'size' : stat.ST_SIZE, 103 'mtime' : stat.ST_MTIME, 104 'ctime' : stat.ST_CTIME, 105 } 106 for label, st in fields.items(): 107 ret[label] = file_stat[st] 108 109 # server expects things like 644, 700, etc. 110 ret['mode'] = deci_to_octal(ret['mode'] & int('07777', 8)) 111 112 #print ret['size'] 113 #if ret['size'] > self.get_maximum_file_size(): 114 # die(4, "File %s exceeds the maximum file size (%s)" % 115 # (path, ret['size'])) 116 117 uid = ret['user'] 118 gid = ret['group'] 119 120 pw_name = self._uid_cache.get(uid) 121 if not pw_name: 122 try: 123 pw_name = pwd.getpwuid(uid)[0] 124 except KeyError: 125 print("Error looking up user id %s" % (uid, )) 126 127 if pw_name: 128 ret['user'] = pw_name 129 self._uid_cache[uid] = pw_name 130 131 gr_name = self._gid_cache.get(gid) 132 if not gr_name: 133 try: 134 gr_name = grp.getgrgid(gid)[0] 135 except KeyError: 136 print("Error looking up group id %s" % (gid, )) 137 138 if gr_name: 139 ret['group'] = gr_name 140 self._gid_cache[gid] = gr_name 141 142 # if selinux is disabled or on RHEL4 we do not send the selinux_ctx 143 # flag at all - see bug 644985 - SELinux context cleared from 144 # RHEL4 rhncfg-client 145 try: 146 selinux_ctx = lgetfilecon(path)[1] 147 except OSError: 148 selinux_ctx = '' 149 if selinux_ctx == None: 150 selinux_ctx = '' 151 152 ret['selinux_ctx'] = selinux_ctx 153 154 return ret
155
156 - def _make_file_info(self, remote_path, local_path=None, delim_start=None, 157 delim_end=None, load_contents=1):
158 if not local_path: 159 # Safe enough to assume local path is the same as the remote one 160 local_path = remote_path 161 162 try: 163 file_stat = os.lstat(local_path) 164 except OSError: 165 e = sys.exc_info()[1] 166 raise_with_tb(cfg_exceptions.RepositoryLocalFileError( 167 "Error lstat()-ing local file: %s" % e), sys.exc_info()[2]) 168 169 # Dlimiters 170 if delim_start or delim_end: 171 if not (delim_start and delim_end): 172 # If only one delimiter is provided, assume the delimiters are 173 # the same, whatever that is (or is nice) 174 delim_start = delim_end = (delim_start or delim_end) 175 else: 176 # Use the default 177 delim_start, delim_end = self.get_file_delimiters(remote_path) 178 179 params = { 180 'path' : remote_path, 181 'delim_start' : delim_start, 182 'delim_end' : delim_end, 183 } 184 185 file_contents = None 186 if os.path.islink(local_path): 187 params['config_file_type_id'] = 3 188 params['symlink'] = os.readlink(local_path) 189 load_contents = 0 190 elif os.path.isdir(local_path): 191 params['config_file_type_id'] = 2 192 load_contents = 0 193 else: 194 params['config_file_type_id'] = 1 195 196 if load_contents: 197 try: 198 file_contents = open(local_path, "rb").read() 199 except IOError: 200 e = sys.exc_info()[1] 201 raise_with_tb(cfg_exceptions.RepositoryLocalFileError( 202 "Error opening local file: %s" % e), sys.exc_info()[2]) 203 204 self._add_content(file_contents, params) 205 206 params.update(self.make_stat_info(local_path, file_stat)) 207 return params
208
209 - def _add_content(self, file_contents, params):
210 """Add the file contents to the params hash""" 211 212 params['enc64'] = 1 213 params['file_contents'] = base64.encodestring(file_contents)
214
215 - def login(self, username=None, password=None):
216 pass
217 218
219 -class RPC_Repository(Repository):
220
221 - def __init__(self, setup_network=1):
222 Repository.__init__(self) 223 # all this so needs to be in a seperate rhnConfig library, 224 # shared by up2date, rhncfg*, etc. 225 # 226 # But, I digress. 227 228 self.__server_url = self._local_config.get('server_url') 229 230 # 6/29/05 wregglej 152388 231 # server_list contains the list of servers to failover to. 232 self.__server_list = self._local_config.get('server_list') 233 234 # 6/29/05 wregglej 152388 235 # Grab server_handler, which is different for rhncfg-client and rhncfg-manager 236 # and is needed when failover occurs. During a failover, when the server object is 237 # being set up to use a new satellite, the server_handler is added to the address so 238 # the tool communicates with the correct xmlrpc handler. 239 handler = self._local_config.get('server_handler') 240 cap_handler = re.sub('[^/]+$', 'XMLRPC', handler) 241 242 if not self.__server_url: 243 raise cfg_exceptions.ConfigurationError( 244 "Missing entry 'server_url' in the config files\n" \ 245 "Try running as root, or configure server_url as described in the configuration file" 246 ) 247 248 log_debug(3, "server url", self.__server_url) 249 self.__proxy_user = None 250 self.__proxy_password = None 251 self.__proxy_host = None 252 253 self.__enable_proxy = self._local_config.get('enableProxy') 254 self.__enable_proxy_auth = self._local_config.get('enableProxyAuth') 255 256 if self.__enable_proxy: 257 self.__proxy_host = self._local_config.get('httpProxy') 258 259 if self.__enable_proxy_auth: 260 self.__proxy_user = self._local_config.get('proxyUser') 261 self.__proxy_password = self._local_config.get('proxyPassword') 262 263 ca = self._local_config.get('sslCACert') 264 if isinstance(ca, basestring): 265 ca = [ca] 266 267 ca_certs = ca or ["/usr/share/rhn/RHNS-CA-CERT"] 268 269 # not sure if we need this or not... 270 lang = None 271 for env in 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG': 272 if env in os.environ: 273 if not os.environ[env]: 274 # sometimes unset 275 continue 276 lang = os.environ[env].split(':')[0] 277 lang = lang.split('.')[0] 278 break 279 280 if setup_network: 281 # Fetch server capabilities - we need the /XMLRPC handler 282 #t = list(utils.parse_url(self.__server_url)) 283 #t[2] = '/XMLRPC' 284 #x_server_url = utils.unparse_url(t) 285 286 # 6/29/05 wregglej 152388 287 # Fetching the server capabilities involves using the /XMLRPC handler. It's 288 # the only place that I know of that does that. The server_url and server_list 289 # both need to have /XMLRPC on the ends, which is what _patch_uris() does by default. 290 x_server_url = self._patch_uris(self.__server_url, cap_handler) 291 if self.__server_list != None: 292 x_server_list = self._patch_uris(self.__server_list, cap_handler) 293 else: 294 x_server_list = None 295 296 x_server = rpclib.Server(x_server_url, 297 proxy=self.__proxy_host, 298 username=self.__proxy_user, 299 password=self.__proxy_password, 300 server_list=x_server_list, 301 rpc_handler="/XMLRPC") 302 303 # Make a call to a function that can export the server's capabilities 304 # without setting any state on the server side 305 try: 306 x_server.registration.welcome_message() 307 except xmlrpclib.Fault: 308 e = sys.exc_info()[1] 309 sys.stderr.write("XML-RPC error while talking to %s:\n %s\n" % (self.__server_url, e)) 310 sys.exit(2) 311 312 self._server_capabilities = get_server_capability(x_server) 313 del x_server 314 315 # 6/29/05 wregglej 152388 316 # From here on out all communication should take place through the xmlrpc handler 317 # that's appropriate for the tool being used. For rhncfg-client that's /CONFIG-MANAGEMENT. 318 # For rhncfg-manager that's /CONFIG-MANAGEMENT-TOOL. No, I don't know the reasoning behind that. 319 # First we need to patch the uris in server_list, to use the correct handler. 320 self.__server_url = self._patch_uris(self.__server_url, handler) 321 if self.__server_list != None: 322 self.__server_list = self._patch_uris(self.__server_list, handler) 323 else: 324 self.__server_list = None 325 326 self.server = rpclib.Server(self.__server_url, 327 proxy=self.__proxy_host, 328 username=self.__proxy_user, 329 password=self.__proxy_password, 330 server_list=self.__server_list, 331 rpc_handler=handler) 332 333 self._set_capabilities() 334 self.server.set_transport_flags( 335 transfer=Output.TRANSFER_BINARY, 336 encoding=Output.ENCODE_GZIP 337 ) 338 339 if lang: 340 self.server.setlang(lang) 341 342 for ca_cert in ca_certs: 343 if not os.access(ca_cert, os.R_OK): 344 raise cfg_exceptions.ConfigurationError("Can not find CA file: %s" % ca_cert) 345 346 log_debug(3, "ca cert", ca_cert) 347 # force the validation of the SSL cert 348 self.server.add_trusted_cert(ca_cert)
349 350 # 6/29/05 wregglej 152388 351 # Places handler at the end of the uri. 352 # uris can be either a uri string or a list of uri strings.
353 - def _patch_uris(self, uris, handler="/XMLRPC"):
354 #Handles patching the uris when they're in a list. 355 if type(uris) == type([]): 356 ret = [] 357 for i in range(len(uris)): 358 t = list(utils.parse_url(uris[i])) 359 t[2] = handler 360 ret.append(utils.unparse_url(t)) 361 #Handles patching the uri when it's a string. 362 else: 363 t = list(utils.parse_url(uris)) 364 t[2] = handler 365 ret = utils.unparse_url(t) 366 return ret
367
368 - def _set_capabilities(self):
369 # list of client capabilities 370 capabilities = { 371 'configfiles.base64_enc' : {'version' : 1, 'value' : 1}, 372 'rhncfg.dirs_enabled' : {'version' : 1, 'value' : 1}, 373 } 374 for name, hashval in capabilities.items(): 375 cap = "%s(%s)=%s" % (name, hashval['version'], hashval['value']) 376 self.server.add_header("X-RHN-Client-Capability", cap)
377
378 - def rpc_call(self, method_name, *params):
379 method = getattr(self.server, method_name) 380 try: 381 result = method(*params) 382 except xmlrpclib.ProtocolError: 383 e = sys.exc_info()[1] 384 sys.stderr.write("XML-RPC call error: %s\n" % e) 385 sys.exit(1) 386 except xmlrpclib.Fault: 387 # Re-raise them 388 raise 389 except Exception: 390 e = sys.exc_info()[1] 391 sys.stderr.write("XML-RPC error while talking to %s: %s\n" % ( 392 self.__server_url, e)) 393 sys.exit(2) 394 395 return result
396
397 - def _get_maximum_file_size(self):
398 return self.rpc_call('config.max_upload_fsize')
399
400 - def _add_content(self, file_contents, params):
401 """Add the file contents to the params hash""" 402 403 # check for the rhncfg.content.base64_decode capability and encode the 404 # data if the server is capable of descoding it 405 if 'rhncfg.content.base64_decode' in self._server_capabilities: 406 params['enc64'] = 1 407 params['file_contents'] = base64.encodestring(file_contents) 408 else: 409 params['file_contents'] = file_contents 410 411 return params
412
413 -def get_server_capability(s):
414 headers = s.get_response_headers() 415 if headers is None: 416 # No request done yet 417 return {} 418 if PY3: 419 cap_headers = ["X-RHN-Server-Capability: %s" % val for val in headers.get_all("X-RHN-Server-Capability")] 420 else: 421 cap_headers = headers.getallmatchingheaders("X-RHN-Server-Capability") 422 423 if not cap_headers: 424 return {} 425 regexp = re.compile( 426 r"^(?P<name>[^(]*)\((?P<version>[^)]*)\)\s*=\s*(?P<value>.*)$") 427 vals = {} 428 for h in cap_headers: 429 arr = h.split(':', 1) 430 assert len(arr) == 2 431 val = arr[1].strip() 432 if not val: 433 continue 434 435 mo = regexp.match(val) 436 if not mo: 437 # XXX Just ignoring it, for now 438 continue 439 vdict = mo.groupdict() 440 for k, v in vdict.items(): 441 vdict[k] = v.strip() 442 443 vals[vdict['name']] = vdict 444 return vals
445