File rhnlib-git-0.c65b45e.obscpio of Package rhnlib.34322
07070100000000000041FD000000000000000000000002666AB47A00000000000000000000000000000000000000000000000700000000rhnlib07070100000001000081B4000000000000000000000001666AB47A000046AC000000000000000000000000000000000000000F00000000rhnlib/COPYING GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
07070100000002000081B4000000000000000000000001666AB47A0000129C000000000000000000000000000000000000001100000000rhnlib/ChangeLogVersion 1.8
- Breaking transports.Transport.request() into smaller pieces
- Added factories for the transport classes in the Server object
- Fixed a bug in File.__del__ (if close is called twice, it tried to call
None)
- Fixed a bug in SSL.read() (on an open stream, read() would block instead of
returning with the amount of data available).
- The previous fix in SSL.read uncovered a nastier bug in httplib:
http://python.org/sf/988120
Fixed it in our HTTPResponse subclass
- The internal _httplib did not have a _read_chunked
(http://bugzilla.redhat.com/128008)
Version 1.7
- Renamed _xmlrpclib.py to _internal_xmlrpclib.py to avoid _xmlrpclib from
self-importing (bug #119876)
- Added a User-Agent header for CONNECTION tunnels.
- Fixed lookupEncoding
Version 1.6
- GET methods have empty bodies, so rpclib.GETServer._req_body should return
empty string not None
- Properly convert chunk sizes in python 1.5.2
Version 1.5
- Added iterator for the UserDictCase objects. Easier than subclassing
IterableUserDict since the code has to work with python 1.5.2.
- rhn/transports.py: one can pass to an Input object a stream that doesn't
have a close() method.
- rpclib exposes File now too
- rhn/transports.py: the Output object now sets Content-Length
(which will be cleared when sent upstream)
Version 1.3
- work on HTTP/HTTPS redirection support at the Server class level in
rhnlib.py:
- new data in the instance to keep setting made to the underlying transport
- new methods to control and check redirection: allow_redirect() and
redirected() which returns the redirected URL or None
- separated the initialization code to provide a default transport as an
new method default_transport()
- added redirection handling in _request() with some associated checking
like https->http redirection being forbidden
- at the transport level, request() checks redirection, position a
redirection flag and returns None if redirected.
- Added some flexibility on the way to specify proxy informations, the
get_proxy_info() function of rhnlib.py is used to extract host, port,
username and password information from the proxy URL or old host:port
format.
- Fixed an error related to decoding the tuples that XMLRPC returns back.
Version 1.2
- Changed the ugly way of passing around InputStream as an exception
- The transport flags are now more generic, allowing for future extensibility
- Added a transport flag 'allow_partial_content' which will prevent the
transport from raising an exception if a 206 status code is received
- New Server methods: get_response_headers, get_response_status and
get_response_reason; you can now inspect the headers even for successful
connections
- Changed the GETServer method calls to accept offset and amount
- Added get_content_range, to parse the interesting values out of the HTTP
headers
- Added accept_ranges, a function that returns true if the server accepts
ranges. Note that even though the server doesn't advertise itself as
accepting ranges, it's still possible for the client to issue a range
request; depending on the response status code, the client can see if the
server supports ranges (206) or not (200)
- set_header and add_header are now different and doing what they are
supposed to do
- added get_transport_flags() as a Server method
- fixed an instance when Input.io was not rewound
Version 1.1
- Minor bugfixes
- Building the rpm with distutils is too painful; switching to a
human-maintained spec file instead.
Version 1.0
- More or less 0.9 for public consumption
Version 0.9
- SSL.py: use load_verify_locations multiple times. This function seems to do
more than my poor method of getting the cert store and adding certs by
hand, probably because load_certificate from a buffer can only handle one
certificate at the time.
- With the addition of SmartIO, the memory usage should be limited to 16k,
after which the objects will be dumped on the disk in temporary files.
- Fixed yet another file descriptor leak (and still in SSL).
Version 0.8
- User-Agent, X-Info and X-Client-Version were not present in the HTTP
headers; fixed
- Completely deprecating rhnHTTPlib: swallowed reportError
- Fixed stupid typo introduced when fixing the previous bug.
- Fixed #69518 (up2date seems to never properly reauthenticate after a auth
timeout)
- Fixed #69311 (leaking file descriptors over SSL connections).
- Fixed #68911 (and some other bugs that were related to this one).
- Fixed a proxy related bug. Thanks to Gil Chilton <Gil.Chilton@capitalone.com>
for helping me discover and test it.
Version 0.7
- More careful distutils packaging.
07070100000003000081B4000000000000000000000001666AB47A00000048000000000000000000000000000000000000001300000000rhnlib/MANIFEST.ininclude ChangeLog COPYING README TODO rhnlib.spec Makefile.rhnlib_build
07070100000004000081B4000000000000000000000001666AB47A00000361000000000000000000000000000000000000001700000000rhnlib/Makefile.pythonTHIS_MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
CURRENT_DIR := $(dir $(THIS_MAKEFILE))
include $(CURRENT_DIR)../../rel-eng/Makefile.python
# Docker tests variables
DOCKER_CONTAINER_BASE = systemsmanagement/uyuni/master/docker/containers/uyuni-master
DOCKER_REGISTRY = registry.opensuse.org
DOCKER_RUN_EXPORT = "PYTHONPATH=$PYTHONPATH"
DOCKER_VOLUMES = -v "$(CURDIR)/../../:/manager"
__pylint ::
$(call update_pip_env)
cd ..; pylint --rcfile=rhn/pylintrc rhn | tee rhn/reports/pylint.log || true
docker_pylint ::
docker run --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/sh -c "cd /manager/python/rhn; make -f Makefile.python __pylint"
docker_shell ::
docker run -t -i --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/bash
07070100000005000081B4000000000000000000000001666AB47A0000061F000000000000000000000000000000000000001700000000rhnlib/Makefile.rhnlib# Makefile for the rhn modules
#
PYTHON = /usr/bin/python
NAME = rhnlib
VERSION = $(call _get_tag,VERSION)
RELEASE = $(call _get_tag,RELEASE)
_get_tag = $(shell rpm -q --queryformat "%{$1}" --specfile $(NAME).spec | head -1)
WORKDIR = $(shell pwd)
SOURCEDIR = $(WORKDIR)
SRCRPMDIR = $(WORKDIR)
BUILDDIR = $(WORKDIR)
RPMDIR = $(WORKDIR)
RPMBUILD = rpmbuild
RPMBUILDCMD = $(RPMBUILD) --define "_sourcedir $(SOURCEDIR)" --define "_srcrpmdir $(SRCRPMDIR)" --define "_builddir $(BUILDDIR)" --define "_rpmdir $(RPMDIR)" --define "_build_name_fmt %{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm"
TARBALL = $(NAME)-$(VERSION).tar.gz
.PHONY: build install rpm devel release clean
all: build
setup.py: setup.py.in $(VERFILE)
@sed -e 's/@VERSION@/$(VERSION)/' -e 's/@NAME@/$(NAME)/' $< > $@
setup.cfg: setup.cfg.in $(VERFILE)
@sed 's/@RELEASE@/$(RELEASE)/' $< > $@
build: setup.py
@$(PYTHON) setup.py build
for i in build/lib*/rhn/rpclib.py build/lib*/rhn/transports.py ; do sed -i 's!\$$Revision\$$!$(VERSION)-$(RELEASE)!' $$i ; done
install: setup.py setup.cfg
@$(PYTHON) setup.py install
$(TARBALL): clean setup.py setup.cfg
@$(PYTHON) setup.py sdist $(DISTUTILS_OPT)
@mv dist/$(TARBALL) $(WORKDIR)
@rm -rf dist
tar: $(TARBALL)
test-srpm: $(TARBALL)
$(RPMBUILDCMD) --nodeps -ts $(TARBALL)
test-rpm: $(TARBALL)
$(RPMBUILDCMD) --nodeps -ta $(TARBALL)
rpm: $(TARBALL)
$(RPMBUILD) -ta $(TARBALL)
release: tar test-srpm
clean:
@rm -rfv dist build MANIFEST setup.cfg setup.py *pyc *.tar.gz *.rpm $(NAME)-$(VERSION)
07070100000006000081B4000000000000000000000001666AB47A00000422000000000000000000000000000000000000000E00000000rhnlib/READMErhnlib - A collection of python modules used by the Spacewalk
(http://spacewalk.redhat.com) software.
Copyright (C) 2002--2015 Red Hat, Inc.
This library is distributed under the terms of the GNU Public License (GPL).
0. HISTORY
Traditionally, rhn used a patched version of Fredrik Lundh's xmlrpclib,
shipped with Red Hat 6.2, 7.0, 7.1, 7.2 and 7.3 in a package called
python-xmlrpc.
The additions included improved transport objects (proxy support, a
better SSL framework, encodings and transfers), most of them part of a module
called cgiwrap.
Since python 2.2 (and previous python 2 releases) ships Fredrik Lundh's
xmlrpclib as a standard library, maintaining a patched version of xmlrpclib
was no longer feasible. SSL support in python 2.2 is also hopelessly
incomplete, so using an external module seems to be a better approach. We
chose pyOpenSSL instead of m2crypto, partly because swig seems to change
rather quickly.
rhnlib tries to reuse as much as possible from the old cgiwrap, as well as
from what the standard python libraries provide.
07070100000007000081B4000000000000000000000001666AB47A00002986000000000000000000000000000000000000000E00000000rhnlib/SSL.py# pylint: disable=invalid-name
#
# Higher-level SSL objects used by rpclib
#
# Copyright (c) 2002--2017 Red Hat, Inc.
#
# Author: Mihai Ibanescu <misa@redhat.com>
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations
# including the two.
# You must obey the GNU General Public License in all respects
# for all of the code used other than OpenSSL. If you modify
# file(s) with this exception, you may extend this exception to your
# version of the file(s), but you are not obligated to do so. If you
# do not wish to do so, delete this exception statement from your
# version. If you delete this exception statement from all source
# files in the program, then also delete it here.
"""
rhn.SSL builds an abstraction on top of the objects provided by pyOpenSSL
"""
# SSL.crypto is provided to other modules
# pylint: disable-next=unused-import
from OpenSSL import crypto
import ssl as SSL
import os
import socket
import select
from rhn.stringutils import bstr
import sys
DEFAULT_TIMEOUT = 120
if hasattr(socket, "sslerror"):
socket_error = socket.sslerror
else:
# pylint: disable-next=unused-import
from ssl import socket_error
try:
# pylint: disable-next=unused-import
from ssl import CertificateError
except ImportError:
# python 2.6
# pylint: disable-next=unused-import
from backports.ssl_match_hostname import match_hostname, CertificateError
class SSLSocket:
"""
Class that wraps a pyOpenSSL Connection object, adding more methods
"""
# pylint: disable-next=redefined-outer-name
def __init__(self, socket, trusted_certs=None):
# SSL.Context object
self._ctx = None
# SSL.Connection object
self._connection = None
self._sock = socket
self._trusted_certs = []
# convert None to empty list
trusted_certs = trusted_certs or []
for f in trusted_certs:
self.add_trusted_cert(f)
# Buffer size for reads
self._buffer_size = 8192
# Position, for tell()
self._pos = 0
# Buffer
self._buffer = bstr("")
# Flag to show if makefile() was called
self._makefile_called = 0
self._closed = None
def add_trusted_cert(self, file):
"""
Adds a trusted certificate to the certificate store of the SSL context
object.
"""
if not os.access(file, os.R_OK):
# pylint: disable-next=consider-using-f-string
raise ValueError("Unable to read certificate file %s" % file)
self._trusted_certs.append(file.encode("utf-8"))
def init_ssl(self, server_name=None):
"""
Initializes the SSL connection.
"""
self._check_closed()
self._ctx = SSL.SSLContext(SSL.PROTOCOL_TLS_CLIENT)
self._ctx.options |= SSL.OP_NO_TLSv1
self._ctx.options |= SSL.OP_NO_TLSv1_1
self._ctx.verify_mode = SSL.CERT_REQUIRED
self._ctx.check_hostname = True
self._ctx.load_default_certs(SSL.Purpose.SERVER_AUTH)
if self._trusted_certs:
# We have been supplied with trusted CA certs
for f in self._trusted_certs:
self._ctx.load_verify_locations(f)
self._connection = self._ctx.wrap_socket(
self._sock, server_hostname=server_name
)
# pylint: disable-next=unused-argument
def makefile(self, mode, bufsize=None):
"""
Returns self, since we are a file-like object already
"""
if bufsize:
self._buffer_size = bufsize
# Increment the counter with the number of times we've called makefile
# - we don't want close to actually close things until all the objects
# that originally called makefile() are gone
self._makefile_called = self._makefile_called + 1
return self
def close(self):
"""
Closes the SSL connection
"""
# XXX Normally sock.makefile does a dup() on the socket file
# descriptor; httplib relies on this, but there is no dup for an ssl
# connection; so we have to count how may times makefile() was called
if self._closed:
# Nothing to do
return
if not self._makefile_called:
self._really_close()
return
self._makefile_called = self._makefile_called - 1
# BZ 1464157 - Python 3 http attempts to call this method during close,
# at least add it empty
def flush(self):
pass
def _really_close(self):
# No connection was established
if self._connection is None:
return
self._connection.close()
self._closed = 1
def _check_closed(self):
if self._closed:
raise ValueError("I/O operation on closed file")
def __getattr__(self, name):
if hasattr(self._connection, name):
return getattr(self._connection, name)
raise AttributeError(name)
# File methods
def isatty(self):
"""
Returns false always.
"""
return 0
def tell(self):
return self._pos
def seek(self, pos, mode=0):
raise NotImplementedError("seek")
def read(self, amt=None):
"""
Reads up to amt bytes from the SSL connection.
"""
self._check_closed()
# Initially, the buffer size is the default buffer size.
# Unfortunately, pending() does not return meaningful data until
# recv() is called, so we only adjust the buffer size after the
# first read
buffer_size = self._buffer_size
buffer_length = len(self._buffer)
# Read only the specified amount of data
while buffer_length < amt or amt is None:
# if amt is None (read till the end), fills in self._buffer
if amt is not None:
buffer_size = min(amt - buffer_length, buffer_size)
try:
data = self._connection.recv(buffer_size)
self._buffer = self._buffer + data
buffer_length = len(self._buffer)
# More bytes to read?
pending = self._connection.pending()
if pending == 0 and buffer_length == amt:
# we're done here
break
except SSL.SSLError as err:
if err.args[0] == SSL.SSL_ERROR_ZERO_RETURN:
# Nothing more to be read
break
elif err.args[0] == SSL.SSL_ERROR_SYSCALL:
e = sys.exc_info()[1]
print("SSL exception", e.args)
break
elif err.args[0] == SSL.SSL_ERROR_WANT_WRITE:
self._poll(select.POLLOUT, "read")
elif err.args[0] == SSL.SSL_ERROR_WANT_READ:
self._poll(select.POLLIN, "read")
if amt:
ret = self._buffer[:amt]
self._buffer = self._buffer[amt:]
else:
ret = self._buffer
self._buffer = bstr("")
self._pos = self._pos + len(ret)
return ret
def readinto(self, buf):
buf[:] = self.read(len(buf))
return len(buf)
def _poll(self, filter_type, caller_name):
poller = select.poll()
poller.register(self._sock, filter_type)
res = poller.poll(self._sock.gettimeout() * 1000)
if res == []:
# pylint: disable-next=consider-using-f-string
raise TimeoutException("Connection timed out on %s" % caller_name)
def write(self, data):
"""
Writes to the SSL connection.
"""
self._check_closed()
# XXX Should use sendall
# sent = self._connection.sendall(data)
origlen = len(data)
while True:
try:
sent = self._connection.send(data)
if sent == len(data):
break
data = data[sent:]
except SSL.SSLError as err:
if err.args[0] == SSL.SSL_ERROR_WANT_WRITE:
self._poll(select.POLLOUT, "write")
elif err.args[0] == SSL.SSL_ERROR_WANT_READ:
self._poll(select.POLLIN, "write")
return origlen
def recv(self, amt):
return self.read(amt)
send = write
sendall = write
def readline(self, length=None):
"""
Reads a single line (up to `length' characters long) from the SSL
connection.
"""
self._check_closed()
while True:
# charcount contains the number of chars to be outputted (or None
# if none to be outputted at this time)
charcount = None
i = self._buffer.find(bstr("\n"))
if i >= 0:
# Go one char past newline
charcount = i + 1
elif length and len(self._buffer) >= length:
charcount = length
if charcount is not None:
ret = self._buffer[:charcount]
self._buffer = self._buffer[charcount:]
self._pos = self._pos + len(ret)
return ret
# Determine the number of chars to be read next
bufsize = self._buffer_size
if length:
# we know length > len(self._buffer)
bufsize = min(self._buffer_size, length - len(self._buffer))
try:
data = self._connection.recv(bufsize)
if len(data) == 0:
break
self._buffer = self._buffer + data
except SSL.SSLError as err:
if err.args[0] == SSL.SSL_ERROR_ZERO_RETURN:
# Nothing more to be read
break
elif err.args[0] == SSL.SSL_ERROR_WANT_WRITE:
self._poll(select.POLLOUT, "readline")
elif err.args[0] == SSL.SSL_ERROR_WANT_READ:
self._poll(select.POLLIN, "readline")
# We got here if we're done reading, so return everything
ret = self._buffer
self._buffer = ""
self._pos = self._pos + len(ret)
return ret
class TimeoutException(SSL.SSLError, socket.timeout):
def __init__(self, *args):
self.args = args
def __str__(self):
return "Timeout Exception"
07070100000008000081B4000000000000000000000001666AB47A000008DC000000000000000000000000000000000000001200000000rhnlib/SmartIO.py# pylint: disable=invalid-name
#
# Smart IO class
#
# Copyright (c) 2002--2016 Red Hat, Inc.
#
# Author: Mihai Ibanescu <misa@redhat.com>
"""
This module implements the SmartIO class
"""
import os
try: # python2
from cStringIO import StringIO
except ImportError: # python3
from io import BytesIO as StringIO
class SmartIO:
"""
The SmartIO class allows one to put a cap on the memory consumption.
StringIO objects are very fast, because they are stored in memory, but
if they are too big the memory footprint becomes noticeable.
The write method of a SmartIO determines if the data that is to be added
to the (initially) StrintIO object does not exceed a certain threshold; if
it does, it switches the storage to a temporary disk file
"""
def __init__(self, max_mem_size=16384, force_mem=0):
self._max_mem_size = max_mem_size
self._io = StringIO()
# self._fixed is a flag to show if we're supposed to consider moving
# the StringIO object into a tempfile
# Invariant: if self._fixed == 0, we have a StringIO (if self._fixed
# is 1 and force_mem was 0, then we have a file)
if force_mem:
self._fixed = 1
else:
self._fixed = 0
def set_max_mem_size(self, max_mem_size):
self._max_mem_size = max_mem_size
def get_max_mem_size(self):
return self._max_mem_size
def write(self, data):
if not self._fixed:
# let's consider moving it to a file
if len(data) + self._io.tell() > self._max_mem_size:
# We'll overflow, change to a tempfile
tmpfile = _tempfile()
tmpfile.write(self._io.getvalue())
self._fixed = 1
self._io = tmpfile
self._io.write(data)
def __getattr__(self, name):
return getattr(self._io, name)
# Creates a temporary file and passes back its file descriptor
def _tempfile():
# pylint: disable-next=import-outside-toplevel
import tempfile
# pylint: disable-next=consider-using-f-string
(fd, fname) = tempfile.mkstemp(prefix="_rhn_transports-%d-" % os.getpid())
# tempfile, unlink it
os.unlink(fname)
return os.fdopen(fd, "wb+")
07070100000009000081B4000000000000000000000001666AB47A00000293000000000000000000000000000000000000000C00000000rhnlib/TODO
- Check nonblocking support for HTTPS as well.
- Allow for multi-valued headers
- Wrapper around the socket class, to allow for calling select() on the socket
I/O
- Split this module intelligently
- Build an __init__ file to expose the desired API
- Fix the Input and Output objects: setting the encoding from the input object
to the output object is difficult (there is a patch somewhere for this, I'll
have to look it up -- misa)
- Clean up things
- More documentation
- Check compliance with python 1.5
- Figure out a better way to pass the client version
- Figure out what the version we pass in the headers should look like.
- Finish the TODO :-)
0707010000000A000081B4000000000000000000000001666AB47A00000CB8000000000000000000000000000000000000001700000000rhnlib/UserDictCase.py# pylint: disable=missing-module-docstring,invalid-name
#
# Copyright (c) 2001--2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
#
# This file implements a case insensitive dictionary on top of the
# UserDict standard python class
#
try: # python2
from UserDict import UserDict
from types import StringType
except ImportError: # python3
from collections import UserDict
StringType = str
from functools import reduce
# A dictionary with case insensitive keys
# pylint: disable-next=missing-class-docstring
class UserDictCase(UserDict):
def __init__(self, data=None):
self.kcase = {}
UserDict.__init__(self, data)
def __lower_string(self, key):
"""Return the lower() of key if it is a string."""
if isinstance(key, StringType):
return key.lower()
else:
return key
# some methods used to make the class work as a dictionary
def __setitem__(self, key, value):
lkey = self.__lower_string(key)
self.data[lkey] = value
self.kcase[lkey] = key
def __getitem__(self, key):
key = self.__lower_string(key)
return self.data[key]
get = __getitem__
def __delitem__(self, key):
key = self.__lower_string(key)
del self.data[key]
del self.kcase[key]
def __contains__(self, key):
key = self.__lower_string(key)
return key in self.data
def keys(self):
return self.kcase.values()
def items(self):
return self.get_hash().items()
def has_key(self, key):
# obsoleted, left for compatibility with older python
return key in self
def clear(self):
self.data.clear()
self.kcase.clear()
# return this data as a real hash
def get_hash(self):
# pylint: disable-next=used-before-assignment
return reduce(
lambda a, t, hc=self.kcase: a.update({hc[t[0]]: t[1]}) or a,
self.data.items(),
{},
)
# return the data for marshalling
def __getstate__(self):
return self.get_hash()
# we need a setstate because of the __getstate__ presence screws up deepcopy
def __setstate__(self, state):
self.__init__(state)
# get a dictionary out of this instance ({}.update doesn't get instances)
def dict(self):
return self.get_hash()
# pylint: disable-next=redefined-builtin
def update(self, dict):
for k, v in dict.items():
self[k] = v
# Expose an iterator. This would normally fail if there is no iter()
# function defined - but __iter__ will never be called on python 1.5.2
def __iter__(self):
return iter(self.data)
0707010000000B000081B4000000000000000000000001666AB47A00000083000000000000000000000000000000000000001300000000rhnlib/__init__.py#
# __init__.py
#
# Copyright (c) 2011--2013 Red Hat, Inc.
#
"""
rhn - A collection of modules used by Red Hat Network Classic
"""
0707010000000C000081FD000000000000000000000001666AB47A00000CC3000000000000000000000000000000000000001700000000rhnlib/buildpackage.sh#!/usr/bin/bash
# script to build a solaris package of up2date-list
#
# Adrian Likins <alikins@redhat.com>
#
NAME=`cat $1 | grep PKG | cut -f2 -d'='| cut -f2 -d\"`
TMP=/tmp
BUILDPREFIX=$TMP/build-$NAME
# er, lame
BUILDPREFIX_REL=tmp/build-$NAME
DESTPREFIX_REL=opt/redhat/rhn/solaris
DESTPREFIX=/${DESTPREFIX_REL}
DATADIR=${TMP}/${NAME}-pkginfo/
USERNAME=`whoami`
# path for solaris build stuff, will need to get tweaked for hpux/aix, but
# theres some infof or that already un use_spec.sh
#PATH=/opt/redhat/rhn/solaris/bin:/opt/redhat/gnupro-03r1/H-sparc-sun-solaris2.5/bin:/opt/redhat/gnupro-03r1/contrib:/opt/redhat/rpm/solaris/bin:/home/cygnus/release/bin:/es/cst/bin:/bsp/bin:/usr/progressive/bin:/usr/unsupported/bin:/bin:/usr/ucb:/usr/sbin:/usr/local/bin:/sbin:/usr/kerberos/bin:/usr/local/bin:/usr/bin:/usr/X11R6/bin
PYTHONDIR=$DESTPREFIX/lib/python2.2/
PYTHONMODDIR=$PYTHONDIR/site-packages
BANGPATH=$DESTPREFIX/bin/python
# helper stuff
#INSTALL=/opt/redhat/gnupro-03r1/contrib/H-sparc-sun-solaris2.6/bin/install
#INSTALL=/es/unsupported/sparc-sun-solaris2.5/src/fileutils-4.1/src/ginstall
INSTALL=/usr/ucb/install
FILELIST=$TMP/filelist-$NAME
mkdir -p $BUILDPREFIX
# clean up the build root
rm -rf $BUILDPREFIX/*
# er, duh
#make
python setup.py build
# install into the fake prefix
# make PREFIX=$BUILDPREFIX INSTALL=$INSTALL PYTHONDIR=$PYTHONDIR PYTHONMOD_DIR=$PYTHONMODDIR BANGPATH=$BANGPATH install
python setup.py install --prefix=$BUILDPREFIX
# find the packages installed into the build root
find $BUILDPREFIX -print > $FILELIST
# create the package prototype
mkdir -p $DATADIR
cat $FILELIST | pkgproto > $DATADIR/prototype
# add the info about pkginfo file
echo "i pkginfo" > $DATADIR/prototype.tmp
cat $DATADIR/prototype >> $DATADIR/prototype.tmp
# write it back again
cp $DATADIR/prototype.tmp $DATADIR/prototype
PROTOTYPE=$DATADIR/prototype
# cp our pkginfo file to the datafir
cp pkginfo $DATADIR/pkginfo
# okay, now the fun begins. The problem is that all the paths are
# wrong. You can't simply change the prototype, cause it will
# look for the new paths, where nothing exist. So you have to
# build the package, then tweak the package info directly.
# build the actual package, pre munge
pkgmk -o -r / -d $TMP -f $PROTOTYPE
ls -la $TMP
# the package is in filesystem format, so we
# need to go into that dir and start mucking
# with stuff
cd $TMP/${NAME}/root/
# since the package as is has a local
# equilvient of $(BUILDPREFIX), we need to
# create a dir structure representing what
# the dest version is
mkdir -p $DESTPREFIX_REL
# move the stuff to the new dir that we actually
# want...
echo $BUILDPREFIX_REL $DESTPREFIX_REL
echo
pwd
echo
mv ${BUILDPREFIX_REL}/* ${DESTPREFIX_REL}/
rm -rf $BUILDPREFIX_REL
# pkgmap is the manifest of files and what not
# we need to blip it about to sub in the new file
# paths
cd ..
# use perl cause I'm dumb and lazy
perl -pi -e "s|${BUILDPREFIX}|${DESTPREFIX}|g" pkgmap
# fix the file ownership as well
perl -pi -e "s|${USERNAME}|root|g" pkgmap
# now that we've got the file all munged up, lets
# go ahead and convert the file to a datastream format
pkgtrans -s $TMP $TMP/$NAME.package $NAME
# probabaly want to compress the package at this
# point
0707010000000D000081B4000000000000000000000001666AB47A00002B36000000000000000000000000000000000000001600000000rhnlib/connections.py# pylint: disable=missing-module-docstring
#
# Connection objects
#
# Copyright (c) 2002--2016 Red Hat, Inc.
# Copyright (c) 2022 SUSE, LLC
#
# Author: Mihai Ibanescu <misa@redhat.com>
import base64
# pylint: disable-next=unused-import
import encodings.idna
import socket
from platform import python_version
from rhn.stringutils import bstr, ustr, sstr
from rhn import SSL
from rhn import nonblocking
try: # python2
import httplib
# Import into the local namespace some httplib-related names
from httplib import _CS_REQ_SENT, _CS_IDLE, ResponseNotReady
import xmlrpclib
except ImportError: # python3
import http.client as httplib
# Import into the local namespace some httplib-related names
from http.client import _CS_REQ_SENT, _CS_IDLE, ResponseNotReady
import xmlrpc.client as xmlrpclib
class HTTPResponse(httplib.HTTPResponse):
def set_callback(self, rs, ws, ex, user_data, callback):
if not isinstance(self.fp, nonblocking.NonBlockingFile):
self.fp = nonblocking.NonBlockingFile(self.fp)
self.fp.set_callback(rs, ws, ex, user_data, callback)
# pylint: disable-next=missing-class-docstring
class HTTPConnection(httplib.HTTPConnection):
response_class = HTTPResponse
def __init__(self, host, port=None, timeout=SSL.DEFAULT_TIMEOUT):
if python_version() >= "2.6.1":
httplib.HTTPConnection.__init__(self, host, port, timeout=timeout)
else:
httplib.HTTPConnection.__init__(self, host, port)
self._cb_rs = []
self._cb_ws = []
self._cb_ex = []
self._cb_user_data = None
self._cb_callback = None
self._user_agent = "rhn.connections $Revision$ (python)"
self.timeout = timeout
def set_callback(self, rs, ws, ex, user_data, callback):
# XXX check the params
self._cb_rs = rs
self._cb_ws = ws
self._cb_ex = ex
self._cb_user_data = user_data
self._cb_callback = callback
def set_user_agent(self, user_agent):
self._user_agent = user_agent
# XXX Had to copy the function from httplib.py, because the nonblocking
# framework had to be initialized
def getresponse(self):
"Get the response from the server."
# check if a prior response has been completed
if self.__response and self.__response.isclosed():
self.__response = None
#
# if a prior response exists, then it must be completed (otherwise, we
# cannot read this response's header to determine the connection-close
# behavior)
#
# note: if a prior response existed, but was connection-close, then the
# socket and response were made independent of this HTTPConnection
# object since a new request requires that we open a whole new
# connection
#
# this means the prior response had one of two states:
# 1) will_close: this connection was reset and the prior socket and
# response operate independently
# 2) persistent: the response was retained and we await its
# isclosed() status to become true.
#
if self.__state != _CS_REQ_SENT or self.__response:
raise ResponseNotReady()
if self.debuglevel > 0:
response = self.response_class(self.sock, self.debuglevel)
else:
response = self.response_class(self.sock)
# The only modification compared to the stock HTTPConnection
if self._cb_callback:
response.set_callback(
self._cb_rs,
self._cb_ws,
self._cb_ex,
self._cb_user_data,
self._cb_callback,
)
response.begin()
# pylint: disable-next=protected-access
assert response.will_close != httplib._UNKNOWN
self.__state = _CS_IDLE
if response.will_close:
# this effectively passes the connection to the response
self.close()
else:
# remember this, so we can tell when it is complete
self.__response = response
return response
def connect(self):
httplib.HTTPConnection.connect(self)
self.sock.settimeout(self.timeout)
# pylint: disable-next=missing-class-docstring
class HTTPProxyConnection(HTTPConnection):
def __init__(
self,
proxy,
host,
port=None,
username=None,
password=None,
timeout=SSL.DEFAULT_TIMEOUT,
):
# The connection goes through the proxy
HTTPConnection.__init__(self, proxy, timeout=timeout)
# save the proxy values
self.__proxy, self.__proxy_port = self.host, self.port
# self.host and self.port will point to the real host
if hasattr(self, "_get_hostport"):
self.host, self.port = self._get_hostport(host, port)
else:
self._set_hostport(host, port)
# save the host and port
self._host, self._port = self.host, self.port
# Authenticated proxies support
self.__username = username
self.__password = password
def connect(self):
# We are actually connecting to the proxy
if hasattr(self, "_get_hostport"):
self.host, self.port = self._get_hostport(self.__proxy, self.__proxy_port)
else:
self._set_hostport(self.__proxy, self.__proxy_port)
HTTPConnection.connect(self)
# Restore the real host and port
if hasattr(self, "_get_hostport"):
self.host, self.port = self._get_hostport(self._host, self._port)
else:
self._set_hostport(self._host, self._port)
def putrequest(self, method, url, skip_host=0):
# The URL has to include the real host
hostname = self._host
if self._port != self.default_port:
hostname = hostname + ":" + str(self._port)
# pylint: disable-next=consider-using-f-string
newurl = "http://%s%s" % (hostname, url)
# Piggyback on the parent class
HTTPConnection.putrequest(self, method, newurl, skip_host=skip_host)
# Add proxy-specific headers
self._add_proxy_headers()
def _add_proxy_headers(self):
if not self.__username:
return
# Authenticated proxy
# pylint: disable-next=consider-using-f-string
userpass = "%s:%s" % (self.__username, self.__password)
enc_userpass = base64.encodestring(bstr(userpass)).replace(bstr("\n"), bstr(""))
# pylint: disable-next=consider-using-f-string
self.putheader("Proxy-Authorization", "Basic %s" % sstr(enc_userpass))
def _set_hostport(self, host, port):
(self.host, self.port) = self._get_hostport(host, port)
# pylint: disable-next=missing-class-docstring
class HTTPSConnection(HTTPConnection):
response_class = HTTPResponse
default_port = httplib.HTTPSConnection.default_port
def __init__(
self, host, port=None, trusted_certs=None, timeout=SSL.DEFAULT_TIMEOUT
):
HTTPConnection.__init__(self, host, port, timeout=timeout)
trusted_certs = trusted_certs or []
self.trusted_certs = trusted_certs
def connect(self):
"Connect to a host on a given (SSL) port"
results = socket.getaddrinfo(
self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM
)
for r in results:
# pylint: disable-next=unused-variable
af, socktype, proto, canonname, sa = r
try:
sock = socket.socket(af, socktype, proto)
except socket.error:
sock = None
continue
try:
sock.connect((self.host, self.port))
sock.settimeout(self.timeout)
except socket.error:
sock.close()
sock = None
continue
break
if sock is None:
raise socket.error("Unable to connect to the host and port specified")
self.sock = SSL.SSLSocket(sock, self.trusted_certs)
self.sock.init_ssl(self.host)
class HTTPSProxyResponse(HTTPResponse):
def begin(self):
HTTPResponse.begin(self)
self.will_close = 0
# pylint: disable-next=missing-class-docstring
class HTTPSProxyConnection(HTTPProxyConnection):
default_port = HTTPSConnection.default_port
def __init__(
self,
proxy,
host,
port=None,
username=None,
password=None,
trusted_certs=None,
timeout=SSL.DEFAULT_TIMEOUT,
):
HTTPProxyConnection.__init__(
self, proxy, host, port, username, password, timeout=timeout
)
trusted_certs = trusted_certs or []
self.trusted_certs = trusted_certs
def connect(self):
# Set the connection with the proxy
HTTPProxyConnection.connect(self)
# Use the stock HTTPConnection putrequest
# pylint: disable-next=consider-using-f-string
host = "%s:%s" % (self._host, self._port)
HTTPConnection.putrequest(self, "CONNECT", host)
# Add proxy-specific stuff
self._add_proxy_headers()
# And send the request
HTTPConnection.endheaders(self)
# Save the response class
response_class = self.response_class
# And replace the response class with our own one, which does not
# close the connection after
self.response_class = HTTPSProxyResponse
response = HTTPConnection.getresponse(self)
# Restore the response class
self.response_class = response_class
# Close the response object manually
response.close()
if response.status != 200:
# Close the connection manually
self.close()
raise xmlrpclib.ProtocolError(
host, response.status, response.reason, response.msg
)
self.sock = SSL.SSLSocket(self.sock, self.trusted_certs)
self.sock.init_ssl(self.host)
def putrequest(self, method, url, skip_host=0):
return HTTPConnection.putrequest(self, method, url, skip_host=skip_host)
def _add_proxy_headers(self):
HTTPProxyConnection._add_proxy_headers(self)
# Add a User-Agent header
self.putheader("User-Agent", self._user_agent)
def idn_puny_to_unicode(hostname):
"""Convert Internationalized domain name from Punycode (RFC3492) to Unicode"""
if hostname is None:
return None
else:
hostname = bstr(hostname)
return hostname.decode("idna")
def idn_ascii_to_puny(hostname):
"""Convert domain name to Punycode (RFC3492). Hostname can be instance of string or Unicode"""
if hostname is None:
return None
else:
hostname = ustr(hostname)
return ustr(hostname.encode("idna"))
idn_pune_to_unicode = idn_puny_to_unicode
idn_ascii_to_pune = idn_ascii_to_puny
0707010000000E000081B4000000000000000000000001666AB47A000003D1000000000000000000000000000000000000001100000000rhnlib/copyrightRhnlib is copyright 2010 by Red Hat Inc.
Rhnlib is currently developed by Spacewalk Development Team <spacewalk-devel@redhat.com>.
License: GPLv2
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
See /usr/share/common-licenses/GPL-2, or
<http://www.gnu.org/copyleft/gpl.txt> for the terms of the latest version
of the GNU General Public License.
0707010000000F000081B4000000000000000000000001666AB47A00000A58000000000000000000000000000000000000001600000000rhnlib/nonblocking.py# pylint: disable=missing-module-docstring
#
#
#
import select
import fcntl
import os
# pylint: disable-next=missing-class-docstring
class NonBlockingFile:
def __init__(self, fd):
# Keep a copy of the file descriptor
self.fd = fd
fcntl.fcntl(self.fd.fileno(), fcntl.F_SETFL, os.O_NDELAY)
# Set the callback-related stuff
self.read_fd_set = []
self.write_fd_set = []
self.exc_fd_set = []
self.user_data = None
self.callback = None
# pylint: disable-next=redefined-outer-name
def set_callback(self, read_fd_set, write_fd_set, exc_fd_set, user_data, callback):
self.read_fd_set = read_fd_set
# Make the objects non-blocking
# pylint: disable-next=redefined-outer-name
for f in self.read_fd_set:
fcntl.fcntl(f.fileno(), fcntl.F_SETFL, os.O_NDELAY)
self.write_fd_set = write_fd_set
self.exc_fd_set = exc_fd_set
self.user_data = user_data
self.callback = callback
def read(self, amt=0):
while 1:
status_changed = 0
readfds = self.read_fd_set + [self.fd]
writefds = self.write_fd_set
excfds = self.exc_fd_set
print("Calling select", readfds)
readfds, writefds, excfds = select.select(readfds, writefds, excfds)
print("Select returned", readfds, writefds, excfds)
if self.fd in readfds:
# Our own file descriptor has changed status
# Mark this, but also try to call the callback with the rest
# of the file descriptors that changed status
status_changed = 1
readfds.remove(self.fd)
if self.callback and (readfds or writefds or excfds):
self.callback(readfds, writefds, excfds, self.user_data)
if status_changed:
break
print("Returning")
return self.fd.read(amt)
def write(self, data):
return self.fd.write(data)
def __getattr__(self, name):
return getattr(self.fd, name)
# pylint: disable-next=unused-argument
def callback(r, w, e, user_data):
print("Callback called", r, w, e)
print(r[0].read())
if __name__ == "__main__":
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("localhost", 5555))
f = s.makefile()
ss = NonBlockingFile(f)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2.connect(("localhost", 5556))
f = s2.makefile()
ss.set_callback([f], [], [], None, callback)
xx = ss.read()
print(len(xx))
07070100000010000081B4000000000000000000000001666AB47A00000EE7000000000000000000000000000000000000001600000000rhnlib/rhnLockfile.py# pylint: disable=missing-module-docstring,invalid-name
#
# Copyright (c) 2008--2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import os
import sys
import fcntl
from errno import EWOULDBLOCK, EEXIST
from rhn.stringutils import bstr
# pylint: disable-next=reimported,ungrouped-imports
import fcntl
class LockfileLockedException(Exception):
"""thrown ONLY when pid file is locked."""
pass
class Lockfile:
"""class that provides simple access to a PID-style lockfile.
methods: __init__(lockfile), acquire(), and release()
NOTE: currently acquires upon init
The *.pid file will be acquired, or an LockfileLockedException is raised.
"""
def __init__(self, lockfile, pid=None):
"""create (if need be), and acquire lock on lockfile
lockfile example: '/var/run/up2date.pid'
"""
# cleanup the path and assign it.
self.lockfile = os.path.abspath(
os.path.expanduser(os.path.expandvars(lockfile))
)
self.pid = pid
if not self.pid:
self.pid = os.getpid()
# create the directory structure
dirname = os.path.dirname(self.lockfile)
if not os.path.exists(dirname):
try:
os.makedirs(dirname)
except OSError:
e = sys.exc_info()[1]
if hasattr(e, "errno") and e.errno == EEXIST:
# race condition... dirname exists now.
pass
else:
raise
# open the file -- non-destructive read-write, unless it needs
# to be created XXX: potential race condition upon create?
self.f = os.open(self.lockfile, os.O_RDWR | os.O_CREAT | os.O_SYNC)
self.acquire()
def acquire(self):
"""acquire the lock; else raise LockfileLockedException."""
try:
fcntl.flock(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
if sys.exc_info()[1].errno == EWOULDBLOCK:
# pylint: disable-next=raise-missing-from
raise LockfileLockedException(
# pylint: disable-next=consider-using-f-string
"cannot acquire lock on %s." % self.lockfile,
None,
sys.exc_info()[2],
)
else:
raise
# unlock upon exit
fcntl.fcntl(self.f, fcntl.F_SETFD, 1)
# truncate and write the pid
os.ftruncate(self.f, 0)
os.write(self.f, bstr(str(self.pid) + "\n"))
def release(self):
# Remove the lock file
os.unlink(self.lockfile)
fcntl.flock(self.f, fcntl.LOCK_UN)
os.close(self.f)
def main():
"""test code"""
try:
L = Lockfile("./test.pid")
except LockfileLockedException:
# pylint: disable-next=consider-using-f-string
sys.stderr.write("%s\n" % sys.exc_info()[1])
sys.exit(-1)
else:
print("lock acquired ")
print("...sleeping for 10 seconds")
# pylint: disable-next=import-outside-toplevel
import time
time.sleep(10)
L.release()
print("lock released ")
if __name__ == "__main__":
# test code
sys.exit(main() or 0)
07070100000011000081B4000000000000000000000001666AB47A00002EFF000000000000000000000000000000000000001600000000rhnlib/rhnlib.changes-------------------------------------------------------------------
Thu Jun 13 10:57:19 CEST 2024 - marina.latini@suse.com
- version 5.0.3-1
* Align changelog entries
-------------------------------------------------------------------
Mon Jan 29 11:43:30 CET 2024 - rosuna@suse.com
- version 5.0.2-1
* Apply Black and Pylint to enforce Python style
-------------------------------------------------------------------
Tue Jan 16 08:20:29 CET 2024 - jgonzalez@suse.com
- version 5.0.1-1
* Specify a packager for Debian like distros
-------------------------------------------------------------------
Fri Dec 15 16:54:45 CET 2023 - rosuna@suse.com
- version 4.4.6-1
* Remove unused makefiles
-------------------------------------------------------------------
Wed Nov 01 20:56:07 CET 2023 - marina.latini@suse.com
- version 4.4.5-1
* Use bundle CA certificate in rhnpush (bsc#1222731)
-------------------------------------------------------------------
Mon Sep 18 14:13:24 CEST 2023 - rosuna@suse.com
- version 4.4.4-1
* Only use TLSv1+ for SSL connections
-------------------------------------------------------------------
Tue Feb 21 14:04:09 CET 2023 - jgonzalez@suse.com
- version 4.4.3-1
* Ensure installation of make for building
-------------------------------------------------------------------
Fri Nov 18 15:05:05 CET 2022 - jgonzalez@suse.com
- version 4.4.2-1
* Don't get stuck at the end of SSL transfers (bsc#1204032)
-------------------------------------------------------------------
Wed Sep 28 11:06:09 CEST 2022 - jgonzalez@suse.com
- version 4.4.1-1
* Bump version to 4.4.0
-------------------------------------------------------------------
Tue Apr 19 11:54:48 CEST 2022 - jgonzalez@suse.com
- version 4.3.4-1
* Fix the condition for preventing building python 2 subpackage
for SLE15
-------------------------------------------------------------------
Fri Mar 11 15:44:41 CET 2022 - jgonzalez@suse.com
- version 4.3.3-1
* Reorganize python files
-------------------------------------------------------------------
Tue Jan 18 13:36:55 CET 2022 - jgonzalez@suse.com
- version 4.3.2-1
* do not build python 2 package for SLE15
-------------------------------------------------------------------
Mon Aug 09 10:56:00 CEST 2021 - jgonzalez@suse.com
- version 4.3.1-1
- Bump version to 4.3.0
-------------------------------------------------------------------
Fri Apr 16 13:14:01 CEST 2021 - jgonzalez@suse.com
- version 4.2.3-1
- require missing python-backports.ssl_match_hostname on SLE 11 (bsc#1183959)
-------------------------------------------------------------------
Wed Feb 17 12:16:30 CET 2021 - jgonzalez@suse.com
- version 4.2.2-1
- change SSL implementation to python ssl for better SAN and
hostname matching support (bsc#1181807)
-------------------------------------------------------------------
Fri Sep 18 11:29:26 CEST 2020 - jgonzalez@suse.com
- version 4.2.1-1
- Update package version to 4.2.0
-------------------------------------------------------------------
Wed Mar 11 10:47:57 CET 2020 - jgonzalez@suse.com
- version 4.1.2-1
- Fix building
-------------------------------------------------------------------
Wed Nov 27 16:57:04 CET 2019 - jgonzalez@suse.com
- version 4.1.1-1
- Fix malformed XML response when data contains non-ASCII chars (bsc#1154968)
- Bump version to 4.1.0 (bsc#1154940)
- fix initialize ssl connection (bsc#1144155)
- Add SNI support for clients
- Fix bootstrapping SLE11SP4 trad client with SSL enabled (bsc#1148177)
-------------------------------------------------------------------
Wed May 15 15:04:15 CEST 2019 - jgonzalez@suse.com
- version 4.0.8-1
- SPEC cleanup
-------------------------------------------------------------------
Mon Apr 22 12:05:29 CEST 2019 - jgonzalez@suse.com
- version 4.0.7-1
- Add makefile and pylint configuration for PyLint
-------------------------------------------------------------------
Fri Mar 29 15:08:26 CET 2019 - jgonzalez@suse.com
- version 4.0.6-1
- Add group to python*-rhnlib to fix building at SLE11
-------------------------------------------------------------------
Sat Mar 02 00:08:46 CET 2019 - jgonzalez@suse.com
- version 4.0.5-1
- Read SSL decoded buffer completely when no pending bytes on the
underlying connection.
-------------------------------------------------------------------
Wed Feb 27 12:58:36 CET 2019 - jgonzalez@suse.com
- version 4.0.4-1
- Fix encoding issues after porting to Python 3.
-------------------------------------------------------------------
Thu Jan 31 09:38:49 CET 2019 - jgonzalez@suse.com
- version 4.0.3-1
- Sync changes from Spacewalk
- 1652859 - python3 http.client does not contain _set_hostport()
-------------------------------------------------------------------
Fri Oct 26 10:02:33 CEST 2018 - jgonzalez@suse.com
- version 4.0.2-1
- use rpm for debian packaging
-------------------------------------------------------------------
Fri Aug 10 15:09:47 CEST 2018 - jgonzalez@suse.com
- version 4.0.1-1
- Bump version to 4.0.0 (bsc#1104034)
- Fix copyright for the package specfile (bsc#1103696)
-------------------------------------------------------------------
Mon May 07 14:57:08 CEST 2018 - jgonzalez@suse.com
- version 2.8.11.1-1
- Sync with upstream (bsc#1083294)
-------------------------------------------------------------------
Mon Mar 26 08:38:18 CEST 2018 - jgonzalez@suse.com
- version 2.8.10.1-1
- Sync with upstream (bsc#1083294)
-------------------------------------------------------------------
Mon Mar 05 08:40:18 CET 2018 - jgonzalez@suse.com
- version 2.8.7.2-1
- remove empty clean section from spec (bsc#1083294)
-------------------------------------------------------------------
Fri Feb 23 10:47:01 CET 2018 - jgonzalez@suse.com
- version 2.8.7.1-1
- Sync with upstream
-------------------------------------------------------------------
Fri Feb 02 11:56:38 CET 2018 - jgonzalez@suse.com
- version 2.8.5.3-1
- replace netstat with ss command
-------------------------------------------------------------------
Wed Jan 17 11:13:43 CET 2018 - jgonzalez@suse.com
- version 2.8.5.2-1
- Fix update mechanism when updating the updateservice (bsc#1073619)
-------------------------------------------------------------------
Fri Nov 10 16:25:29 CET 2017 - mc@suse.de
- version 2.8.5.1-1
- merge back upstream changes
-------------------------------------------------------------------
Thu Oct 26 16:44:28 CEST 2017 - mc@suse.de
- version 2.8.4.1-1
- build python3 package
- check a state of handshake before shutdown SSL connection
- Python's OpenSSL.SSL.Connection method for getting state was renamed.
-------------------------------------------------------------------
Wed May 03 16:38:11 CEST 2017 - michele.bologna@suse.com
- version 2.7.2.1-1
-------------------------------------------------------------------
Fri Mar 31 09:33:33 CEST 2017 - mc@suse.de
- version 2.7.1.3-1
- support all TLS versions in rpclib (bsc#1025312)
-------------------------------------------------------------------
Mon Mar 06 16:19:18 CET 2017 - mc@suse.de
- version 2.7.1.2-1
- Updated links to github in spec files
-------------------------------------------------------------------
Wed Jan 11 15:31:19 CET 2017 - michele.bologna@suse.com
- version 2.7.1.1-1
- Version 2.7.1-1
-------------------------------------------------------------------
Thu Oct 06 15:08:47 CEST 2016 - mc@suse.de
- version 2.5.84.3-1
- Add function aliases for backward compatibility (bsc#998185)
-------------------------------------------------------------------
Mon Mar 21 16:41:39 CET 2016 - mc@suse.de
- version 2.5.84.2-1
- use TLSv1_METHOD in SSL Context (bsc#970989)
-------------------------------------------------------------------
Wed Mar 02 11:13:30 CET 2016 - mc@suse.de
- version 2.5.84.1-1
- just set one required attribute to be compatible with all
xmlrpclib versions
- fixed SyntaxError: invalid syntax ' .. = b ' to work in
python 2.4
-------------------------------------------------------------------
Tue Jan 26 13:53:44 CET 2016 - mc@suse.de
- version 2.5.83.1-1
- change encoding for python 2
- os.write wants buffer
- xmlrpc can't convert map objects
- dict.iteritems() is not in python3
- perform decoding on byte string
- modified exception raising to work in python2/3
- fixed string module calls to work in python3
-------------------------------------------------------------------
Sat Jan 16 11:31:43 CET 2016 - mc@suse.de
- version 2.5.81.1-1
- make rhnlib compatible with python 2 and 3
-------------------------------------------------------------------
Mon Nov 30 10:48:24 CET 2015 - mc@suse.de
- version 2.5.77.2-1
- python 2.7 httplib changed function _set_hostport to _get_hostport
-------------------------------------------------------------------
Wed Oct 07 15:09:31 CEST 2015 - mc@suse.de
- version 2.5.77.1-1
- python 3 compatibility
- SmartIO: don't use tmpDir configuration from /etc/sysconfig/rhn/up2date
-------------------------------------------------------------------
Tue Mar 31 14:30:45 CEST 2015 - mc@suse.de
- version 2.5.69.7-1
- Documentation changes - fix name and refer to RFC.
-------------------------------------------------------------------
Tue Feb 03 12:42:14 CET 2015 - mc@suse.de
- version 2.5.69.6-1
- Getting rid of Tabs and trailing spaces
-------------------------------------------------------------------
Thu Dec 18 13:50:31 CET 2014 - mc@suse.de
- version 2.5.69.5-1
-------------------------------------------------------------------
Wed Sep 3 01:41:38 CEST 2014 - ro@suse.de
- sanitize release line in specfile
-------------------------------------------------------------------
Tue Jun 17 11:02:20 CEST 2014 - jrenner@suse.de
- version 2.5.69.4-1
- Ensure bytes strings are sent to pyOpenSSL (bnc#880388)
-------------------------------------------------------------------
Thu Apr 10 17:30:43 CEST 2014 - mc@suse.de
- version 2.5.69.3-1
-------------------------------------------------------------------
Thu Apr 10 17:30:28 CEST 2014 - mc@suse.de
- fix release in specfile for SLE12 (bnc#872970)
-------------------------------------------------------------------
Mon Jan 20 15:16:29 CET 2014 - mc@suse.de
- version 2.5.69.2-1
- change license to SPDX format
-------------------------------------------------------------------
Mon Dec 09 16:05:46 CET 2013 - mc@suse.de
- version 2.5.69.1-1
- switch to 2.1
-------------------------------------------------------------------
Thu Nov 28 16:27:53 CET 2013 - mc@suse.de
- version 2.5.51.6-1
- fixed UserDictCase behaviour when key is not found
-------------------------------------------------------------------
Wed Aug 21 15:37:30 CEST 2013 - mc@suse.de
- version 2.5.51.5-1
- timeout fixes
-------------------------------------------------------------------
Mon Apr 22 15:44:27 CEST 2013 - mc@suse.de
- version 2.5.51.4-1
- Make timeout of yum-rhn-plugin calls through rhn-client-tools
configurable (bnc#815460)
- make Proxy timeouts configurable (bnc#815460)
-------------------------------------------------------------------
Thu Jun 21 11:34:34 CEST 2012 - jrenner@suse.de
- version 2.5.51.3-1
- allow linking against openssl
-------------------------------------------------------------------
Fri Apr 27 16:56:23 CEST 2012 - mc@suse.de
- version 2.5.51.2-1
- fixing typos
-------------------------------------------------------------------
Wed Mar 21 17:06:43 CET 2012 - mc@suse.de
- version 2.5.51.1-1
- Bumping package version
-------------------------------------------------------------------
Mon Sep 19 15:36:29 CEST 2011 - mc@suse.de
- require python-openssl on SUSE
-------------------------------------------------------------------
Sat Jan 22 12:39:39 CET 2011 - mc@suse.de
- fix macros
-------------------------------------------------------------------
Tue Sep 14 17:56:57 CEST 2010 - mantel@suse.de
- Initial release of rhnlib
-------------------------------------------------------------------
07070100000012000081B4000000000000000000000001666AB47A000000C6000000000000000000000000000000000000001600000000rhnlib/rhnlib.pkginfoPKG="RHATrhnl"
NAME="Red Hat update agent"
VERSION="1.0"
ARCH="sparc"
CLASSES="none"
CATEGORY="utility"
VENDOR="RHAT"
EMAIL="alikins@redhat.com"
ISTATES="S s 1 2 3"
RSTATES="S s 1 2 3"
BASEDIR="/"
07070100000013000081B4000000000000000000000001666AB47A00001782000000000000000000000000000000000000001300000000rhnlib/rhnlib.spec#
# spec file for package rhnlib
#
# Copyright (c) 2024 SUSE LLC
# Copyright (c) 2008-2018 Red Hat, Inc.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
%if 0%{?fedora} || 0%{?suse_version} > 1320 || 0%{?rhel} >= 8
%global build_py3 1
%{!?__python3:%global __python3 /usr/bin/python3}
%if %{undefined python3_sitelib}
%global python3_sitelib %(%{__python3} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
%endif
%endif
%if !(0%{?rhel} >= 8 || 0%{?sle_version} >= 150000 )
%global build_py2 1
%{!?__python2:%global __python2 /usr/bin/python2}
%if %{undefined python2_sitelib}
%global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
%endif
%endif
%if "%{_vendor}" == "debbuild"
# For making sure we can set the right args for deb distros
%global is_deb 1
%endif
Summary: Python libraries for the Spacewalk project
License: GPL-2.0-only
Name: rhnlib
Version: 5.0.3
Release: 1
%if "%{_vendor}" == "debbuild"
Group: python
Packager: Uyuni Project <devel@lists.uyuni-project.org>
%else
Group: Development/Libraries
%endif
URL: https://github.com/uyuni-project/uyuni
Source0: %{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-build
%if 0%{?fedora} || 0%{?rhel} || 0%{?suse_version} >= 1210
BuildArch: noarch
%endif
BuildRequires: make
%description
rhnlib is a collection of python modules used by the Spacewalk (http://spacewalk.redhat.com) software.
%if 0%{?build_py2}
%package -n python2-rhnlib
Summary: Python libraries for the Spacewalk project
Group: Development/Libraries
%if "%{_vendor}" != "debbuild"
%if 0%{?fedora} >= 28 || 0%{?rhel} >= 8
BuildRequires: python2-devel
Requires: python2-pyOpenSSL
%else
BuildRequires: python-devel
%if 0%{?suse_version}
%if 0%{?suse_version} > 1200
Requires: python-pyOpenSSL
%else
Requires: python-backports.ssl_match_hostname
Requires: python-openssl
%endif # 0{?suse_version} > 1200
%else
Requires: pyOpenSSL
%endif # 0{?suse_version}
%endif # 0{?fedora} >= 28 || 0{?rhel} >= 8
%endif # {_vendor} != "debbuild"
%if "%{_vendor}" == "debbuild"
BuildRequires: python-dev
BuildRequires: rpm
Requires(preun):python-minimal
Requires(post): python-minimal
Requires: python-openssl
Obsoletes: python-rhn
Conflicts: python-rhn
%endif
Conflicts: rhn-client-tools < 1.3.3
Conflicts: rhn-custom-info < 5.4.7
Conflicts: rhncfg < 5.10.45
Conflicts: rhnclient < 0.10
Conflicts: rhnpush < 5.5.10
Conflicts: spacewalk-proxy < 1.3.6
Conflicts: spacewalk-proxy-installer < 1.3.2
Provides: rhnlib = %{version}-%{release}
Obsoletes: rhnlib < %{version}-%{release}
%description -n python2-rhnlib
rhnlib is a collection of python modules used by the Spacewalk software.
%endif # 0%{?build_py2}
%if 0%{?build_py3}
%package -n python3-rhnlib
Summary: Python libraries for the Spacewalk project
Group: python
%if "%{_vendor}" != "debbuild"
BuildRequires: python3-devel
%if 0%{?suse_version}
BuildRequires: python-rpm-macros
%endif
%endif
Requires: python3-pyOpenSSL
%if "%{_vendor}" == "debbuild"
BuildRequires: python3-dev
BuildRequires: rpm
Requires(preun):python3-minimal
Requires(post): python3-minimal
Requires: python3-openssl
%endif
Conflicts: rhn-client-tools < 1.3.3
Conflicts: rhn-custom-info < 5.4.7
Conflicts: rhncfg < 5.10.45
Conflicts: rhnclient < 0.10
Conflicts: rhnpush < 5.5.10
Conflicts: spacewalk-proxy < 1.3.6
Conflicts: spacewalk-proxy-installer < 1.3.2
%description -n python3-rhnlib
rhnlib is a collection of python modules used by the Spacewalk software.
%endif # 0%{?build_py2}
%prep
%setup -q
# Recreate the rhn module
mkdir rhn
pushd rhn
for pyfile in $(ls ../*.py)
do
ln -s $pyfile
done
popd
if [ ! -e setup.py ]; then
sed -e 's/@VERSION@/%{version}/' -e 's/@NAME@/%{name}/' setup.py.in > setup.py
fi
if [ ! -e setup.cfg ]; then
sed 's/@RELEASE@/%{release}/' setup.cfg.in > setup.cfg
fi
%build
%if 0%{?build_py2}
make -f Makefile.rhnlib PYTHON=%{__python2}
%endif
%if 0%{?build_py3}
make -f Makefile.rhnlib PYTHON=%{__python3}
%endif
%install
%if 0%{?build_py2}
%{__python2} setup.py install %{!?is_deb:-O1}%{?is_deb:--no-compile -O0} --skip-build --root $RPM_BUILD_ROOT %{?is_deb:--install-layout=deb} --prefix=%{_prefix}
%endif
%if 0%{?build_py3}
%{__python3} setup.py install %{!?is_deb:-O1}%{?is_deb:--no-compile -O0} --skip-build --root $RPM_BUILD_ROOT %{?is_deb:--install-layout=deb} --prefix=%{_prefix}
%endif
%if 0%{?build_py2}
%files -n python2-rhnlib
%defattr(-,root,root)
%doc ChangeLog COPYING README TODO
%{python2_sitelib}/*
%endif
%if 0%{?build_py3}
%files -n python3-rhnlib
%doc ChangeLog COPYING README TODO
%{python3_sitelib}/*
%endif
%if "%{_vendor}" == "debbuild"
%post -n python2-rhnlib
# Do late-stage bytecompilation, per debian policy
pycompile -p python2-rhnlib -V -3.0
%preun -n python2-rhnlib
# Ensure all *.py[co] files are deleted, per debian policy
pyclean -p python2-rhnlib
%if 0%{?build_py3}
%post -n python3-rhnlib
# Do late-stage bytecompilation, per debian policy
py3compile -p python3-rhnlib -V -4.0
%preun -n python3-rhnlib
# Ensure all *.py[co] files are deleted, per debian policy
py3clean -p python3-rhnlib
%endif
%endif
%changelog
07070100000014000081B4000000000000000000000001666AB47A00006A72000000000000000000000000000000000000001100000000rhnlib/rpclib.py# pylint: disable=missing-module-docstring
#
# This module contains all the RPC-related functions the RHN code uses
#
# Copyright (c) 2005--2018 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
__version__ = "$Revision$"
import socket
import re
import sys
from rhn import transports
from rhn.stringutils import sstr
from rhn.UserDictCase import UserDictCase
try: # python2
import xmlrpclib
from types import (
ListType,
TupleType,
StringType,
UnicodeType,
DictType,
DictionaryType,
)
from urllib import splittype, splithost
except ImportError: # python3
import xmlrpc.client as xmlrpclib
ListType = list
TupleType = tuple
StringType = bytes
UnicodeType = str
DictType = dict
DictionaryType = dict
# pylint: disable-next=ungrouped-imports
from urllib.parse import splittype, splithost
# Redirection handling
MAX_REDIRECTIONS = 5
def check_ipv6(n):
"""Returns true if n is IPv6 address, false otherwise."""
try:
socket.inet_pton(socket.AF_INET6, n)
return True
# pylint: disable-next=bare-except
except:
return False
def split_host(hoststring):
"""Function used to split host information in an URL per RFC 2396
handle full hostname like user:passwd@host:port
"""
l = hoststring.split("@", 1)
host = None
port = None
user = None
passwd = None
if len(l) == 2:
hostport = l[1]
# userinfo present
userinfo = l[0].split(":", 1)
user = userinfo[0]
if len(userinfo) == 2:
passwd = userinfo[1]
else:
hostport = l[0]
# Now parse hostport
if hostport[0] == "[":
# IPv6 with port
# pylint: disable-next=anomalous-backslash-in-string
host, port = re.split("(?<=\]):", hostport, 1)
host = host.lstrip("[").rstrip("]")
elif check_ipv6(hostport):
# just IPv6
host = hostport
else:
# IPv4
arr = hostport.split(":", 1)
host = arr[0]
if len(arr) == 2:
port = arr[1]
return (host, port, user, passwd)
def get_proxy_info(proxy):
# pylint: disable-next=singleton-comparison
if proxy == None:
raise ValueError("Host string cannot be null")
arr = proxy.split("://", 1)
if len(arr) == 2:
# scheme found, strip it
proxy = arr[1]
return split_host(proxy)
class MalformedURIError(IOError):
pass
# Originaly taken from xmlrpclib.ServerProxy, now changed most of the code
class Server:
"""uri [,options] -> a logical connection to an XML-RPC server
uri is the connection point on the server, given as
scheme://host/target.
The standard implementation always supports the "http" scheme. If
SSL socket support is available (Python 2.0), it also supports
"https".
If the target part and the slash preceding it are both omitted,
"/RPC2" is assumed.
The following options can be given as keyword arguments:
transport: a transport factory
encoding: the request encoding (default is UTF-8)
verbose: verbosity level
proxy: use an HTTP proxy
username: username for authenticated HTTP proxy
password: password for authenticated HTTP proxy
All 8-bit strings passed to the server proxy are assumed to use
the given encoding.
"""
# Default factories
_transport_class = transports.Transport
_transport_class_https = transports.SafeTransport
_transport_class_proxy = transports.ProxyTransport
_transport_class_https_proxy = transports.SafeProxyTransport
def __init__(
self,
uri,
transport=None,
encoding=None,
verbose=0,
proxy=None,
username=None,
password=None,
# pylint: disable-next=invalid-name
refreshCallback=None,
# pylint: disable-next=invalid-name
progressCallback=None,
timeout=None,
):
# establish a "logical" server connection
#
# First parse the proxy information if available
#
# pylint: disable-next=singleton-comparison
if proxy != None:
(ph, pp, pu, pw) = get_proxy_info(proxy)
if pp is not None:
# pylint: disable-next=consider-using-f-string
proxy = "%s:%s" % (ph, pp)
else:
proxy = ph
# username and password will override whatever was passed in the
# URL
if pu is not None and username is None:
username = pu
if pw is not None and password is None:
password = pw
self._uri = sstr(uri)
# pylint: disable-next=invalid-name
self._refreshCallback = None
# pylint: disable-next=invalid-name
self._progressCallback = None
# pylint: disable-next=invalid-name
self._bufferSize = None
self._proxy = proxy
self._username = username
self._password = password
self._timeout = timeout
if len(__version__.split()) > 1:
self.rpc_version = __version__.split()[1]
else:
self.rpc_version = __version__
self._reset_host_handler_and_type()
if transport is None:
self._allow_redirect = 1
transport = self.default_transport(
self._type, proxy, username, password, timeout
)
else:
#
# dont allow redirect on unknow transports, that should be
# set up independantly
#
self._allow_redirect = 0
self._redirected = None
self.use_handler_path = 1
self._transport = transport
self._trusted_cert_files = []
self._lang = None
self._encoding = encoding
self._verbose = verbose
self.set_refresh_callback(refreshCallback)
self.set_progress_callback(progressCallback)
# referer, which redirect us to new handler
self.send_handler = None
self._headers = UserDictCase()
def default_transport(
self,
# pylint: disable-next=redefined-builtin
type,
proxy=None,
username=None,
password=None,
timeout=None,
):
if proxy:
if type == "https":
transport = self._transport_class_https_proxy(
proxy,
proxyUsername=username,
proxyPassword=password,
timeout=timeout,
)
else:
transport = self._transport_class_proxy(
proxy,
proxyUsername=username,
proxyPassword=password,
timeout=timeout,
)
else:
if type == "https":
transport = self._transport_class_https(timeout=timeout)
else:
transport = self._transport_class(timeout=timeout)
return transport
def allow_redirect(self, allow):
self._allow_redirect = allow
def redirected(self):
if not self._allow_redirect:
return None
return self._redirected
# pylint: disable-next=invalid-name
def set_refresh_callback(self, refreshCallback):
self._refreshCallback = refreshCallback
self._transport.set_refresh_callback(refreshCallback)
# pylint: disable-next=invalid-name
def set_buffer_size(self, bufferSize):
self._bufferSize = bufferSize
self._transport.set_buffer_size(bufferSize)
# pylint: disable-next=invalid-name
def set_progress_callback(self, progressCallback, bufferSize=16384):
self._progressCallback = progressCallback
self._transport.set_progress_callback(progressCallback, bufferSize)
def _req_body(self, params, methodname):
return xmlrpclib.dumps(params, methodname, encoding=self._encoding)
def get_response_headers(self):
if self._transport:
return self._transport.headers_in
return None
def get_response_status(self):
if self._transport:
return self._transport.response_status
return None
def get_response_reason(self):
if self._transport:
return self._transport.response_reason
return None
def get_content_range(self):
"""Returns a dictionary with three values:
length: the total length of the entity-body (can be None)
first_byte_pos: the position of the first byte (zero based)
last_byte_pos: the position of the last byte (zero based)
The range is inclusive; that is, a response 8-9/102 means two bytes
"""
headers = self.get_response_headers()
if not headers:
return None
content_range = headers.get("Content-Range")
if not content_range:
return None
arr = list(filter(None, content_range.split()))
assert arr[0] == "bytes"
assert len(arr) == 2
arr = arr[1].split("/")
assert len(arr) == 2
brange, total_len = arr
if total_len == "*":
# Per RFC, the server is allowed to use * if the length of the
# entity-body is unknown or difficult to determine
total_len = None
else:
total_len = int(total_len)
start, end = brange.split("-")
result = {
"length": total_len,
"first_byte_pos": int(start),
"last_byte_pos": int(end),
}
return result
def accept_ranges(self):
headers = self.get_response_headers()
if not headers:
return None
if "Accept-Ranges" in headers:
return headers["Accept-Ranges"]
return None
def _reset_host_handler_and_type(self):
"""Reset the attributes:
self._host, self._handler, self._type
according the value of self._uri.
"""
# get the url
# pylint: disable-next=redefined-builtin
type, uri = splittype(self._uri)
if type is None:
raise MalformedURIError("missing protocol in uri")
# with a real uri passed in, uri will now contain "//hostname..." so we
# need at least 3 chars for it to maybe be ok...
if len(uri) < 3 or uri[0:2] != "//":
raise MalformedURIError
self._type = type.lower()
if self._type not in ("http", "https"):
raise IOError("unsupported XML-RPC protocol")
self._host, self._handler = splithost(uri)
if not self._handler:
self._handler = "/RPC2"
def _strip_characters(self, *args):
"""Strip characters, which are not allowed according:
http://www.w3.org/TR/2006/REC-xml-20060816/#charsets
From spec:
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */
"""
regexp = r"[\x00-\x09]|[\x0b-\x0c]|[\x0e-\x1f]"
result = []
for item in args:
item_type = type(item)
if item_type == StringType or item_type == UnicodeType:
item = re.sub(regexp, "", sstr(item))
elif item_type == TupleType:
item = tuple(self._strip_characters(i) for i in item)
elif item_type == ListType:
item = [self._strip_characters(i) for i in item]
elif item_type == DictType or item_type == DictionaryType:
item = dict(
[(self._strip_characters(name, val)) for name, val in item.items()]
)
# else: some object - should take care of himself
# numbers - are safe
result.append(item)
if len(result) == 1:
return result[0]
else:
return tuple(result)
def _request(self, methodname, params):
"""Call a method on the remote server
we can handle redirections."""
# the loop is used to handle redirections
redirect_response = 0
retry = 0
self._reset_host_handler_and_type()
while 1:
if retry >= MAX_REDIRECTIONS:
raise InvalidRedirectionError("Unable to fetch requested Package")
# Clear the transport headers first
self._transport.clear_headers()
for k, v in self._headers.items():
self._transport.set_header(k, v)
self._transport.add_header(
"X-Info",
# pylint: disable-next=consider-using-f-string
"RPC Processor (C) Red Hat, Inc (version %s)" % self.rpc_version,
)
# identify the capability set of this client to the server
self._transport.set_header("X-Client-Version", 1)
if self._allow_redirect:
# Advertise that we follow redirects
# changing the version from 1 to 2 to support backward compatibility
self._transport.add_header(
"X-RHN-Transport-Capability", "follow-redirects=3"
)
if redirect_response:
self._transport.add_header("X-RHN-Redirect", "0")
if self.send_handler:
self._transport.add_header("X-RHN-Path", self.send_handler)
request = self._req_body(self._strip_characters(params), methodname)
try:
response = self._transport.request(
self._host, self._handler, request, verbose=self._verbose
)
save_response = self._transport.response_status
except xmlrpclib.ProtocolError:
if self.use_handler_path:
raise
else:
save_response = sys.exc_info()[1].errcode
self._redirected = None
retry += 1
if save_response == 200:
# exit redirects loop and return response
break
elif save_response not in (301, 302):
# Retry pkg fetch
self.use_handler_path = 1
continue
# rest of loop is run only if we are redirected (301, 302)
self._redirected = self._transport.redirected()
self.use_handler_path = 0
redirect_response = 1
if not self._allow_redirect:
raise InvalidRedirectionError("Redirects not allowed")
if self._verbose:
# pylint: disable-next=consider-using-f-string
print("%s redirected to %s" % (self._uri, self._redirected))
typ, uri = splittype(self._redirected)
# pylint: disable-next=singleton-comparison
if typ != None:
typ = typ.lower()
if typ not in ("http", "https"):
raise InvalidRedirectionError(
# pylint: disable-next=consider-using-f-string
"Redirected to unsupported protocol %s"
% typ
)
#
# We forbid HTTPS -> HTTP for security reasons
# Note that HTTP -> HTTPS -> HTTP is allowed (because we compare
# the protocol for the redirect with the original one)
#
if self._type == "https" and typ == "http":
raise InvalidRedirectionError(
"HTTPS redirected to HTTP is not supported"
)
self._host, self._handler = splithost(uri)
if not self._handler:
self._handler = "/RPC2"
# Create a new transport for the redirected service and
# set up the parameters on the new transport
del self._transport
self._transport = self.default_transport(
typ, self._proxy, self._username, self._password, self._timeout
)
self.set_progress_callback(self._progressCallback)
self.set_refresh_callback(self._refreshCallback)
self.set_buffer_size(self._bufferSize)
self.setlang(self._lang)
# pylint: disable-next=use-implicit-booleaness-not-comparison
if self._trusted_cert_files != [] and hasattr(
self._transport, "add_trusted_cert"
):
for certfile in self._trusted_cert_files:
self._transport.add_trusted_cert(certfile)
# Then restart the loop to try the new entry point.
if isinstance(response, transports.File):
# Just return the file
return response
# an XML-RPC encoded data structure
if isinstance(response, TupleType) and len(response) == 1:
response = response[0]
return response
def __repr__(self):
# pylint: disable-next=consider-using-f-string
return "<%s for %s%s>" % (self.__class__.__name__, self._host, self._handler)
__str__ = __repr__
def __getattr__(self, name):
# magic method dispatcher
return _Method(self._request, name)
# note: to call a remote object with an non-standard name, use
# result getattr(server, "strange-python-name")(args)
def set_transport_flags(self, transfer=0, encoding=0, **kwargs):
if not self._transport:
# Nothing to do
return
kwargs.update(
{
"transfer": transfer,
"encoding": encoding,
}
)
self._transport.set_transport_flags(**kwargs)
def get_transport_flags(self):
if not self._transport:
# Nothing to do
return {}
return self._transport.get_transport_flags()
def reset_transport_flags(self):
# Does nothing
pass
# Allow user-defined additional headers.
def set_header(self, name, arg):
if type(arg) in [type([]), type(())]:
# Multivalued header
self._headers[name] = [str(a) for a in arg]
else:
self._headers[name] = str(arg)
def add_header(self, name, arg):
if name in self._headers:
vlist = self._headers[name]
if not isinstance(vlist, ListType):
vlist = [vlist]
else:
vlist = self._headers[name] = []
vlist.append(str(arg))
# Sets the i18n options
def setlang(self, lang):
self._lang = lang
if self._transport and hasattr(self._transport, "setlang"):
self._transport.setlang(lang)
# Sets the CA chain to be used
# pylint: disable-next=invalid-name
def use_CA_chain(self, ca_chain=None):
raise NotImplementedError("This method is deprecated")
def add_trusted_cert(self, certfile):
self._trusted_cert_files.append(certfile)
if self._transport and hasattr(self._transport, "add_trusted_cert"):
self._transport.add_trusted_cert(certfile)
def close(self):
if self._transport:
self._transport.close()
self._transport = None
# RHN GET server
# pylint: disable-next=missing-class-docstring
class GETServer(Server):
# pylint: disable-next=dangerous-default-value
def __init__(
self,
uri,
transport=None,
proxy=None,
username=None,
password=None,
client_version=2,
headers={},
refreshCallback=None,
progressCallback=None,
timeout=None,
):
Server.__init__(
self,
uri,
proxy=proxy,
username=username,
password=password,
transport=transport,
refreshCallback=refreshCallback,
progressCallback=progressCallback,
timeout=timeout,
)
self._client_version = client_version
self._headers = headers
# Back up the original handler, since we mangle it
self._orig_handler = self._handler
# Download resumption
self.set_range(offset=None, amount=None)
def _req_body(self, params, methodname):
if not params or len(params) < 1:
# pylint: disable-next=broad-exception-raised
raise Exception("Required parameter channel not found")
# Strip the multiple / from the handler
h_comps = list(filter(lambda x: x != "", self._orig_handler.split("/")))
# Set the handler we are going to request
hndl = h_comps + ["$RHN", params[0], methodname] + list(params[1:])
self._handler = "/" + "/".join(hndl)
# save the constructed handler in case of redirect
self.send_handler = self._handler
# Add headers
# override the handler to replace /XMLRPC with pkg path
if self._redirected and not self.use_handler_path:
self._handler = self._new_req_body()
for h, v in self._headers.items():
self._transport.set_header(h, v)
if self._offset is not None:
if self._offset >= 0:
brange = str(self._offset) + "-"
if self._amount is not None:
brange = brange + str(self._offset + self._amount - 1)
else:
# The last bytes
# amount is ignored in this case
brange = "-" + str(-self._offset)
self._transport.set_header("Range", "bytes=" + brange)
# Flag that we allow for partial content
self._transport.set_transport_flags(allow_partial_content=1)
# GET requests have empty body
return ""
def _new_req_body(self):
# pylint: disable-next=redefined-builtin,unused-variable
type, tmpuri = splittype(self._redirected)
# pylint: disable-next=unused-variable
site, handler = splithost(tmpuri)
return handler
def set_range(self, offset=None, amount=None):
if offset is not None:
try:
offset = int(offset)
except ValueError:
# Error
# pylint: disable-next=raise-missing-from
raise RangeError(
# pylint: disable-next=consider-using-f-string
"Invalid value `%s' for offset" % offset,
None,
sys.exc_info()[2],
)
if amount is not None:
try:
amount = int(amount)
except ValueError:
# Error
# pylint: disable-next=raise-missing-from
raise RangeError(
# pylint: disable-next=consider-using-f-string
"Invalid value `%s' for amount" % amount,
None,
sys.exc_info()[2],
)
if amount <= 0:
# pylint: disable-next=consider-using-f-string
raise RangeError("Invalid value `%s' for amount" % amount)
self._amount = amount
self._offset = offset
def reset_transport_flags(self):
self._transport.set_transport_flags(allow_partial_content=0)
def __getattr__(self, name):
# magic method dispatcher
return SlicingMethod(self._request, name)
def default_transport(
self,
# pylint: disable-next=redefined-builtin
type,
proxy=None,
username=None,
password=None,
timeout=None,
):
ret = Server.default_transport(
self,
type,
proxy=proxy,
username=username,
password=password,
timeout=timeout,
)
ret.set_method("GET")
return ret
class RangeError(Exception):
pass
class InvalidRedirectionError(Exception):
pass
# pylint: disable-next=invalid-name
def getHeaderValues(headers, name):
# pylint: disable-next=import-outside-toplevel
import mimetools
if not isinstance(headers, mimetools.Message):
if name in headers:
return [headers[name]]
return []
return [x.split(":", 1)[1].strip() for x in headers.getallmatchingheaders(name)]
class _Method:
"""some magic to bind an XML-RPC method to an RPC server.
supports "nested" methods (e.g. examples.getStateName)
"""
def __init__(self, send, name):
self._send = send
self._name = name
def __getattr__(self, name):
# pylint: disable-next=consider-using-f-string
return _Method(self._send, "%s.%s" % (self._name, name))
def __call__(self, *args):
return self._send(self._name, args)
def __repr__(self):
# pylint: disable-next=consider-using-f-string
return "<%s %s (%s)>" % (self.__class__.__name__, self._name, self._send)
__str__ = __repr__
class SlicingMethod(_Method):
"""
A "slicing method" allows for byte range requests
"""
def __init__(self, send, name):
_Method.__init__(self, send, name)
self._offset = None
def __getattr__(self, name):
# pylint: disable-next=consider-using-f-string
return SlicingMethod(self._send, "%s.%s" % (self._name, name))
def __call__(self, *args, **kwargs):
self._offset = kwargs.get("offset")
self._amount = kwargs.get("amount")
# im_self is a pointer to self, so we can modify the class underneath
try:
self._send.im_self.set_range(offset=self._offset, amount=self._amount)
except AttributeError:
pass
result = self._send(self._name, args)
# Reset "sticky" transport flags
try:
self._send.im_self.reset_transport_flags()
except AttributeError:
pass
return result
# pylint: disable-next=invalid-name
def reportError(headers):
"""Reports the error from the headers."""
errcode = 0
errmsg = ""
s = "X-RHN-Fault-Code"
if s in headers:
errcode = int(headers[s])
s = "X-RHN-Fault-String"
if s in headers:
# pylint: disable-next=invalid-name
_sList = getHeaderValues(headers, s)
if _sList:
# pylint: disable-next=invalid-name
_s = "".join(_sList)
# pylint: disable-next=import-outside-toplevel
import base64
# pylint: disable-next=consider-using-f-string
errmsg = "%s" % base64.decodestring(_s)
return errcode, errmsg
07070100000015000081B4000000000000000000000001666AB47A00000101000000000000000000000000000000000000001400000000rhnlib/setup.cfg.in# Configuration file for distutils
[bdist_rpm]
release = @RELEASE@
packager = Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>
requires = pyOpenSSL python
doc-files = ChangeLog COPYING README TODO
changelog = changelog.spec
build-requires = python-devel
07070100000016000081B4000000000000000000000001666AB47A000001E1000000000000000000000000000000000000001300000000rhnlib/setup.py.in#!/usr/bin/python
#
#
from distutils.core import setup
setup(name = "@NAME@",
version = "@VERSION@",
description = "Python libraries for the Spacewalk project",
long_description = """\
rhnlib is a collection of python modules used by the
Spacewalk (http://spacewalk.redhat.com) software.""",
author = 'Mihai Ibanescu',
author_email = 'misa@redhat.com',
url = 'http://rhn.redhat.com',
packages = ["rhn"],
license = "GPL",
)
07070100000017000081B4000000000000000000000001666AB47A000007A4000000000000000000000000000000000000001600000000rhnlib/stringutils.py# pylint: disable=missing-module-docstring
#
# This module contains all the RPC-related functions the RHN code uses
#
# Copyright (c) 2016 Red Hat, Inc.
# Copyright (c) 2022 SUSE, LLC
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
from sys import version_info
try:
PY3 = version_info.major >= 3
except AttributeError:
PY3 = False
def ustr(obj):
# converts object to unicode like object
if PY3: # python3
if isinstance(obj, str):
return obj
else:
return str(obj, "utf8", errors="ignore")
else: # python2
# pylint: disable-next=undefined-variable
if isinstance(obj, unicode):
return obj
# pylint: disable-next=undefined-variable
return unicode(obj, "utf8", "ignore")
def bstr(obj):
# converts object to bytes like object
if PY3: # python3
if isinstance(obj, bytes):
return obj
else:
return bytes(obj, "utf8", errors="ignore")
else: # python2
if isinstance(obj, str):
return obj
return str(obj.encode("utf8", "ignore"))
def sstr(obj):
# converts object to string
if PY3: # python3
if isinstance(obj, str):
return obj
else:
return str(obj, "utf8", errors="ignore")
else: # python2
if isinstance(obj, str):
return obj
return str(obj.encode("utf8", "ignore"))
07070100000018000081B4000000000000000000000001666AB47A000003A5000000000000000000000000000000000000000D00000000rhnlib/tb.py# pylint: disable=missing-module-docstring
#
# Copyright (c) 2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import sys
try:
PY3 = sys.version_info.major >= 3
except AttributeError:
PY3 = False
if PY3:
def raise_with_tb(e):
raise e
else:
# pylint: disable-next=exec-used
exec(
"""
def raise_with_tb(e):
raise e, None, sys.exc_info()[2]
"""
)
07070100000019000081B4000000000000000000000001666AB47A00008B9E000000000000000000000000000000000000001500000000rhnlib/transports.py# pylint: disable=missing-module-docstring
#
# Helper transport objects
#
# Copyright (c) 2002--2016 Red Hat, Inc.
#
# Author: Mihai Ibanescu <misa@redhat.com>
# Based on what was previously shipped as cgiwrap:
# - Cristian Gafton <gafton@redhat.com>
# - Erik Troan <ewt@redhat.com>
# Transport objects
import os
import sys
import time
from rhn import connections
from rhn.stringutils import sstr, bstr
from rhn.SmartIO import SmartIO
from rhn.UserDictCase import UserDictCase
try: # python2
import xmlrpclib
from types import IntType, StringType, ListType
except ImportError: # python3
import xmlrpc.client as xmlrpclib
IntType = int
StringType = bytes
ListType = list
__version__ = "$Revision$"
# XXX
COMPRESS_LEVEL = 6
# Exceptions
class NotProcessed(Exception):
pass
# pylint: disable-next=missing-class-docstring
class Transport(xmlrpclib.Transport):
# pylint: disable-next=consider-using-f-string
user_agent = "rhn.rpclib.py/%s" % __version__
# pylint: disable-next=super-init-not-called
def __init__(
self,
transfer=0,
encoding=0,
refreshCallback=None,
progressCallback=None,
use_datetime=None,
timeout=None,
):
self._use_builtin_types = False
self._transport_flags = {"transfer": 0, "encoding": 0}
self.set_transport_flags(transfer=transfer, encoding=encoding)
self._headers = UserDictCase()
self.verbose = 0
self.connection = None
self.method = "POST"
self._lang = None
# pylint: disable-next=invalid-name
self.refreshCallback = refreshCallback
# pylint: disable-next=invalid-name
self.progressCallback = progressCallback
# pylint: disable-next=invalid-name
self.bufferSize = 16384
self.headers_in = None
self.response_status = None
self.response_reason = None
self._redirected = None
self._use_datetime = use_datetime
self.timeout = timeout
# set the progress callback
# pylint: disable-next=invalid-name
def set_progress_callback(self, progressCallback, bufferSize=16384):
self.progressCallback = progressCallback
self.bufferSize = bufferSize
# set the refresh callback
# pylint: disable-next=invalid-name
def set_refresh_callback(self, refreshCallback):
self.refreshCallback = refreshCallback
# set the buffer size
# The bigger this is, the faster the read is, but the more seldom is the
# progress callback called
# pylint: disable-next=invalid-name
def set_buffer_size(self, bufferSize):
if bufferSize is None:
# No buffer size specified; go with 16k
bufferSize = 16384
self.bufferSize = bufferSize
# set the request method
def set_method(self, method):
if method not in ("GET", "POST"):
# pylint: disable-next=consider-using-f-string
raise IOError("Unknown request method %s" % method)
self.method = method
# reset the transport options
def set_transport_flags(self, transfer=None, encoding=None, **kwargs):
# For backwards compatibility, we keep transfer and encoding as
# positional parameters (they could come in as kwargs easily)
self._transport_flags.update(kwargs)
if transfer is not None:
self._transport_flags["transfer"] = transfer
if encoding is not None:
self._transport_flags["encoding"] = encoding
self.validate_transport_flags()
def get_transport_flags(self):
return self._transport_flags.copy()
def validate_transport_flags(self):
# Transfer and encoding are guaranteed to be there
transfer = self._transport_flags.get("transfer")
transfer = lookupTransfer(transfer, strict=1)
self._transport_flags["transfer"] = transfer
encoding = self._transport_flags.get("encoding")
encoding = lookupEncoding(encoding, strict=1)
self._transport_flags["encoding"] = encoding
# Add arbitrary additional headers.
def set_header(self, name, arg):
if type(arg) in [type([]), type(())]:
# Multivalued header
self._headers[name] = [str(a) for a in arg]
else:
self._headers[name] = str(arg)
def add_header(self, name, arg):
if name in self._headers:
vlist = self._headers[name]
if not isinstance(vlist, ListType):
vlist = [vlist]
else:
vlist = self._headers[name] = []
vlist.append(str(arg))
def clear_headers(self):
self._headers.clear()
def get_connection(self, host):
if self.verbose:
# pylint: disable-next=consider-using-f-string
print("Connecting via http to %s" % (host,))
if self.timeout:
return connections.HTTPConnection(host, timeout=self.timeout)
else:
return connections.HTTPConnection(host)
def request(self, host, handler, request_body, verbose=0):
# issue XML-RPC request
# XXX: automatically compute how to send depending on how much data
# you want to send
# XXX Deal with HTTP/1.1 if necessary
self.verbose = verbose
# implement BASIC HTTP AUTHENTICATION
# pylint: disable-next=unused-variable
host, extra_headers, x509 = self.get_host_info(host)
if not extra_headers:
extra_headers = []
# Establish the connection
connection = self.get_connection(host)
# Setting the user agent. Only interesting for SSL tunnels, in any
# other case the general headers are good enough.
connection.set_user_agent(self.user_agent)
if self.verbose:
connection.set_debuglevel(self.verbose - 1)
# Get the output object to push data with
req = Output(connection=connection, method=self.method)
req.set_transport_flags(**self._transport_flags)
# Add the extra headers
req.set_header("User-Agent", self.user_agent)
for header, value in list(self._headers.items()) + extra_headers:
# Output.set_header correctly deals with multivalued headers now
req.set_header(header, value)
# Content-Type
req.set_header("Content-Type", "text/xml")
req.process(request_body)
# Host and Content-Length are set by HTTP*Connection
for h in ["Content-Length", "Host"]:
req.clear_header(h)
headers, fd = req.send_http(host, handler)
if self.verbose:
print("Incoming headers:")
for header, value in headers.items():
# pylint: disable-next=consider-using-f-string
print("\t%s : %s" % (header, value))
if fd.status in (301, 302):
self._redirected = headers["Location"]
self.response_status = fd.status
return None
# Save the headers
self.headers_in = headers
self.response_status = fd.status
self.response_reason = fd.reason
return self._process_response(fd, connection)
def _process_response(self, fd, connection):
# Now use the Input class in case we get an enhanced response
resp = Input(
self.headers_in,
progressCallback=self.progressCallback,
bufferSize=self.bufferSize,
)
fd = resp.decode(fd)
if isinstance(fd, InputStream):
# When the File object goes out of scope, so will the InputStream;
# that will eventually call the connection's close() method and
# cleanly reap it
f = File(
fd.fd,
fd.length,
fd.name,
bufferSize=self.bufferSize,
progressCallback=self.progressCallback,
)
# Set the File's close method to the connection's
# Note that calling the HTTPResponse's close() is not enough,
# since the main socket would remain open, and this is
# particularily bad with SSL
f.close = connection.close
return f
# We can safely close the connection now; if we had an
# application/octet/stream (for which Input.read passes the original
# socket object), Input.decode would return an InputStream,
# so we wouldn't reach this point
connection.close()
return self.parse_response(fd)
# Give back the new URL if redirected
def redirected(self):
return self._redirected
# Rewrite parse_response to provide refresh callbacks
def parse_response(self, f):
# read response from input file, and parse it
p, u = self.getparser()
while 1:
response = f.read(1024)
if not response:
break
if self.refreshCallback:
self.refreshCallback()
if self.verbose:
print("body:", repr(response))
p.feed(response)
f.close()
p.close()
return u.close()
def setlang(self, lang):
self._lang = lang
# pylint: disable-next=missing-class-docstring
class SafeTransport(Transport):
def __init__(
self,
transfer=0,
encoding=0,
refreshCallback=None,
progressCallback=None,
trusted_certs=None,
timeout=None,
):
Transport.__init__(
self,
transfer,
encoding,
refreshCallback=refreshCallback,
progressCallback=progressCallback,
timeout=timeout,
)
self.trusted_certs = []
for certfile in trusted_certs or []:
self.add_trusted_cert(certfile)
def add_trusted_cert(self, certfile):
if not os.access(certfile, os.R_OK):
raise ValueError(
# pylint: disable-next=consider-using-f-string
"SafeTransport: Certificate file %s is not accessible"
% certfile
)
self.trusted_certs.append(certfile)
def get_connection(self, host):
# implement BASIC HTTP AUTHENTICATION
# pylint: disable-next=unused-variable
host, extra_headers, x509 = self.get_host_info(host)
if self.verbose:
# pylint: disable-next=consider-using-f-string
print("Connecting via https to %s" % (host,))
if self.timeout:
return connections.HTTPSConnection(
host, trusted_certs=self.trusted_certs, timeout=self.timeout
)
else:
return connections.HTTPSConnection(host, trusted_certs=self.trusted_certs)
# pylint: disable-next=missing-class-docstring
class ProxyTransport(Transport):
def __init__(
self,
proxy,
proxyUsername=None,
proxyPassword=None,
transfer=0,
encoding=0,
refreshCallback=None,
progressCallback=None,
timeout=None,
):
Transport.__init__(
self,
transfer,
encoding,
refreshCallback=refreshCallback,
progressCallback=progressCallback,
timeout=timeout,
)
self._proxy = proxy
self._proxy_username = proxyUsername
self._proxy_password = proxyPassword
def get_connection(self, host):
if self.verbose:
print(
# pylint: disable-next=consider-using-f-string
"Connecting via http to %s proxy %s, username %s, pass %s"
% (host, self._proxy, self._proxy_username, self._proxy_password)
)
if self.timeout:
return connections.HTTPProxyConnection(
self._proxy,
host,
username=self._proxy_username,
password=self._proxy_password,
timeout=self.timeout,
)
else:
return connections.HTTPProxyConnection(
self._proxy,
host,
username=self._proxy_username,
password=self._proxy_password,
)
# pylint: disable-next=missing-class-docstring
class SafeProxyTransport(ProxyTransport):
def __init__(
self,
proxy,
proxyUsername=None,
proxyPassword=None,
transfer=0,
encoding=0,
refreshCallback=None,
progressCallback=None,
trusted_certs=None,
timeout=None,
):
ProxyTransport.__init__(
self,
proxy,
proxyUsername=proxyUsername,
proxyPassword=proxyPassword,
transfer=transfer,
encoding=encoding,
refreshCallback=refreshCallback,
progressCallback=progressCallback,
timeout=timeout,
)
self.trusted_certs = []
for certfile in trusted_certs or []:
self.add_trusted_cert(certfile)
def add_trusted_cert(self, certfile):
if not os.access(certfile, os.R_OK):
raise ValueError(
# pylint: disable-next=consider-using-f-string
"SafeProxyTransport:Certificate file %s is not accessible"
% certfile
)
self.trusted_certs.append(certfile)
def get_connection(self, host):
if self.verbose:
print(
# pylint: disable-next=consider-using-f-string
"Connecting via https to %s proxy %s, username %s, pass %s"
% (host, self._proxy, self._proxy_username, self._proxy_password)
)
if self.timeout:
return connections.HTTPSProxyConnection(
self._proxy,
host,
username=self._proxy_username,
password=self._proxy_password,
trusted_certs=self.trusted_certs,
timeout=self.timeout,
)
else:
return connections.HTTPSProxyConnection(
self._proxy,
host,
username=self._proxy_username,
password=self._proxy_password,
trusted_certs=self.trusted_certs,
)
# ============================================================================
# Extended capabilities for transport
#
# We allow for the following possible headers:
#
# Content-Transfer-Encoding:
# This header tells us how the POST data is encoded in what we read.
# If it is not set, we assume plain text that can be passed along
# without any other modification. If set, valid values are:
# - binary : straight binary data
# - base64 : will pass through base64 decoder to get the binary data
#
# Content-Encoding:
# This header tells us what should we do with the binary data obtained
# after acting on the Content-Transfer-Encoding header. Valid values:
# - x-gzip : will need to pass through GNU gunzip-like to get plain
# text out
# - x-zlib : this denotes the Python's own zlib bindings which are a
# datastream based on gzip, but not quite
# - x-gpg : will need to pass through GPG to get out the text we want
# ============================================================================
# Input class to automate reading the posting from the network
# Having to work with environment variables blows, though
# pylint: disable-next=missing-class-docstring
class Input:
def __init__(
self,
headers=None,
# pylint: disable-next=invalid-name
progressCallback=None,
# pylint: disable-next=invalid-name
bufferSize=1024,
max_mem_size=16384,
):
self.transfer = None
self.encoding = None
self.type = None
self.length = 0
self.lang = "C"
self.name = ""
# pylint: disable-next=invalid-name
self.progressCallback = progressCallback
# pylint: disable-next=invalid-name
self.bufferSize = bufferSize
self.max_mem_size = max_mem_size
if not headers:
# we need to get them from environment
if "HTTP_CONTENT_TRANSFER_ENCODING" in os.environ:
self.transfer = os.environ["HTTP_CONTENT_TRANSFER_ENCODING"].lower()
if "HTTP_CONTENT_ENCODING" in os.environ:
self.encoding = os.environ["HTTP_CONTENT_ENCODING"].lower()
if "CONTENT-TYPE" in os.environ:
self.type = os.environ["CONTENT-TYPE"].lower()
if "CONTENT_LENGTH" in os.environ:
self.length = int(os.environ["CONTENT_LENGTH"])
if "HTTP_ACCEPT_LANGUAGE" in os.environ:
self.lang = os.environ["HTTP_ACCEPT_LANGUAGE"]
if "HTTP_X_PACKAGE_FILENAME" in os.environ:
self.name = os.environ["HTTP_X_PACKAGE_FILENAME"]
else:
# The stupid httplib screws up the headers from the HTTP repsonse
# and converts them to lowercase. This means that we have to
# convert to lowercase all the dictionary keys in case somebody calls
# us with sane values --gaftonc (actually mimetools is the culprit)
for header in headers.keys():
value = headers[header]
h = header.lower()
if h == "content-length":
try:
self.length = int(value)
except ValueError:
self.length = 0
elif h == "content-transfer-encoding":
# RFC 2045 #6.1: case insensitive
self.transfer = value.lower()
elif h == "content-encoding":
# RFC 2616 #3.5: case insensitive
self.encoding = value.lower()
elif h == "content-type":
# RFC 2616 #3.7: case insensitive
self.type = value.lower()
elif h == "accept-language":
# RFC 2616 #3.10: case insensitive
self.lang = value.lower()
elif h == "x-package-filename":
self.name = value
self.io = None
def read(self, fd=sys.stdin):
# The octet-streams are passed right back
if self.type == "application/octet-stream":
return
if self.length:
# Read exactly the amount of data we were told
self.io = _smart_read(
fd,
self.length,
bufferSize=self.bufferSize,
progressCallback=self.progressCallback,
max_mem_size=self.max_mem_size,
)
else:
# Oh well, no clue; read until EOF (hopefully)
self.io = _smart_total_read(fd)
if not self.transfer or self.transfer == "binary":
return
elif self.transfer == "base64":
# pylint: disable-next=import-outside-toplevel
import base64
old_io = self.io
old_io.seek(0, 0)
self.io = SmartIO(max_mem_size=self.max_mem_size)
base64.decode(old_io, self.io)
else:
raise NotImplementedError(self.transfer)
def decode(self, fd=sys.stdin):
# The octet-stream data are passed right back
if self.type == "application/octet-stream":
return InputStream(fd, self.length, self.name, close=fd.close)
if not self.io:
self.read(fd)
# At this point self.io exists (the only case when self.read() does
# not initialize self.io is when content-type is
# "application/octet-stream" - and we already dealt with that case
# We can now close the file descriptor
if hasattr(fd, "close"):
fd.close()
# Now we have the binary goo
if not self.encoding or self.encoding == "__plain":
# all is fine.
pass
elif self.encoding in ("x-zlib", "deflate"):
# pylint: disable-next=import-outside-toplevel
import zlib
obj = zlib.decompressobj()
self.io.seek(0, 0)
data = obj.decompress(self.io.read()) + obj.flush()
del obj
self.length = len(data)
self.io = SmartIO(max_mem_size=self.max_mem_size)
self.io.write(data)
elif self.encoding in ("x-gzip", "gzip"):
# pylint: disable-next=import-outside-toplevel
import gzip
self.io.seek(0, 0)
gz = gzip.GzipFile(mode="rb", compresslevel=COMPRESS_LEVEL, fileobj=self.io)
data = gz.read()
self.length = len(data)
self.io = SmartIO(max_mem_size=self.max_mem_size)
self.io.write(data)
elif self.encoding == "x-gpg":
# XXX: should be written
raise NotImplementedError(self.transfer, self.encoding)
else:
raise NotImplementedError(self.transfer, self.encoding)
# Play nicely and rewind the file descriptor
self.io.seek(0, 0)
return self.io
def getlang(self):
return self.lang
# Utility functions
# pylint: disable-next=invalid-name
def _smart_total_read(fd, bufferSize=1024, max_mem_size=16384):
"""
Tries to read data from the supplied stream, and puts the results into a
StmartIO object. The data will be in memory or in a temporary file,
depending on how much it's been read
Returns a SmartIO object
"""
io = SmartIO(max_mem_size=max_mem_size)
while 1:
chunk = fd.read(bufferSize)
if not chunk:
# EOF reached
break
io.write(chunk)
return io
# pylint: disable-next=invalid-name
def _smart_read(fd, amt, bufferSize=1024, progressCallback=None, max_mem_size=16384):
# Reads amt bytes from fd, or until the end of file, whichever
# occurs first
# The function will read in memory if the amout to be read is smaller than
# max_mem_size, or to a temporary file otherwise
#
# Unlike read(), _smart_read tries to return exactly the requested amount
# (whereas read will return _up_to_ that amount). Reads from sockets will
# usually reaturn less data, or the read can be interrupted
#
# Inspired by Greg Stein's httplib.py (the standard in python 2.x)
#
# support for progress callbacks added
# pylint: disable-next=invalid-name
startTime = time.time()
# pylint: disable-next=invalid-name
lastTime = startTime
buf = SmartIO(max_mem_size=max_mem_size)
origsize = amt
while amt > 0:
# pylint: disable-next=invalid-name
curTime = time.time()
l = min(bufferSize, amt)
chunk = fd.read(l)
# read guarantees that len(chunk) <= l
l = len(chunk)
if not l:
# Oops. Most likely EOF
break
# And since the original l was smaller than amt, we know amt >= 0
amt = amt - l
buf.write(chunk)
if progressCallback is None:
# No progress callback, so don't do fancy computations
continue
# We update the progress callback if:
# we haven't updated it for more than a secord, or
# it's the last read (amt == 0)
if curTime - lastTime >= 1 or amt == 0:
# pylint: disable-next=invalid-name
lastTime = curTime
# use float() so that we force float division in the next step
# pylint: disable-next=invalid-name
bytesRead = float(origsize - amt)
# if amt == 0, on a fast machine it is possible to have
# curTime - lastTime == 0, so add an epsilon to prevent a division
# by zero
speed = bytesRead / ((curTime - startTime) + 0.000001)
if origsize == 0:
secs = 0
else:
# speed != 0 because bytesRead > 0
# (if bytesRead == 0 then origsize == amt, which means a read
# of 0 length; but that's impossible since we already checked
# that l is non-null
secs = amt / speed
progressCallback(bytesRead, origsize, speed, secs)
# Now rewind the SmartIO
buf.seek(0, 0)
return buf
class InputStream:
def __init__(self, fd, length, name="<unknown>", close=None):
self.fd = fd
self.length = int(length)
self.name = name
# Close function
self.close = close
def __repr__(self):
# pylint: disable-next=consider-using-f-string
return "Input data is a stream of %d bytes for file %s.\n" % (
self.length,
self.name,
)
# ============================================================================
# Output class that will be used to build the temporary output string
# pylint: disable-next=missing-class-docstring
class BaseOutput:
# DEFINES for instances use
# Content-Encoding
ENCODE_NONE = 0
ENCODE_GZIP = 1
ENCODE_ZLIB = 2
ENCODE_GPG = 3
# Content-Transfer-Encoding
TRANSFER_NONE = 0
TRANSFER_BINARY = 1
TRANSFER_BASE64 = 2
# Mappings to make things easy
encodings = [
[None, "__plain"], # ENCODE_NONE
["x-gzip", "gzip"], # ENCODE_GZIP
["x-zlib", "deflate"], # ENCODE_ZLIB
["x-gpg"], # ENCODE_GPG
]
transfers = [
None, # TRANSFER_NONE
"binary", # TRANSFRE_BINARY
"base64", # TRANSFER_BASE64
]
def __init__(self, transfer=0, encoding=0, connection=None, method="POST"):
# Assumes connection is an instance of HTTPConnection
if connection:
if not isinstance(connection, connections.HTTPConnection):
# pylint: disable-next=broad-exception-raised
raise Exception("Expected an HTTPConnection type object")
self.method = method
# Store the connection
self._connection = connection
self.data = None
self.headers = UserDictCase()
self.encoding = 0
self.transfer = 0
self.transport_flags = {}
# for authenticated proxies
self.username = None
self.password = None
# Fields to keep the information about the server
self._host = None
self._handler = None
self._http_type = None
self._protocol = None
# Initialize self.transfer and self.encoding
self.set_transport_flags(transfer=transfer, encoding=encoding)
# internal flags
self.__processed = 0
def set_header(self, name, arg):
if type(arg) in [type([]), type(())]:
# Multi-valued header
#
# Per RFC 2616, section 4.2 (Message Headers):
# Multiple message-header fields with the same field-name MAY be
# present in a message if and only if the entire field-value for
# the header field is defined as a comma-separated list [i.e.
# #(values)]. It MUST be possible to combine the multiple header
# fields into one "field-name: field-value" pair, without
# changing the semantics of the message, by appending each
# subsequent field-value to the first, each separated by a comma.
self.headers[name] = ",".join(map(str, arg))
else:
self.headers[name] = str(arg)
def clear_header(self, name):
if name in self.headers:
del self.headers[name]
def process(self, data):
# Assume straight text/xml
self.data = data
# Content-Encoding header
if self.encoding == self.ENCODE_GZIP:
# pylint: disable-next=import-outside-toplevel
import gzip
encoding_name = self.encodings[self.ENCODE_GZIP][0]
self.set_header("Content-Encoding", encoding_name)
f = SmartIO(force_mem=1)
gz = gzip.GzipFile(mode="wb", compresslevel=COMPRESS_LEVEL, fileobj=f)
if sys.version_info[0] == 3:
gz.write(bstr(data))
else:
gz.write(sstr(data))
gz.close()
self.data = f.getvalue()
f.close()
elif self.encoding == self.ENCODE_ZLIB:
# pylint: disable-next=import-outside-toplevel
import zlib
encoding_name = self.encodings[self.ENCODE_ZLIB][0]
self.set_header("Content-Encoding", encoding_name)
obj = zlib.compressobj(COMPRESS_LEVEL)
self.data = obj.compress(data.encode()) + obj.flush()
elif self.encoding == self.ENCODE_GPG:
# XXX: fix me.
raise NotImplementedError(self.transfer, self.encoding)
# Content-Transfer-Encoding header
if self.transfer == self.TRANSFER_BINARY:
transfer_name = self.transfers[self.TRANSFER_BINARY]
self.set_header("Content-Transfer-Encoding", transfer_name)
self.set_header("Content-Type", "application/binary")
elif self.transfer == self.TRANSFER_BASE64:
# pylint: disable-next=import-outside-toplevel
import base64
transfer_name = self.transfers[self.TRANSFER_BASE64]
self.set_header("Content-Transfer-Encoding", transfer_name)
self.set_header("Content-Type", "text/base64")
self.data = base64.encodestring(self.data).decode()
self.set_header("Content-Length", len(bstr(self.data)))
rpc_version = __version__
if len(__version__.split()) > 1:
rpc_version = __version__.split()[1]
# other headers
self.set_header(
"X-Transport-Info",
# pylint: disable-next=consider-using-f-string
"Extended Capabilities Transport (C) Red Hat, Inc (version %s)"
% rpc_version,
)
self.__processed = 1
# reset the transport options
def set_transport_flags(self, transfer=0, encoding=0, **kwargs):
self.transfer = transfer
self.encoding = encoding
self.transport_flags.update(kwargs)
def send_http(self, host, handler="/RPC2"):
if not self.__processed:
raise NotProcessed
self._host = host
if self._connection is None:
# pylint: disable-next=broad-exception-raised
raise Exception("No connection object found")
self._connection.connect()
# wrap self data into binary object, otherwise HTTPConnection.request
# will encode it as ISO-8859-1 https://docs.python.org/3/library/http.client.html#httpconnection-objects
self._connection.request(
self.method, handler, body=bstr(self.data), headers=self.headers
)
response = self._connection.getresponse()
if not self.response_acceptable(response):
raise xmlrpclib.ProtocolError(
# pylint: disable-next=consider-using-f-string
"%s %s" % (self._host, handler),
response.status,
response.reason,
response.msg,
)
# A response object has read() and close() methods, so we can safely
# pass the whole object back
return response.msg, response
def response_acceptable(self, response):
"""Returns true if the response is acceptable"""
if response.status == 200:
return 1
if response.status in (301, 302):
return 1
if response.status != 206:
return 0
# If the flag is not set, it's unacceptable
if not self.transport_flags.get("allow_partial_content"):
return 0
if response.msg["Content-Type"] != "application/octet-stream":
# Don't allow anything else to be requested as a range, it could
# break the XML parser
return 0
return 1
def close(self):
if self._connection:
self._connection.close()
self._connection = None
# pylint: disable-next=invalid-name
def lookupTransfer(transfer, strict=0):
"""Given a string or numeric representation of a transfer, return the
transfer code"""
if transfer is None:
# Plain
return 0
if isinstance(transfer, IntType) and 0 <= transfer < len(Output.transfers):
return transfer
if isinstance(transfer, StringType):
for i in range(len(Output.transfers)):
if Output.transfers[i] == transfer.lower():
return i
if strict:
# pylint: disable-next=consider-using-f-string
raise ValueError("Unsupported transfer %s" % transfer)
# Return default
return 0
# pylint: disable-next=invalid-name
def lookupEncoding(encoding, strict=0):
"""Given a string or numeric representation of an encoding, return the
encoding code"""
if encoding is None:
# Plain
return 0
if isinstance(encoding, IntType) and 0 <= encoding < len(Output.encodings):
return encoding
if isinstance(encoding, StringType):
for i in range(len(Output.encodings)):
if encoding.lower() in Output.encodings[i]:
return i
if strict:
# pylint: disable-next=consider-using-f-string
raise ValueError("Unsupported encoding %s" % encoding)
# Return default
return 0
Output = BaseOutput
# File object
# pylint: disable-next=missing-class-docstring
class File:
def __init__(
self,
file_obj,
length=0,
name=None,
# pylint: disable-next=invalid-name
progressCallback=None,
# pylint: disable-next=invalid-name
bufferSize=16384,
):
self.length = length
self.file_obj = file_obj
self.close = file_obj.close
# pylint: disable-next=invalid-name
self.bufferSize = bufferSize
self.name = ""
if name:
self.name = name[name.rfind("/") + 1 :]
# pylint: disable-next=invalid-name
self.progressCallback = progressCallback
def __len__(self):
return self.length
def read(self, amt=None):
# If they want to read everything, use _smart_read
if amt is None:
fd = self._get_file()
return fd.read()
return self.file_obj.read(amt)
def read_to_file(self, file):
"""Copies the contents of this File object into another file
object"""
fd = self._get_file()
while 1:
buf = fd.read(self.bufferSize)
if not buf:
break
if sys.version_info[0] == 3:
file.write(bstr(buf))
else:
file.write(sstr(buf))
return file
def _get_file(self):
"""Read everything into a temporary file and call the progress
callbacks if the file length is defined, or just reads till EOF"""
if self.length:
io = _smart_read(
self.file_obj,
self.length,
bufferSize=self.bufferSize,
progressCallback=self.progressCallback,
)
io.seek(0, 0)
else:
# Read everuthing - no callbacks involved
io = _smart_total_read(self.file_obj, bufferSize=self.bufferSize)
io.seek(0, 0)
return io
def __del__(self):
if self.close:
self.close()
self.close = None
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!