Adding upstream version 2.23+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
parent
4274e522c0
commit
1e97beb507
21 changed files with 3809 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Git clutter
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# Python bits
|
||||||
|
/*.pyc
|
||||||
|
|
||||||
|
# Man Pages
|
||||||
|
/*.8
|
||||||
|
/*.1
|
||||||
|
|
||||||
|
# HTML Docs
|
||||||
|
/*.html
|
27
COPYING
Normal file
27
COPYING
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
BSD LICENSE
|
||||||
|
|
||||||
|
Copyright (c) 2015, Eric S. Raymond
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
277
LICENSE
Normal file
277
LICENSE
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
Eclipse Public License - v 2.0
|
||||||
|
|
||||||
|
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
|
||||||
|
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
|
||||||
|
OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
|
||||||
|
|
||||||
|
1. DEFINITIONS
|
||||||
|
|
||||||
|
"Contribution" means:
|
||||||
|
|
||||||
|
a) in the case of the initial Contributor, the initial content
|
||||||
|
Distributed under this Agreement, and
|
||||||
|
|
||||||
|
b) in the case of each subsequent Contributor:
|
||||||
|
i) changes to the Program, and
|
||||||
|
ii) additions to the Program;
|
||||||
|
where such changes and/or additions to the Program originate from
|
||||||
|
and are Distributed by that particular Contributor. A Contribution
|
||||||
|
"originates" from a Contributor if it was added to the Program by
|
||||||
|
such Contributor itself or anyone acting on such Contributor's behalf.
|
||||||
|
Contributions do not include changes or additions to the Program that
|
||||||
|
are not Modified Works.
|
||||||
|
|
||||||
|
"Contributor" means any person or entity that Distributes the Program.
|
||||||
|
|
||||||
|
"Licensed Patents" mean patent claims licensable by a Contributor which
|
||||||
|
are necessarily infringed by the use or sale of its Contribution alone
|
||||||
|
or when combined with the Program.
|
||||||
|
|
||||||
|
"Program" means the Contributions Distributed in accordance with this
|
||||||
|
Agreement.
|
||||||
|
|
||||||
|
"Recipient" means anyone who receives the Program under this Agreement
|
||||||
|
or any Secondary License (as applicable), including Contributors.
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source Code or other
|
||||||
|
form, that is based on (or derived from) the Program and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship.
|
||||||
|
|
||||||
|
"Modified Works" shall mean any work in Source Code or other form that
|
||||||
|
results from an addition to, deletion from, or modification of the
|
||||||
|
contents of the Program, including, for purposes of clarity any new file
|
||||||
|
in Source Code form that contains any contents of the Program. Modified
|
||||||
|
Works shall not include works that contain only declarations,
|
||||||
|
interfaces, types, classes, structures, or files of the Program solely
|
||||||
|
in each case in order to link to, bind by name, or subclass the Program
|
||||||
|
or Modified Works thereof.
|
||||||
|
|
||||||
|
"Distribute" means the acts of a) distributing or b) making available
|
||||||
|
in any manner that enables the transfer of a copy.
|
||||||
|
|
||||||
|
"Source Code" means the form of a Program preferred for making
|
||||||
|
modifications, including but not limited to software source code,
|
||||||
|
documentation source, and configuration files.
|
||||||
|
|
||||||
|
"Secondary License" means either the GNU General Public License,
|
||||||
|
Version 2.0, or any later versions of that license, including any
|
||||||
|
exceptions or additional permissions as identified by the initial
|
||||||
|
Contributor.
|
||||||
|
|
||||||
|
2. GRANT OF RIGHTS
|
||||||
|
|
||||||
|
a) Subject to the terms of this Agreement, each Contributor hereby
|
||||||
|
grants Recipient a non-exclusive, worldwide, royalty-free copyright
|
||||||
|
license to reproduce, prepare Derivative Works of, publicly display,
|
||||||
|
publicly perform, Distribute and sublicense the Contribution of such
|
||||||
|
Contributor, if any, and such Derivative Works.
|
||||||
|
|
||||||
|
b) Subject to the terms of this Agreement, each Contributor hereby
|
||||||
|
grants Recipient a non-exclusive, worldwide, royalty-free patent
|
||||||
|
license under Licensed Patents to make, use, sell, offer to sell,
|
||||||
|
import and otherwise transfer the Contribution of such Contributor,
|
||||||
|
if any, in Source Code or other form. This patent license shall
|
||||||
|
apply to the combination of the Contribution and the Program if, at
|
||||||
|
the time the Contribution is added by the Contributor, such addition
|
||||||
|
of the Contribution causes such combination to be covered by the
|
||||||
|
Licensed Patents. The patent license shall not apply to any other
|
||||||
|
combinations which include the Contribution. No hardware per se is
|
||||||
|
licensed hereunder.
|
||||||
|
|
||||||
|
c) Recipient understands that although each Contributor grants the
|
||||||
|
licenses to its Contributions set forth herein, no assurances are
|
||||||
|
provided by any Contributor that the Program does not infringe the
|
||||||
|
patent or other intellectual property rights of any other entity.
|
||||||
|
Each Contributor disclaims any liability to Recipient for claims
|
||||||
|
brought by any other entity based on infringement of intellectual
|
||||||
|
property rights or otherwise. As a condition to exercising the
|
||||||
|
rights and licenses granted hereunder, each Recipient hereby
|
||||||
|
assumes sole responsibility to secure any other intellectual
|
||||||
|
property rights needed, if any. For example, if a third party
|
||||||
|
patent license is required to allow Recipient to Distribute the
|
||||||
|
Program, it is Recipient's responsibility to acquire that license
|
||||||
|
before distributing the Program.
|
||||||
|
|
||||||
|
d) Each Contributor represents that to its knowledge it has
|
||||||
|
sufficient copyright rights in its Contribution, if any, to grant
|
||||||
|
the copyright license set forth in this Agreement.
|
||||||
|
|
||||||
|
e) Notwithstanding the terms of any Secondary License, no
|
||||||
|
Contributor makes additional grants to any Recipient (other than
|
||||||
|
those set forth in this Agreement) as a result of such Recipient's
|
||||||
|
receipt of the Program under the terms of a Secondary License
|
||||||
|
(if permitted under the terms of Section 3).
|
||||||
|
|
||||||
|
3. REQUIREMENTS
|
||||||
|
|
||||||
|
3.1 If a Contributor Distributes the Program in any form, then:
|
||||||
|
|
||||||
|
a) the Program must also be made available as Source Code, in
|
||||||
|
accordance with section 3.2, and the Contributor must accompany
|
||||||
|
the Program with a statement that the Source Code for the Program
|
||||||
|
is available under this Agreement, and informs Recipients how to
|
||||||
|
obtain it in a reasonable manner on or through a medium customarily
|
||||||
|
used for software exchange; and
|
||||||
|
|
||||||
|
b) the Contributor may Distribute the Program under a license
|
||||||
|
different than this Agreement, provided that such license:
|
||||||
|
i) effectively disclaims on behalf of all other Contributors all
|
||||||
|
warranties and conditions, express and implied, including
|
||||||
|
warranties or conditions of title and non-infringement, and
|
||||||
|
implied warranties or conditions of merchantability and fitness
|
||||||
|
for a particular purpose;
|
||||||
|
|
||||||
|
ii) effectively excludes on behalf of all other Contributors all
|
||||||
|
liability for damages, including direct, indirect, special,
|
||||||
|
incidental and consequential damages, such as lost profits;
|
||||||
|
|
||||||
|
iii) does not attempt to limit or alter the recipients' rights
|
||||||
|
in the Source Code under section 3.2; and
|
||||||
|
|
||||||
|
iv) requires any subsequent distribution of the Program by any
|
||||||
|
party to be under a license that satisfies the requirements
|
||||||
|
of this section 3.
|
||||||
|
|
||||||
|
3.2 When the Program is Distributed as Source Code:
|
||||||
|
|
||||||
|
a) it must be made available under this Agreement, or if the
|
||||||
|
Program (i) is combined with other material in a separate file or
|
||||||
|
files made available under a Secondary License, and (ii) the initial
|
||||||
|
Contributor attached to the Source Code the notice described in
|
||||||
|
Exhibit A of this Agreement, then the Program may be made available
|
||||||
|
under the terms of such Secondary Licenses, and
|
||||||
|
|
||||||
|
b) a copy of this Agreement must be included with each copy of
|
||||||
|
the Program.
|
||||||
|
|
||||||
|
3.3 Contributors may not remove or alter any copyright, patent,
|
||||||
|
trademark, attribution notices, disclaimers of warranty, or limitations
|
||||||
|
of liability ("notices") contained within the Program from any copy of
|
||||||
|
the Program which they Distribute, provided that Contributors may add
|
||||||
|
their own appropriate notices.
|
||||||
|
|
||||||
|
4. COMMERCIAL DISTRIBUTION
|
||||||
|
|
||||||
|
Commercial distributors of software may accept certain responsibilities
|
||||||
|
with respect to end users, business partners and the like. While this
|
||||||
|
license is intended to facilitate the commercial use of the Program,
|
||||||
|
the Contributor who includes the Program in a commercial product
|
||||||
|
offering should do so in a manner which does not create potential
|
||||||
|
liability for other Contributors. Therefore, if a Contributor includes
|
||||||
|
the Program in a commercial product offering, such Contributor
|
||||||
|
("Commercial Contributor") hereby agrees to defend and indemnify every
|
||||||
|
other Contributor ("Indemnified Contributor") against any losses,
|
||||||
|
damages and costs (collectively "Losses") arising from claims, lawsuits
|
||||||
|
and other legal actions brought by a third party against the Indemnified
|
||||||
|
Contributor to the extent caused by the acts or omissions of such
|
||||||
|
Commercial Contributor in connection with its distribution of the Program
|
||||||
|
in a commercial product offering. The obligations in this section do not
|
||||||
|
apply to any claims or Losses relating to any actual or alleged
|
||||||
|
intellectual property infringement. In order to qualify, an Indemnified
|
||||||
|
Contributor must: a) promptly notify the Commercial Contributor in
|
||||||
|
writing of such claim, and b) allow the Commercial Contributor to control,
|
||||||
|
and cooperate with the Commercial Contributor in, the defense and any
|
||||||
|
related settlement negotiations. The Indemnified Contributor may
|
||||||
|
participate in any such claim at its own expense.
|
||||||
|
|
||||||
|
For example, a Contributor might include the Program in a commercial
|
||||||
|
product offering, Product X. That Contributor is then a Commercial
|
||||||
|
Contributor. If that Commercial Contributor then makes performance
|
||||||
|
claims, or offers warranties related to Product X, those performance
|
||||||
|
claims and warranties are such Commercial Contributor's responsibility
|
||||||
|
alone. Under this section, the Commercial Contributor would have to
|
||||||
|
defend claims against the other Contributors related to those performance
|
||||||
|
claims and warranties, and if a court requires any other Contributor to
|
||||||
|
pay any damages as a result, the Commercial Contributor must pay
|
||||||
|
those damages.
|
||||||
|
|
||||||
|
5. NO WARRANTY
|
||||||
|
|
||||||
|
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
|
||||||
|
PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
|
||||||
|
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
|
||||||
|
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
|
||||||
|
TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. Each Recipient is solely responsible for determining the
|
||||||
|
appropriateness of using and distributing the Program and assumes all
|
||||||
|
risks associated with its exercise of rights under this Agreement,
|
||||||
|
including but not limited to the risks and costs of program errors,
|
||||||
|
compliance with applicable laws, damage to or loss of data, programs
|
||||||
|
or equipment, and unavailability or interruption of operations.
|
||||||
|
|
||||||
|
6. DISCLAIMER OF LIABILITY
|
||||||
|
|
||||||
|
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
|
||||||
|
PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
|
||||||
|
SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
|
||||||
|
PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
|
||||||
|
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
7. GENERAL
|
||||||
|
|
||||||
|
If any provision of this Agreement is invalid or unenforceable under
|
||||||
|
applicable law, it shall not affect the validity or enforceability of
|
||||||
|
the remainder of the terms of this Agreement, and without further
|
||||||
|
action by the parties hereto, such provision shall be reformed to the
|
||||||
|
minimum extent necessary to make such provision valid and enforceable.
|
||||||
|
|
||||||
|
If Recipient institutes patent litigation against any entity
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that the
|
||||||
|
Program itself (excluding combinations of the Program with other software
|
||||||
|
or hardware) infringes such Recipient's patent(s), then such Recipient's
|
||||||
|
rights granted under Section 2(b) shall terminate as of the date such
|
||||||
|
litigation is filed.
|
||||||
|
|
||||||
|
All Recipient's rights under this Agreement shall terminate if it
|
||||||
|
fails to comply with any of the material terms or conditions of this
|
||||||
|
Agreement and does not cure such failure in a reasonable period of
|
||||||
|
time after becoming aware of such noncompliance. If all Recipient's
|
||||||
|
rights under this Agreement terminate, Recipient agrees to cease use
|
||||||
|
and distribution of the Program as soon as reasonably practicable.
|
||||||
|
However, Recipient's obligations under this Agreement and any licenses
|
||||||
|
granted by Recipient relating to the Program shall continue and survive.
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute copies of this Agreement,
|
||||||
|
but in order to avoid inconsistency the Agreement is copyrighted and
|
||||||
|
may only be modified in the following manner. The Agreement Steward
|
||||||
|
reserves the right to publish new versions (including revisions) of
|
||||||
|
this Agreement from time to time. No one other than the Agreement
|
||||||
|
Steward has the right to modify this Agreement. The Eclipse Foundation
|
||||||
|
is the initial Agreement Steward. The Eclipse Foundation may assign the
|
||||||
|
responsibility to serve as the Agreement Steward to a suitable separate
|
||||||
|
entity. Each new version of the Agreement will be given a distinguishing
|
||||||
|
version number. The Program (including Contributions) may always be
|
||||||
|
Distributed subject to the version of the Agreement under which it was
|
||||||
|
received. In addition, after a new version of the Agreement is published,
|
||||||
|
Contributor may elect to Distribute the Program (including its
|
||||||
|
Contributions) under the new version.
|
||||||
|
|
||||||
|
Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
|
||||||
|
receives no rights or licenses to the intellectual property of any
|
||||||
|
Contributor under this Agreement, whether expressly, by implication,
|
||||||
|
estoppel or otherwise. All rights in the Program not expressly granted
|
||||||
|
under this Agreement are reserved. Nothing in this Agreement is intended
|
||||||
|
to be enforceable by any entity that is not a Contributor or Recipient.
|
||||||
|
No third-party beneficiary rights are created under this Agreement.
|
||||||
|
|
||||||
|
Exhibit A - Form of Secondary Licenses Notice
|
||||||
|
|
||||||
|
"This Source Code may also be made available under the following
|
||||||
|
Secondary Licenses when the conditions for such availability set forth
|
||||||
|
in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
|
||||||
|
version(s), and exceptions or additional permissions here}."
|
||||||
|
|
||||||
|
Simply including a copy of this Agreement, including this Exhibit A
|
||||||
|
is not sufficient to license the Source Code under Secondary Licenses.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to
|
||||||
|
look for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
113
Makefile
Normal file
113
Makefile
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
# Makefile for the irker relaying daemon
|
||||||
|
|
||||||
|
VERS := $(shell sed -n 's/version = "\(.\+\)"/\1/p' irkerd)
|
||||||
|
SYSTEMDSYSTEMUNITDIR := $(shell pkg-config --variable=systemdsystemunitdir systemd)
|
||||||
|
|
||||||
|
# `prefix`, `mandir` & `DESTDIR` can and should be set on the command
|
||||||
|
# line to control installation locations
|
||||||
|
prefix ?= /usr
|
||||||
|
mandir ?= /share/man
|
||||||
|
target = $(DESTDIR)$(prefix)
|
||||||
|
|
||||||
|
docs: irkerd.html irkerd.8 irkerhook.html irkerhook.1 irk.html irk.1
|
||||||
|
|
||||||
|
irkerd.8: irkerd.xml
|
||||||
|
xmlto man irkerd.xml
|
||||||
|
irkerd.html: irkerd.xml
|
||||||
|
xmlto html-nochunks irkerd.xml
|
||||||
|
|
||||||
|
irkerhook.1: irkerhook.xml
|
||||||
|
xmlto man irkerhook.xml
|
||||||
|
irkerhook.html: irkerhook.xml
|
||||||
|
xmlto html-nochunks irkerhook.xml
|
||||||
|
|
||||||
|
irk.1: irk.xml
|
||||||
|
xmlto man irk.xml
|
||||||
|
irk.html: irk.xml
|
||||||
|
xmlto html-nochunks irk.xml
|
||||||
|
|
||||||
|
install.html: install.adoc
|
||||||
|
asciidoc -o install.html install.adoc
|
||||||
|
security.html: security.adoc
|
||||||
|
asciidoc -o security.html security.adoc
|
||||||
|
hacking.html: hacking.adoc
|
||||||
|
asciidoc -o hacking.html hacking.adoc
|
||||||
|
|
||||||
|
install: irk.1 irkerd.8 irkerhook.1 uninstall
|
||||||
|
install -m 755 -o 0 -g 0 -d "$(target)/bin"
|
||||||
|
install -m 755 -o 0 -g 0 irkerd "$(target)/bin/irkerd"
|
||||||
|
ifneq ($(strip $(SYSTEMDSYSTEMUNITDIR)),)
|
||||||
|
install -m 755 -o 0 -g 0 -d "$(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)"
|
||||||
|
install -m 644 -o 0 -g 0 irkerd.service "$(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)"
|
||||||
|
endif
|
||||||
|
install -m 755 -o 0 -g 0 -d "$(target)$(mandir)/man8"
|
||||||
|
install -m 755 -o 0 -g 0 irkerd.8 "$(target)$(mandir)/man8/irkerd.8"
|
||||||
|
install -m 755 -o 0 -g 0 -d "$(target)$(mandir)/man1"
|
||||||
|
install -m 755 -o 0 -g 0 irkerhook.1 "$(target)$(mandir)/man1/irkerhook.1"
|
||||||
|
install -m 755 -o 0 -g 0 irk.1 "$(target)$(mandir)/man1/irk.1"
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f "$(target)/bin/irkerd"
|
||||||
|
ifneq ($(strip $(SYSTEMDSYSTEMUNITDIR)),)
|
||||||
|
rm -f "$(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)/irkerd.service"
|
||||||
|
endif
|
||||||
|
rm -f "$(target)$(mandir)/man8/irkerd.8"
|
||||||
|
rm -f "$(target)$(mandir)/man1/irkerhook.1"
|
||||||
|
rm -f "$(target)$(mandir)/man1/irk.1"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f irkerd.8 irkerhook.1 irk.1 irker-*.tar.gz *~ *.html
|
||||||
|
|
||||||
|
pylint:
|
||||||
|
@pylint --score=n irkerd irkerhook.py
|
||||||
|
|
||||||
|
loc:
|
||||||
|
@echo "LOC:"; wc -l irkerd irkerhook.py
|
||||||
|
@echo -n "LLOC: "; grep -vE '(^ *#|^ *$$)' irkerd irkerhook.py | wc -l
|
||||||
|
|
||||||
|
DOCS = \
|
||||||
|
README \
|
||||||
|
COPYING \
|
||||||
|
NEWS \
|
||||||
|
install.adoc \
|
||||||
|
security.adoc \
|
||||||
|
hacking.adoc \
|
||||||
|
irkerhook.xml \
|
||||||
|
irkerd.xml \
|
||||||
|
irk.xml \
|
||||||
|
|
||||||
|
SOURCES = \
|
||||||
|
$(DOCS) \
|
||||||
|
irkerd \
|
||||||
|
irkerhook.py \
|
||||||
|
filter-example.py \
|
||||||
|
filter-test.py \
|
||||||
|
irk \
|
||||||
|
Makefile
|
||||||
|
|
||||||
|
EXTRA_DIST = \
|
||||||
|
org.catb.irkerd.plist \
|
||||||
|
irkerd.service \
|
||||||
|
irker-logo.png
|
||||||
|
|
||||||
|
version:
|
||||||
|
@echo $(VERS)
|
||||||
|
|
||||||
|
irker-$(VERS).tar.gz: $(SOURCES) irkerd.8 irkerhook.1 irk.1
|
||||||
|
mkdir irker-$(VERS)
|
||||||
|
cp -pR $(SOURCES) $(EXTRA_DIST) irker-$(VERS)/
|
||||||
|
@COPYFILE_DISABLE=1 tar -cvzf irker-$(VERS).tar.gz irker-$(VERS)
|
||||||
|
rm -fr irker-$(VERS)
|
||||||
|
|
||||||
|
irker-$(VERS).md5:
|
||||||
|
@md5sum irker-$(VERS).tar.gz >irker-$(VERS).md5
|
||||||
|
|
||||||
|
dist: irker-$(VERS).tar.gz irker-$(VERS).md5
|
||||||
|
|
||||||
|
WEBDOCS = irkerd.html irk.html irkerhook.html install.html security.html hacking.html
|
||||||
|
|
||||||
|
release: irker-$(VERS).tar.gz irker-$(VERS).md5 $(WEBDOCS)
|
||||||
|
shipper version=$(VERS) | sh -e -x
|
||||||
|
|
||||||
|
refresh: $(WEBDOCS)
|
||||||
|
shipper -N -w version=$(VERS) | sh -e -x
|
195
NEWS
Normal file
195
NEWS
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
irker history
|
||||||
|
|
||||||
|
2.23: 2023-01-27::
|
||||||
|
Fix typo in support for IPv6 listening.
|
||||||
|
|
||||||
|
2.22: 2022-03-15::
|
||||||
|
Add support for IPv6 listening.
|
||||||
|
|
||||||
|
2.21: 2022-01-25::
|
||||||
|
Restore function of immediate option.
|
||||||
|
|
||||||
|
2.20: 2021-09-20
|
||||||
|
Added --posord-file option
|
||||||
|
Add socket connection-timeout option.
|
||||||
|
Ubuntu deleted /usr/bin/python, change all invocations to Python 3.
|
||||||
|
|
||||||
|
2.19: 2020-06-29
|
||||||
|
Codebase is now fully forward-poerted to Python 3.
|
||||||
|
|
||||||
|
2.18: 2016-06-02
|
||||||
|
Add the ability to set the notification-message template (Debian bug #824512)
|
||||||
|
|
||||||
|
2.17: 2016-03-14
|
||||||
|
Add a reconnect delay (Debian bug #749650).
|
||||||
|
Add proxy support (requres setting some variables in the source file).
|
||||||
|
Use git abbreviated hash to address Debian complaints.
|
||||||
|
|
||||||
|
2.16: 2016-02-18
|
||||||
|
Code now runs under either Python 2 or Python 3
|
||||||
|
|
||||||
|
2.15: 2016-01-12
|
||||||
|
Emergency backout of getaddrinfo, it randomly hangs.
|
||||||
|
|
||||||
|
2.14: 2016-01-12
|
||||||
|
Lookup with getaddrinfo allows use with IPv6.
|
||||||
|
Documentation improvements.
|
||||||
|
|
||||||
|
2.13: 2015-06-14
|
||||||
|
SSL validation fix.
|
||||||
|
Hardening against Unicode decode errors.
|
||||||
|
irk becomes a library so it can be re-used.
|
||||||
|
|
||||||
|
2.12: 2014-10-22
|
||||||
|
Catch erroneous UTF-8 or non-UTF-8 from servers.
|
||||||
|
Also autodetect the right logging device under FreeBSD: /var/run/syslog
|
||||||
|
|
||||||
|
2.11: 2014-06-20
|
||||||
|
With -i, message string argument now optional, stdin is read if it is absent.
|
||||||
|
Auto-adapt to BSD & OS X log device as well as Linux's.
|
||||||
|
|
||||||
|
2.10: 2014-06-19
|
||||||
|
irk no longer fails on ircs channel URLs.
|
||||||
|
|
||||||
|
2.9: 2014-06-01
|
||||||
|
If irkerd is running in background, log to /dev/syslog (facility daemon).
|
||||||
|
New -H option to set host listening address.
|
||||||
|
Add support for using CertFP to auth to the IRC server, and document it.
|
||||||
|
|
||||||
|
2.8: 2014-05-30
|
||||||
|
Various minor improvements to irk.
|
||||||
|
Cope better with branch names containing slashes.
|
||||||
|
|
||||||
|
2.7: 2014-03-15
|
||||||
|
Add support for ircs:// and SSL/TLS connections to IRC servers.
|
||||||
|
Add support for per-URL usernames and passwords.
|
||||||
|
|
||||||
|
2.6: 2014-02-04
|
||||||
|
Fix for an infinite loop on failing to connect to IRC
|
||||||
|
|
||||||
|
2.5: 2013-12-24
|
||||||
|
Bug fix - remove a deadlock we inherited from irclib.
|
||||||
|
|
||||||
|
2.4: 2013-12-03
|
||||||
|
Bug fix release - some users reported failure to connect with 2.3.
|
||||||
|
Also prevent a crash if Unicode shows up in the wrong place.
|
||||||
|
|
||||||
|
2.3: 2013-11-30
|
||||||
|
-i option enables immediate sending of one line in foreground.
|
||||||
|
|
||||||
|
2.2: 2013-11-29
|
||||||
|
Fixed Unicode processing - got busted in 2.0 when irclib was removed.
|
||||||
|
Show Python traceback on higher debug levels.
|
||||||
|
|
||||||
|
2.1: 2013-11-26
|
||||||
|
A performance improvement in the git repository hook.
|
||||||
|
Documentation polishing.
|
||||||
|
|
||||||
|
2.0: 2013-11-16
|
||||||
|
The dependency on irclib is gone.
|
||||||
|
An email delivery method, suitable for use on SourceForge.
|
||||||
|
irkerhook can now be used as a hg changegroup hook.
|
||||||
|
Prevent misbehavior on UTF-8 in commit metadata.
|
||||||
|
Fix a crash bug on invalid hostnames.
|
||||||
|
|
||||||
|
1.20: 2013-05-17
|
||||||
|
Compatibility back to Python 2.4 (provided simplejson is present).
|
||||||
|
Increased anti-flood delay to avoid trouble with freenode.
|
||||||
|
|
||||||
|
1.19: 2013-05-06
|
||||||
|
Fixed a minor bug in argument processing
|
||||||
|
|
||||||
|
1.18: 2013-04-16
|
||||||
|
Added -l option; irker can now be used as a channel monitor.
|
||||||
|
Added -n and -p option: the nick can be forced and authenticated.
|
||||||
|
|
||||||
|
1.17: 2013-02-03
|
||||||
|
Various minor fixes and bulletproofing.
|
||||||
|
|
||||||
|
1.16: 2013-01-24
|
||||||
|
Deal gracefully with non-ASCII author names and '|' in the command line.
|
||||||
|
|
||||||
|
1.15: 2012-12-08
|
||||||
|
Don't append an extra newline in the Subversion hook.
|
||||||
|
|
||||||
|
1.14: 2012-11-26
|
||||||
|
irclib 5.0 and urlparse compatibility fixes.
|
||||||
|
|
||||||
|
1.13: 2012-11-06
|
||||||
|
Fix for a very rare thread race found by AI0867.
|
||||||
|
Work around a midesign in the IRC library.
|
||||||
|
|
||||||
|
1.12: 2012-10-11
|
||||||
|
Emergency workaround for a Unicode-handling error buried deep in irclib.
|
||||||
|
The IRC library at version 3.2 or later is required for this version!
|
||||||
|
Only ship to freenode #commits by default.
|
||||||
|
|
||||||
|
1.11: 2012-10-10
|
||||||
|
Code is now fully Unicode-safe.
|
||||||
|
A 'cialike' option emulates the file-summary behavior on the old CIA service.
|
||||||
|
|
||||||
|
1.10: 2012-10-09
|
||||||
|
Expire disconnected connections if they aren't needed or can't reconnect.
|
||||||
|
Eventlet support removed - didn't play well with the library mutex.
|
||||||
|
|
||||||
|
1.9: 2012-10-08
|
||||||
|
Proper mutex locks prevent an occasional thread crash on session timeout.
|
||||||
|
There's now systemd installation support for irkerd.
|
||||||
|
|
||||||
|
1.8: 2012-10-06
|
||||||
|
It's now possible to send to nick URLs.
|
||||||
|
Cope gracefully if an IRC server dies or hangs during the nick handshake.
|
||||||
|
|
||||||
|
1.7: 2012-10-05
|
||||||
|
Optional metadata filtering with a user-specified command.
|
||||||
|
irkerd code is now armored against IRC library errors in the delivery threads.
|
||||||
|
|
||||||
|
1.6: 2012-10-04
|
||||||
|
In 1.5 trying to appease pylint broke the Mercurial hook.
|
||||||
|
Added credits for contributors in hacking.txt.
|
||||||
|
Fix the aging out of connections when we hit a resource limit.
|
||||||
|
|
||||||
|
1.5: 2012-10-03
|
||||||
|
Mercurial support.
|
||||||
|
Shorten nick negotiation by choosing a random nick base from a large range.
|
||||||
|
Make irkerd exit cleanly on control-C.
|
||||||
|
|
||||||
|
1.4: 2012-10-02
|
||||||
|
Graceful handling of server disconnects and kicks.
|
||||||
|
Distribution now inclues an installable irkerd plist for Mac OS/X.
|
||||||
|
The color variable is no longer boolean; may be miRC or ANSI.
|
||||||
|
The installation instructions for irkerhook.py have changed!
|
||||||
|
|
||||||
|
1.3: 2012-10-01
|
||||||
|
Support for an irker.conf file to set irkerhook variables under Subversion.
|
||||||
|
Color highlighting of notification fields can be enabled.
|
||||||
|
irkerhook.py now has its own manual page.
|
||||||
|
Added channelmax variable for rate-limiting.
|
||||||
|
irkerd now uses green threads, with much lower overhead.
|
||||||
|
Fix a bug in handling of channel names with no prefix.
|
||||||
|
|
||||||
|
1.2: 2012-09-30
|
||||||
|
All segments of a message with embedded newlines are now transmitted.
|
||||||
|
Message reduction - irkerhook drops the filelist on excessively long ones.
|
||||||
|
Shell quote hardening in irkerhook.py and some anti-DoS logic.
|
||||||
|
|
||||||
|
1.1: 2012-09-28
|
||||||
|
Add a delay to avoid threads spinning on the empty-queue-check, eating CPU.
|
||||||
|
Fix a bug in reporting of multi-file commits.
|
||||||
|
|
||||||
|
1.0: 2012-09-27
|
||||||
|
First production version, somewhat rushed by the sudden death of cia.vc
|
||||||
|
on 24 September.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
24
README
Normal file
24
README
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
irker - submission tools for IRC notifications
|
||||||
|
|
||||||
|
irkerd is a specialized IRC client that runs as a daemon, allowing
|
||||||
|
other programs to ship IRC notifications by sending JSON objects to a
|
||||||
|
listening socket.
|
||||||
|
|
||||||
|
It is meant to be used by hook scripts in version-control
|
||||||
|
repositories, allowing them to send commit notifications to project
|
||||||
|
IRC channels. A hook script, irkerhook.py, supporting git, hg, and
|
||||||
|
Subversion is included in the distribution; see the install.adoc file
|
||||||
|
for installation instructions.
|
||||||
|
|
||||||
|
The advantage of using this daemon over individual scripted sends
|
||||||
|
is that it can maintain connection state for multiple channels,
|
||||||
|
avoiding obnoxious join/leave spam.
|
||||||
|
|
||||||
|
The file install.adoc describes how to install the software safely, so
|
||||||
|
it can't be used as a spam conduit.
|
||||||
|
|
||||||
|
Please read the files security.adoc and hacking.adoc before modifying
|
||||||
|
this code.
|
||||||
|
|
||||||
|
Eric S. Raymond
|
||||||
|
September 2012
|
29
control
Normal file
29
control
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# This is not a real Debian control file, though the syntax is compatible.
|
||||||
|
# It's project metadata for the shipper tool
|
||||||
|
|
||||||
|
Package: irker
|
||||||
|
|
||||||
|
Description: An IRC client that runs as a daemon accepting notification requests.
|
||||||
|
You present them JSON objects presented to a listening socket. It is
|
||||||
|
meant to be used by hook scripts in version-control repositories,
|
||||||
|
allowing them to send commit notifications to project IRC channels.
|
||||||
|
A hook script that works with git, hg, and svn is included in the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
XBS-Destinations: mailto:patrick@gentoo.org
|
||||||
|
|
||||||
|
Homepage: mailto:packages@qa.debian.org
|
||||||
|
|
||||||
|
XBS-HTML-Target: index.html
|
||||||
|
|
||||||
|
XBS-Repository-URL: https://gitlab.com/esr/irker
|
||||||
|
|
||||||
|
XBS-Debian-Packages: irker
|
||||||
|
|
||||||
|
XBS-OpenHub-URL: http://www.openhub.net/p/irker
|
||||||
|
|
||||||
|
XBS-IRC-Channel: irc://chat.freenode.net/#irker
|
||||||
|
|
||||||
|
XBS-Logo: irker-logo.png
|
||||||
|
|
||||||
|
XBS-VC-Tag-Template: %(version)s
|
13
filter-example.py
Executable file
13
filter-example.py
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# This is a trivial example of a metadata filter.
|
||||||
|
# All it does is change the name of the commit's author.
|
||||||
|
# It could do other things, including modifying the
|
||||||
|
# channels list
|
||||||
|
#
|
||||||
|
import sys, json
|
||||||
|
metadata = json.loads(sys.argv[1])
|
||||||
|
|
||||||
|
metadata['author'] = "The Great and Powerful Oz"
|
||||||
|
|
||||||
|
print json.dumps(metadata)
|
||||||
|
# end
|
35
filter-test.py
Executable file
35
filter-test.py
Executable file
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Test hook to launch an irker instance (if it doesn't already exist)
|
||||||
|
# just before shipping the notification. We start it in in another terminal
|
||||||
|
# so you can watch the debug messages. Intended to be used in the root
|
||||||
|
# directory of the irker repo. Probably only of interest only to irker
|
||||||
|
# developers
|
||||||
|
#
|
||||||
|
# To use this, set up irkerhook.py to fire on each commit. Creating a
|
||||||
|
# .git/hooks/post-commit file containing the line "irkerhook.py"; be
|
||||||
|
# sure to make the opos-commit file executable. Then set the
|
||||||
|
# filtercmd variable in your repo config as follows:
|
||||||
|
#
|
||||||
|
# [irker]
|
||||||
|
# filtercmd = filter-test.py
|
||||||
|
|
||||||
|
import os, sys, json, subprocess, time
|
||||||
|
metadata = json.loads(sys.argv[1])
|
||||||
|
|
||||||
|
ps = subprocess.Popen("ps -U %s uh" % os.getenv("LOGNAME"),
|
||||||
|
shell=True,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
data = ps.stdout.read()
|
||||||
|
irkerd_count = len([x for x in data.split("\n") if x.find("irkerd") != -1])
|
||||||
|
|
||||||
|
if irkerd_count:
|
||||||
|
sys.stderr.write("Using a running irker instance...\n")
|
||||||
|
else:
|
||||||
|
sys.stderr.write("Launching a new irker instance...\n")
|
||||||
|
os.system("gnome-terminal --title 'irkerd' -e 'irkerd -d 2' &")
|
||||||
|
|
||||||
|
time.sleep(1.5) # Avoid a race condition
|
||||||
|
|
||||||
|
print json.dumps(metadata)
|
||||||
|
# end
|
80
hacking.adoc
Normal file
80
hacking.adoc
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
= Hacker's Guide to irker =
|
||||||
|
|
||||||
|
== Design philosopy ==
|
||||||
|
|
||||||
|
Points to you if some of this seems familiar from GPSD...
|
||||||
|
|
||||||
|
=== Keep mechanism and policy separate ===
|
||||||
|
|
||||||
|
Mechanism goes in irkerd. Policy goes in irkerhook.py
|
||||||
|
|
||||||
|
irkerd is intended to be super-simple and completely indifferent to
|
||||||
|
what content passes through it. It doesn't know, in any sense, that
|
||||||
|
the use-case it was designed for is broadcasting notifications from
|
||||||
|
version control systems. irkerhook.py is the part that knows about how
|
||||||
|
to mine data from repositories and sets the format of notifications.
|
||||||
|
|
||||||
|
=== If you think the mechanism needs an option, think again ===
|
||||||
|
|
||||||
|
Because irkerhook.py does policy, it takes policy options. Because
|
||||||
|
irkerd is pure mechanism, it shouldn't need any. If you think it
|
||||||
|
does, you have almost certainly got a bug in your thinking. Fix
|
||||||
|
that before you modify code.
|
||||||
|
|
||||||
|
=== Never configure what you can autoconfigure ===
|
||||||
|
|
||||||
|
Human attention is more expensive than machine time. Humans are
|
||||||
|
careless and failure-prone. Therefore, whenever you make a user tell
|
||||||
|
your code something the code can deduce for itself, you are
|
||||||
|
introducing unnecessary inefficiency and unnecessary failure modes.
|
||||||
|
|
||||||
|
This, in particular, is why irkerhook.py doesn't have a repository
|
||||||
|
type switch. It can deduce the repo type by looking, so it should.
|
||||||
|
|
||||||
|
== Release procedure ==
|
||||||
|
|
||||||
|
1. Check for merge requests at the repository.
|
||||||
|
|
||||||
|
2. Do 'make pylint' to audit the code.
|
||||||
|
|
||||||
|
3. Run irk with a sample message; look at #irker on freenode to verify.
|
||||||
|
|
||||||
|
4. Bump the version numbers in irkerd and irkerhook.py
|
||||||
|
|
||||||
|
5. Run irkerhook.py -n and verify that the output looks sane.
|
||||||
|
|
||||||
|
6. Update the NEWS file
|
||||||
|
|
||||||
|
7. git commit -a
|
||||||
|
|
||||||
|
8. make release
|
||||||
|
|
||||||
|
== Thanks where due ==
|
||||||
|
|
||||||
|
Alexander van Gessel (AI0867) <ai0867@gmail.com> contributed the
|
||||||
|
Subversion support in irkerhook.py. Since the 1.0 release he has
|
||||||
|
kept as close an eye on the code as the author and has fixed at least
|
||||||
|
as many bugs.
|
||||||
|
|
||||||
|
//W. here causes asciidoc to see this as a list entry.
|
||||||
|
W Trevor King <wking@tremily.us> added SSL/TLS support and did
|
||||||
|
significant refactoring work.
|
||||||
|
|
||||||
|
Daniel Franke <dfoxfranke@gmail.com> performed a security audit of irkerd.
|
||||||
|
|
||||||
|
Georg Brandl <georg@python.org> contributed the Mercurial support in
|
||||||
|
irkerhook.py and explained how to make Control-C work right.
|
||||||
|
|
||||||
|
Laurent Bachelier <laurent@bachelier.name> fixed the Makefile so it
|
||||||
|
wouldn't break stuff and wrote the first version of the external
|
||||||
|
filtering option.
|
||||||
|
|
||||||
|
dak180 (name withheld by request) wrote the OS X launchd plist.
|
||||||
|
|
||||||
|
Wulf C. Krueger <philantrop@exherbo.org> wrote the systemd
|
||||||
|
installation support.
|
||||||
|
|
||||||
|
Other people on the freenode #irker channel (Kingpin, fpcfan,
|
||||||
|
shadowm, Rick) smoked out bugs in irkerd before they could seriously
|
||||||
|
bug anybody.
|
||||||
|
|
116
install.adoc
Normal file
116
install.adoc
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
= Forge installation instructions =
|
||||||
|
|
||||||
|
irker and irkerhook.py are intended to be installed on forge sites
|
||||||
|
such as SourceForge, GitHub, GitLab, Gna, and Savannah. This
|
||||||
|
file explains the theory of operation, how to install the code,
|
||||||
|
and how to test it.
|
||||||
|
|
||||||
|
== Prerequisites ==
|
||||||
|
|
||||||
|
You should have Python 3 installed. While Python 2 support
|
||||||
|
has not yet been removed, it is unmaintained and vulnerable
|
||||||
|
to bitrot.
|
||||||
|
|
||||||
|
If you just want to use irkerd and/or irkerhook.py,
|
||||||
|
you need not bother with the Makefile. It's for building
|
||||||
|
the derived versions of the documebtation and rubnning
|
||||||
|
validation tools.
|
||||||
|
|
||||||
|
If you want to run irkerd using a socket proxy,
|
||||||
|
you'll want to do this:
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
pip install -r requirements.txt
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Otherwise the code has no dependencies outside
|
||||||
|
the Python standard library.
|
||||||
|
|
||||||
|
== Theory of operation ==
|
||||||
|
|
||||||
|
irkerhook.py creates JSON notification requests and ships them to
|
||||||
|
irkerd's listener socket. irkerd run as a daemon in order to maintain
|
||||||
|
all the client state required to post multiple notifications while generating
|
||||||
|
a minimum of join/leave messages (which, from the point of view of
|
||||||
|
humans watching irkerd's output, are mere spam).
|
||||||
|
|
||||||
|
See the security.txt document for a detailed discussion of security
|
||||||
|
and DoS vulnerabilities related to irker. The short version: as
|
||||||
|
long as your firewall blocks port 6659 and irkerd is running inside
|
||||||
|
it, you should be fine.
|
||||||
|
|
||||||
|
== Prerequisites ==
|
||||||
|
|
||||||
|
You will need either
|
||||||
|
|
||||||
|
1. Python at version 2.6 or later, which has JSON built in
|
||||||
|
|
||||||
|
2. Python at version no older than 2.4, and a version of the
|
||||||
|
simplejson library installed that it can use. Some newer
|
||||||
|
versions of simplejson discard 2.4 compatibility; 2.0.9
|
||||||
|
is known to work.
|
||||||
|
|
||||||
|
== Installing irkerd ==
|
||||||
|
|
||||||
|
irker needs to run constantly, watching for TCP and UDP traffic on
|
||||||
|
port 6659. Install it accordingly. It has no config file; you can
|
||||||
|
just start it up with no arguments. If you want to see what it's
|
||||||
|
doing, give it command-line options -d info for sparse messages and
|
||||||
|
-d debug to show all traffic with IRC servers.
|
||||||
|
|
||||||
|
You should *not* make irker visible from outside the site firewall, as
|
||||||
|
it can be used to spam IRC channels while masking the source address.
|
||||||
|
The firewall should block port 6659.
|
||||||
|
|
||||||
|
The design of irker assumes the machine on which it is running is also
|
||||||
|
inside the firewall, so that repository hooks can reach port 6659.
|
||||||
|
|
||||||
|
The file org.catb.irkerd.plist is a Mac OS/X plist that can be
|
||||||
|
installed to launch irkerd as a boot-time service on that system.
|
||||||
|
|
||||||
|
irker.service is a systemd unit that can run irkerd as a boot-time
|
||||||
|
service on systems that support systemd. This is configured to
|
||||||
|
run irkerd under a separate user account (irker), so this needs to
|
||||||
|
be set up before starting irker, or the unit needs to be modified
|
||||||
|
to use a different user.
|
||||||
|
|
||||||
|
== Installing irkerhook.py ==
|
||||||
|
|
||||||
|
Under git, a call to irkerhook.py should be installed in the update
|
||||||
|
hook script of your repo. Under Subversion, the call goes in your
|
||||||
|
repo's post-commit script. Under Mercurial there are two different
|
||||||
|
ways to install it. See the irkerhook manual page for details; the
|
||||||
|
source is irkerhook.xml in this distribution.
|
||||||
|
|
||||||
|
SourceForge is a special case: see
|
||||||
|
|
||||||
|
https://github.com/AI0867/sf-git-irker-pipeline
|
||||||
|
|
||||||
|
for tools and instructions on how to work around its limitations.
|
||||||
|
|
||||||
|
== Testing ==
|
||||||
|
|
||||||
|
To verify that your repo produces well-formed JSON notifications,
|
||||||
|
you can run irkerhook.py in the repo directory using the -n switch,
|
||||||
|
which emits JSON to standard output rather than attempting to ship
|
||||||
|
to an irkerd instance.
|
||||||
|
|
||||||
|
Then, start irkerd and call irkerhook.py while watching the freenode
|
||||||
|
#commits channel.
|
||||||
|
|
||||||
|
The 'irk' script is a little test tool that takes two arguments,
|
||||||
|
a channel and a message, and does what you'd expect.
|
||||||
|
|
||||||
|
If you need help, there's a project chat channel at
|
||||||
|
|
||||||
|
irc://chat.freenode.net/#irker
|
||||||
|
|
||||||
|
== Read-only access ==
|
||||||
|
|
||||||
|
If, for whatever reason, you can't modify the hook scripts in your
|
||||||
|
repository, there is still hope. There's a poller daemon that can
|
||||||
|
watch activity in a Subversion repository and ship notifications via
|
||||||
|
an irker instance.
|
||||||
|
|
||||||
|
https://github.com/shikadilord/irker-svnpoller
|
||||||
|
|
65
irk
Executable file
65
irk
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Illustrates how to test irkerd.
|
||||||
|
#
|
||||||
|
# First argument must be a channel URL. If it does not begin with "irc",
|
||||||
|
# the base URL for freenode is prepended.
|
||||||
|
#
|
||||||
|
# Second argument must be a payload string. Standard C-style escapes
|
||||||
|
# such as \n and \t are decoded.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import fileinput
|
||||||
|
|
||||||
|
DEFAULT_SERVER = ("localhost", 6659)
|
||||||
|
|
||||||
|
def connect(server = DEFAULT_SERVER):
|
||||||
|
return socket.create_connection(server)
|
||||||
|
|
||||||
|
def send(s, target, message):
|
||||||
|
data = {"to": target, "privmsg" : message}
|
||||||
|
dump = json.dumps(data)
|
||||||
|
if not isinstance(dump, bytes):
|
||||||
|
dump = dump.encode('ascii')
|
||||||
|
s.sendall(dump)
|
||||||
|
|
||||||
|
def irk(target, message, server = DEFAULT_SERVER):
|
||||||
|
s = connect(server)
|
||||||
|
if "irc:" not in target and "ircs:" not in target:
|
||||||
|
target = "irc://chat.freenode.net/{0}".format(target)
|
||||||
|
if message == '-':
|
||||||
|
for line in fileinput.input('-'):
|
||||||
|
send(s, target, line.rstrip('\n'))
|
||||||
|
else:
|
||||||
|
send(s, target, message)
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
sys.stderr.write("irk: a URL argument is required\n")
|
||||||
|
sys.exit(1)
|
||||||
|
target = sys.argv[1]
|
||||||
|
message = " ".join(sys.argv[2:])
|
||||||
|
# Allows pretty formatting of irker messages
|
||||||
|
if str == bytes:
|
||||||
|
message = message.decode('string_escape')
|
||||||
|
|
||||||
|
# The actual IRC limit is 512. Avoid any off-by-ones
|
||||||
|
chunksize = 511
|
||||||
|
try:
|
||||||
|
while message[:chunksize]:
|
||||||
|
irk(target, message[:chunksize])
|
||||||
|
message = message[chunksize:]
|
||||||
|
except socket.error as e:
|
||||||
|
sys.stderr.write("irk: write to server failed: %r\n" % e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
|
# The following sets edit modes for GNU EMACS
|
||||||
|
# Local Variables:
|
||||||
|
# mode:python
|
||||||
|
# End:
|
84
irk.xml
Normal file
84
irk.xml
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<!DOCTYPE refentry PUBLIC
|
||||||
|
"-//OASIS//DTD DocBook XML V4.1.2//EN"
|
||||||
|
"docbook/docbookx.dtd">
|
||||||
|
<refentry id='irk.8'>
|
||||||
|
<refmeta>
|
||||||
|
<refentrytitle>irk</refentrytitle>
|
||||||
|
<manvolnum>1</manvolnum>
|
||||||
|
<refmiscinfo class='date'>Apr 30 2014</refmiscinfo>
|
||||||
|
<refmiscinfo class='source'>irker</refmiscinfo>
|
||||||
|
<refmiscinfo class='product'>irker</refmiscinfo>
|
||||||
|
<refmiscinfo class='manual'>Commands</refmiscinfo>
|
||||||
|
</refmeta>
|
||||||
|
<refnamediv id='name'>
|
||||||
|
<refname>irk</refname>
|
||||||
|
<refpurpose>test program for irkerd</refpurpose>
|
||||||
|
</refnamediv>
|
||||||
|
<refsynopsisdiv id='synopsis'>
|
||||||
|
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>irk</command>
|
||||||
|
<arg><replaceable>target</replaceable></arg>
|
||||||
|
<arg choice='opt'><replaceable>message text</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</refsynopsisdiv>
|
||||||
|
|
||||||
|
<refsect1 id='description'><title>DESCRIPTION</title>
|
||||||
|
|
||||||
|
<para><application>irk</application> is a simple test program for
|
||||||
|
<citerefentry><refentrytitle>irkerd</refentrytitle><manvolnum>8</manvolnum></citerefentry>. It
|
||||||
|
will construct a simple JSON object and pass it to the daemon running
|
||||||
|
on localhost.</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='options'><title>OPTIONS</title>
|
||||||
|
|
||||||
|
<para><application>irk</application> takes the following options:</para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>target</term>
|
||||||
|
<listitem><para>Which server and channel to join to announced the
|
||||||
|
message. If not prefixed with "irc:", it will prefix
|
||||||
|
"irc://chat.freenode.net/" to the argument before passing it directly
|
||||||
|
to irkerd. This argument is passed as the "to" parameter in the JSON
|
||||||
|
object.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>message</term>
|
||||||
|
<listitem><para>Which message to send to the target specified
|
||||||
|
above. If the string "-", the message will be read from standard
|
||||||
|
input, with newlines stripped.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='limitations'><title>LIMITATIONS</title>
|
||||||
|
|
||||||
|
<para><application>irk</application> has no commandline usage and may
|
||||||
|
be riddled with bugs.</para>
|
||||||
|
|
||||||
|
<para><application>irk</application> doesn't know how to talk to your
|
||||||
|
favorite VCS. You will generally want to use
|
||||||
|
<citerefentry><refentrytitle>irkerhook</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||||
|
instead</para>
|
||||||
|
|
||||||
|
<para><application>irk</application> has also all the limitations of
|
||||||
|
<application>irkerd</application>.</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='see_also'><title>SEE ALSO</title>
|
||||||
|
<para>
|
||||||
|
<citerefentry><refentrytitle>irkerhook</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||||
|
</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='authors'><title>AUTHOR</title>
|
||||||
|
<para>Eric S. Raymond <email>esr@snark.thyrsus.com</email>. See the
|
||||||
|
project page at <ulink
|
||||||
|
url='http://www.catb.org/~esr/irker'>http://www.catb.org/~esr/irker</ulink>
|
||||||
|
for updates and other resources, including an installable repository
|
||||||
|
hook script.</para>
|
||||||
|
</refsect1>
|
||||||
|
</refentry>
|
16
irkerd.service
Normal file
16
irkerd.service
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright 2012 Wulf C. Krueger <philantrop@exherbo.org>
|
||||||
|
# Distributed under the terms of the BSD LICENSE
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Internet Relay Chat (IRC) notification daemon
|
||||||
|
Requires=network.target
|
||||||
|
Documentation=man:irkerd(8) man:irkerhook(1) man:irk(1)
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=irker
|
||||||
|
ExecStart=/usr/bin/irkerd
|
||||||
|
User=irker
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
Alias=irker.service
|
261
irkerd.xml
Normal file
261
irkerd.xml
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
<!DOCTYPE refentry PUBLIC
|
||||||
|
"-//OASIS//DTD DocBook XML V4.1.2//EN"
|
||||||
|
"docbook/docbookx.dtd">
|
||||||
|
<refentry id='irkerd.8'>
|
||||||
|
<refmeta>
|
||||||
|
<refentrytitle>irkerd</refentrytitle>
|
||||||
|
<manvolnum>8</manvolnum>
|
||||||
|
<refmiscinfo class='date'>Aug 27 2012</refmiscinfo>
|
||||||
|
<refmiscinfo class='source'>irker</refmiscinfo>
|
||||||
|
<refmiscinfo class='product'>irker</refmiscinfo>
|
||||||
|
<refmiscinfo class='manual'>Commands</refmiscinfo>
|
||||||
|
</refmeta>
|
||||||
|
<refnamediv id='name'>
|
||||||
|
<refname>irkerd</refname>
|
||||||
|
<refpurpose>relay for shipping notifications to IRC servers</refpurpose>
|
||||||
|
</refnamediv>
|
||||||
|
<refsynopsisdiv id='synopsis'>
|
||||||
|
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>irkerd</command>
|
||||||
|
<arg>-c <replaceable>ca-file</replaceable></arg>
|
||||||
|
<arg>-d <replaceable>debuglevel</replaceable></arg>
|
||||||
|
<arg>-e <replaceable>cert-file</replaceable></arg>
|
||||||
|
<arg>-l <replaceable>logfile</replaceable></arg>
|
||||||
|
<arg>-H <replaceable>host</replaceable></arg>
|
||||||
|
<arg>-n <replaceable>nick</replaceable></arg>
|
||||||
|
<arg>-p <replaceable>password</replaceable></arg>
|
||||||
|
<arg>-P <replaceable>password-file</replaceable></arg>
|
||||||
|
<arg>-i <replaceable>IRC-URL</replaceable></arg>
|
||||||
|
<arg>-t <replaceable>timeout</replaceable></arg>
|
||||||
|
<arg>-V</arg>
|
||||||
|
<arg>-h</arg>
|
||||||
|
<arg choice='opt'><replaceable>message text</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</refsynopsisdiv>
|
||||||
|
|
||||||
|
<refsect1 id='description'><title>DESCRIPTION</title>
|
||||||
|
|
||||||
|
<para><application>irkerd</application> is a specialized write-only IRC
|
||||||
|
client intended to be used for shipping notification messages to IRC
|
||||||
|
channels. The use case in mind when it was designed was broadcasting
|
||||||
|
notifications from commit hooks in version-control systems.</para>
|
||||||
|
|
||||||
|
<para>The main advantage of relaying through this daemon over
|
||||||
|
individual scripted sends from applications is that it can maintain
|
||||||
|
connection state for multiple channels, rather than producing obnoxious
|
||||||
|
join/leave channel spam on every message.</para>
|
||||||
|
|
||||||
|
<para><application>irkerd</application> is a socket server that
|
||||||
|
listens on for UDP or TCP packets on port 6659 for textual request
|
||||||
|
lines containing JSON objects and terminated by a newline. Each JSON
|
||||||
|
object must have two members: "to" specifying a destination or
|
||||||
|
destination list, and "privmsg" specifying the message text.
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
{"to":"irc://chat.freenode.net/git-ciabot", "privmsg":"Hello, world!"}
|
||||||
|
{"to":["irc://chat.freenode.net/#git-ciabot","irc://chat.freenode.net/#gpsd"],"privmsg":"Multichannel test"}
|
||||||
|
{"to":"irc://chat.hypothetical.net:6668/git-ciabot", "privmsg":"Hello, world!"}
|
||||||
|
{"to":"ircs://chat.hypothetical.net/git-private?key=topsecret", "privmsg":"Keyed channel test"}
|
||||||
|
{"to":"ircs://:topsecret@chat.example.net/git-private", "privmsg":"Password-protected server test"}
|
||||||
|
</programlisting></para>
|
||||||
|
|
||||||
|
<para>If the channel part of the URL does not have one of the prefix
|
||||||
|
characters <quote>#</quote>, <quote>&</quote>, or
|
||||||
|
<quote>+</quote>, a <quote>#</quote> will be prepended to it before
|
||||||
|
shipping - <emphasis>unless</emphasis> the channel part has the suffix
|
||||||
|
",isnick" (which is unconditionally removed).</para>
|
||||||
|
|
||||||
|
<para>The host part of the URL may have a port-number suffix separated by a
|
||||||
|
colon, as shown in the third example; otherwise
|
||||||
|
<application>irkerd</application> sends plaintext messages to the default
|
||||||
|
6667 IRC port of each server, and SSL/TLS messages to 6697.</para>
|
||||||
|
|
||||||
|
<para>The password for password-protected servers can be set using the
|
||||||
|
usual <quote>[{username}:{password}@]{host}:{port}</quote> defined in
|
||||||
|
RFC 3986, as shown in the fifth example. Non-empty URL usernames
|
||||||
|
override the default <quote>irker</quote> username.</para>
|
||||||
|
|
||||||
|
<para>When the <quote>to</quote> URL uses the <quote>ircs</quote>
|
||||||
|
scheme (as shown in the fourth and fifth examples), the connection to
|
||||||
|
the IRC server is made via SSL/TLS (vs. a plaintext connection with the
|
||||||
|
<quote>irc</quote> scheme). To connect via SSL/TLS with Python 2.x,
|
||||||
|
you need to explicitly declare the certificate authority file used to
|
||||||
|
verify server certificates. For example, <quote>-c
|
||||||
|
/etc/ssl/certs/ca-certificates.crt</quote>. In Python 3.2 and later,
|
||||||
|
you can still set this option to declare a custom CA file, but
|
||||||
|
<application>irkerd</application>; if you don't set it
|
||||||
|
<application>irkerd</application> will use OpenSSL's default file
|
||||||
|
(using Python's
|
||||||
|
<quote>ssl.SSLContext.set_default_verify_paths</quote>). In Python
|
||||||
|
3.2 and later, <quote>ssl.match_hostname</quote> is used to ensure the
|
||||||
|
server certificate belongs to the intended host, as well as being
|
||||||
|
signed by a trusted CA.</para>
|
||||||
|
|
||||||
|
<para>To join password-protected (mode +k) channels, the channel part of the
|
||||||
|
URL may be followed with a query-string indicating the channel key, of the
|
||||||
|
form <quote>?secret</quote> or <quote>?key=secret</quote>, where
|
||||||
|
<quote>secret</quote> is the channel key.</para>
|
||||||
|
|
||||||
|
<para>An empty message is legal and will cause
|
||||||
|
<application>irkerd</application> to join or maintain a connection to
|
||||||
|
the target channels without actually emitting a message. This may be
|
||||||
|
useful for advertising that an instance is up and running, or for
|
||||||
|
joining a channel to log its traffic.</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='options'><title>OPTIONS</title>
|
||||||
|
|
||||||
|
<para><application>irkerd</application> takes the following options:</para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-d</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Takes a following value, setting the debugging level from it;
|
||||||
|
possible values are 'critical', 'error', 'warning', 'info',
|
||||||
|
'debug'. This option will generally only be of interest to
|
||||||
|
developers, as the logs are designed to help trace
|
||||||
|
<application>irkerd</application>'s internal state. These tracing
|
||||||
|
logs are independent of the traffic logs controlled by
|
||||||
|
<quote>-l</quote>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Logging will be to standard error (if
|
||||||
|
<application>irkerd</application> is running in the foreground) or
|
||||||
|
to <quote>/dev/syslog</quote> with facility "daemon" (if
|
||||||
|
<application>irkerd</application> is running in the background).
|
||||||
|
The background-ness of <application>irkerd</application> is
|
||||||
|
determined by comparing the process group id with the process
|
||||||
|
group associated with the terminal attached to stdout (with
|
||||||
|
non-matches for background processes). We assume you aren't
|
||||||
|
running <application>irkerd</application> in Windows or another OS
|
||||||
|
that doesn't support <quote>os.getpgrp</quote> or
|
||||||
|
<quote>tcgetpgrp</quote>. We assume that if stdout is attached to
|
||||||
|
a TTY associated with the same process group as
|
||||||
|
<application>irkerd</application>, you do intend to log to stderr
|
||||||
|
and not syslog.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-e</term>
|
||||||
|
<listitem><para>Takes a following filename in pem format and uses it
|
||||||
|
to authenticate to the IRC server. You must be connecting to the IRC server
|
||||||
|
over SSL for this to function properly. This is commonly known as
|
||||||
|
<quote>CertFP.</quote>
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-e</term>
|
||||||
|
<listitem><para>Takes a following filename in pem format and uses it
|
||||||
|
to authenticate to the IRC server. You must be connecting to the IRC
|
||||||
|
server over SSL for this to function properly. This is commonly known
|
||||||
|
as <quote>CertFP.</quote></para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-l</term>
|
||||||
|
<listitem><para>Takes a following filename, logs traffic to that file.
|
||||||
|
Each log line consists of three |-separated fields; a numeric
|
||||||
|
timestamp in Unix time, the FQDN of the sending server, and the
|
||||||
|
message data.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-H</term>
|
||||||
|
<listitem><para>Takes a following hostname, and binds to that address
|
||||||
|
when listening for messages. <application>irkerd</application> binds
|
||||||
|
to localhost by default, but you may want to use your host's public
|
||||||
|
address to listen on a local network. Listening on a public interface
|
||||||
|
is not recommended, as it makes spamming IRC channels very
|
||||||
|
easy.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-n</term>
|
||||||
|
<listitem><para>Takes a following value, setting the nick
|
||||||
|
to be used. If the nick contains a numeric format element
|
||||||
|
(such as %03d) it is used to generate suffixed fallback names
|
||||||
|
in the event of a nick collision.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-p</term>
|
||||||
|
<listitem><para>Takes a following value, setting a nickserv
|
||||||
|
password to be used. If given, this password is shipped to
|
||||||
|
authenticate the nick on receipt of a welcome message.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-P</term>
|
||||||
|
<listitem><para>Liuke p, but the argument is interpreted as a filename
|
||||||
|
from which to read the password</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-t</term>
|
||||||
|
<listitem><para>Takes a following value, setting the connection
|
||||||
|
timeout for server-socket opens.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-i</term>
|
||||||
|
<listitem><para>Immediate mode, to be run in foreground. Takes a following
|
||||||
|
following value interpreted as a channel URL. May take a second
|
||||||
|
argument giving a message string; if the second argument is absent the
|
||||||
|
message is read from standard input (and may contain newlines).
|
||||||
|
Sends the message, then quits.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-V</term>
|
||||||
|
<listitem><para>Write the program version to stdout and
|
||||||
|
terminate.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-h</term>
|
||||||
|
<listitem><para>Print usage instructions and terminate.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='limitations'><title>LIMITATIONS</title>
|
||||||
|
<para>Requests via UDP optimizes for lowest latency and network load
|
||||||
|
by avoiding TCP connection setup time; the cost is that delivery is
|
||||||
|
not reliable in the face of packet loss.</para>
|
||||||
|
|
||||||
|
<para>An <application>irkerd</application> instance with a
|
||||||
|
publicly-accessible request socket could complicate blocking of IRC
|
||||||
|
spam by making it easy for spammers to submit while hiding their IP
|
||||||
|
addresses; the better way to deploy, then, is on places like
|
||||||
|
project-hosting sites where the <application>irkerd</application>
|
||||||
|
socket can be visible from commit-hook code but not exposed to the
|
||||||
|
outside world. Priming your firewall with blocklists of IP addresses
|
||||||
|
known to spew spam is always a good idea.</para>
|
||||||
|
|
||||||
|
<para>The absence of any option to set the service port is deliberate.
|
||||||
|
If you think you need to do that, you have a problem better solved at
|
||||||
|
your firewall.</para>
|
||||||
|
|
||||||
|
<para>IRC has a message length limit of 510 bytes; generate your
|
||||||
|
privmsg attribute values with appropriate care.</para>
|
||||||
|
|
||||||
|
<para>IRC ignores any text after an embedded newline. Be aware that
|
||||||
|
<application>irkerd</application> will turn payload strings with
|
||||||
|
embedded newlines into multiple IRC sends to avoid having message data
|
||||||
|
discarded. </para>
|
||||||
|
|
||||||
|
<para>Due to a bug in Python URL parsing, IRC urls with both a # and a
|
||||||
|
key part may fail unexpectedly. The workaround is to remove the #.</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='see_also'><title>SEE ALSO</title>
|
||||||
|
<para>
|
||||||
|
<citerefentry><refentrytitle>irkerhook</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||||
|
</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='authors'><title>AUTHOR</title>
|
||||||
|
<para>Eric S. Raymond <email>esr@snark.thyrsus.com</email>. See the
|
||||||
|
project page at <ulink
|
||||||
|
url='http://www.catb.org/~esr/irker'>http://www.catb.org/~esr/irker</ulink>
|
||||||
|
for updates and other resources, including an installable repository
|
||||||
|
hook script.</para>
|
||||||
|
</refsect1>
|
||||||
|
</refentry>
|
609
irkerhook.py
Executable file
609
irkerhook.py
Executable file
|
@ -0,0 +1,609 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2012 Eric S. Raymond <esr@thyrsus.com>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
'''
|
||||||
|
This script contains git porcelain and porcelain byproducts.
|
||||||
|
Requires either Python 2.6, or 2.5 with the simplejson library installed
|
||||||
|
or Python 3.x.
|
||||||
|
|
||||||
|
usage: irkerhook.py [-V] [-n] [--variable=value...] [commit_id...]
|
||||||
|
|
||||||
|
This script is meant to be run in an update or post-commit hook.
|
||||||
|
Try it with -n to see the notification dumped to stdout and verify
|
||||||
|
that it looks sane. With -V this script dumps its version and exits.
|
||||||
|
|
||||||
|
See the irkerhook manual page in the distribution for a detailed
|
||||||
|
explanation of how to configure this hook.
|
||||||
|
|
||||||
|
The default location of the irker proxy, if the project configuration
|
||||||
|
does not override it.
|
||||||
|
'''
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
from __future__ import print_function, absolute_import
|
||||||
|
|
||||||
|
# pylint: disable=line-too-long,invalid-name,missing-function-docstring,missing-class-docstring,no-else-break,no-else-return,too-many-instance-attributes,too-many-locals,too-many-branches,too-many-statements,redefined-outer-name,import-outside-toplevel,raise-missing-from
|
||||||
|
|
||||||
|
default_server = "localhost"
|
||||||
|
IRKER_PORT = 6659
|
||||||
|
|
||||||
|
# The default service used to turn your web-view URL into a tinyurl so it
|
||||||
|
# will take up less space on the IRC notification line.
|
||||||
|
default_tinyifier = u"http://tinyurl.com/api-create.php?url="
|
||||||
|
|
||||||
|
# Map magic urlprefix values to actual URL prefixes.
|
||||||
|
urlprefixmap = {
|
||||||
|
"viewcvs": "http://%(host)s/viewcvs/%(repo)s?view=revision&revision=",
|
||||||
|
"gitweb": "http://%(host)s/cgi-bin/gitweb.cgi?p=%(repo)s;a=commit;h=",
|
||||||
|
"cgit": "http://%(host)s/cgi-bin/cgit.cgi/%(repo)s/commit/?id=",
|
||||||
|
}
|
||||||
|
|
||||||
|
# By default, ship to the freenode #commits list
|
||||||
|
default_channels = u"irc://chat.freenode.net/#commits"
|
||||||
|
|
||||||
|
#
|
||||||
|
# No user-serviceable parts below this line:
|
||||||
|
#
|
||||||
|
|
||||||
|
version = "2.21"
|
||||||
|
|
||||||
|
# pylint: disable=multiple-imports,wrong-import-position
|
||||||
|
import os, sys, socket, subprocess, locale, datetime, re
|
||||||
|
|
||||||
|
try
|
||||||
|
from shlex import quote as shellquote
|
||||||
|
except ImportError:
|
||||||
|
from pipes import quote as shellquote
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib2 import urlopen, HTTPError
|
||||||
|
except ImportError:
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
try:
|
||||||
|
import simplejson as json # Faster, also makes us Python-2.5-compatible
|
||||||
|
except ImportError:
|
||||||
|
import json
|
||||||
|
|
||||||
|
if sys.version_info.major == 2:
|
||||||
|
# pylint: disable=undefined-variable
|
||||||
|
string_type = unicode
|
||||||
|
else:
|
||||||
|
string_type = str
|
||||||
|
|
||||||
|
try:
|
||||||
|
getstatusoutput = subprocess.getstatusoutput
|
||||||
|
except AttributeError:
|
||||||
|
# pylint: disable=import-error
|
||||||
|
import commands
|
||||||
|
getstatusoutput = commands.getstatusoutput
|
||||||
|
|
||||||
|
def do(command):
|
||||||
|
if sys.version_info.major == 2:
|
||||||
|
return string_type(getstatusoutput(command)[1], locale.getlocale()[1] or 'UTF-8')
|
||||||
|
else:
|
||||||
|
return getstatusoutput(command)[1]
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class Commit:
|
||||||
|
def __init__(self, extractor, commit):
|
||||||
|
"Per-commit data."
|
||||||
|
self.commit = commit
|
||||||
|
self.branch = None
|
||||||
|
self.rev = None
|
||||||
|
self.mail = None
|
||||||
|
self.author = None
|
||||||
|
self.files = None
|
||||||
|
self.logmsg = None
|
||||||
|
self.url = None
|
||||||
|
self.author_name = None
|
||||||
|
self.author_date = None
|
||||||
|
self.commit_date = None
|
||||||
|
self.id = None
|
||||||
|
self.__dict__.update(extractor.__dict__)
|
||||||
|
|
||||||
|
if sys.version_info.major == 2:
|
||||||
|
# Convert __str__ to __unicode__ for python 2
|
||||||
|
self.__unicode__ = self.__str__
|
||||||
|
# Not really needed, but maybe useful for debugging
|
||||||
|
self.__str__ = lambda x: x.__unicode__().encode('utf-8')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"Produce a notification string from this commit."
|
||||||
|
# pylint: disable=no-member
|
||||||
|
if not self.urlprefix:
|
||||||
|
self.url = ""
|
||||||
|
else:
|
||||||
|
# pylint: disable=no-member
|
||||||
|
urlprefix = urlprefixmap.get(self.urlprefix, self.urlprefix)
|
||||||
|
webview = (urlprefix % self.__dict__) + self.commit
|
||||||
|
try:
|
||||||
|
# See it the url is accessible
|
||||||
|
res = urlopen(webview)
|
||||||
|
if self.tinyifier and self.tinyifier.lower() != "none":
|
||||||
|
try:
|
||||||
|
# Didn't get a retrieval error on the web
|
||||||
|
# view, so try to tinyify a reference to it.
|
||||||
|
self.url = urlopen(self.tinyifier + webview).read()
|
||||||
|
try:
|
||||||
|
self.url = self.url.decode('UTF-8')
|
||||||
|
except UnicodeError:
|
||||||
|
pass
|
||||||
|
except IOError:
|
||||||
|
self.url = webview
|
||||||
|
else:
|
||||||
|
self.url = webview
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.code == 401:
|
||||||
|
# Authentication error, so we assume the view is valid
|
||||||
|
self.url = webview
|
||||||
|
else:
|
||||||
|
self.url = ""
|
||||||
|
except IOError:
|
||||||
|
self.url = ""
|
||||||
|
# pylint: disable=no-member
|
||||||
|
res = self.template % self.__dict__
|
||||||
|
return string_type(res, 'UTF-8') if not isinstance(res, string_type) else res
|
||||||
|
|
||||||
|
class GenericExtractor:
|
||||||
|
"Generic class for encapsulating data from a VCS."
|
||||||
|
booleans = ["tcp"]
|
||||||
|
numerics = ["maxchannels"]
|
||||||
|
strings = ["email"]
|
||||||
|
def __init__(self, arguments):
|
||||||
|
self.arguments = arguments
|
||||||
|
self.project = None
|
||||||
|
self.repo = None
|
||||||
|
# These aren't really repo data but they belong here anyway...
|
||||||
|
self.email = None
|
||||||
|
self.tcp = True
|
||||||
|
self.tinyifier = default_tinyifier
|
||||||
|
self.server = None
|
||||||
|
self.channels = None
|
||||||
|
self.maxchannels = 0
|
||||||
|
self.template = None
|
||||||
|
self.urlprefix = None
|
||||||
|
self.host = socket.getfqdn()
|
||||||
|
self.cialike = None
|
||||||
|
self.filtercmd = None
|
||||||
|
# Color highlighting is disabled by default.
|
||||||
|
self.color = None
|
||||||
|
self.bold = self.green = self.blue = self.yellow = self.red = ""
|
||||||
|
self.brown = self.magenta = self.cyan = self.reset = ""
|
||||||
|
def activate_color(self, style):
|
||||||
|
"IRC color codes."
|
||||||
|
if style == 'mIRC':
|
||||||
|
# mIRC colors are mapped as closely to the ANSI colors as
|
||||||
|
# possible. However, bright colors (green, blue, red,
|
||||||
|
# yellow) have been made their dark counterparts since
|
||||||
|
# ChatZilla does not properly darken mIRC colors in the
|
||||||
|
# Light Motif color scheme.
|
||||||
|
self.bold = '\x02'
|
||||||
|
self.green = '\x0303'
|
||||||
|
self.blue = '\x0302'
|
||||||
|
self.red = '\x0304'
|
||||||
|
self.red = '\x0305'
|
||||||
|
self.yellow = '\x0307'
|
||||||
|
self.brown = '\x0305'
|
||||||
|
self.magenta = '\x0306'
|
||||||
|
self.cyan = '\x0310'
|
||||||
|
self.reset = '\x0F'
|
||||||
|
if style == 'ANSI':
|
||||||
|
self.bold = '\x1b[1m'
|
||||||
|
self.green = '\x1b[1;32m'
|
||||||
|
self.blue = '\x1b[1;34m'
|
||||||
|
self.red = '\x1b[1;31m'
|
||||||
|
self.yellow = '\x1b[1;33m'
|
||||||
|
self.brown = '\x1b[33m'
|
||||||
|
self.magenta = '\x1b[35m'
|
||||||
|
self.cyan = '\x1b[36m'
|
||||||
|
self.reset = '\x1b[0m'
|
||||||
|
def load_preferences(self, conf):
|
||||||
|
"Load preferences from a file in the repository root."
|
||||||
|
if not os.path.exists(conf):
|
||||||
|
return
|
||||||
|
ln = 0
|
||||||
|
for line in open(conf):
|
||||||
|
ln += 1
|
||||||
|
if line.startswith("#") or not line.strip():
|
||||||
|
continue
|
||||||
|
if line.count('=') != 1:
|
||||||
|
sys.stderr.write('%s:%d: missing = in config line\n' \
|
||||||
|
% (conf, ln))
|
||||||
|
continue
|
||||||
|
fields = line.split('=')
|
||||||
|
if len(fields) != 2:
|
||||||
|
sys.stderr.write('%s:%d: too many fields in config line\n' \
|
||||||
|
% (conf, ln))
|
||||||
|
continue
|
||||||
|
variable = fields[0].strip()
|
||||||
|
value = fields[1].strip()
|
||||||
|
if value.lower() == "true":
|
||||||
|
value = True
|
||||||
|
elif value.lower() == "false":
|
||||||
|
value = False
|
||||||
|
# User cannot set maxchannels - only a command-line arg can do that.
|
||||||
|
if variable == "maxchannels":
|
||||||
|
return
|
||||||
|
setattr(self, variable, value)
|
||||||
|
def do_overrides(self):
|
||||||
|
"Make command-line overrides possible."
|
||||||
|
for tok in self.arguments:
|
||||||
|
for key in self.__dict__:
|
||||||
|
if tok.startswith("--" + key + "="):
|
||||||
|
val = tok[len(key)+3:]
|
||||||
|
setattr(self, key, val)
|
||||||
|
for (key, val) in self.__dict__.items():
|
||||||
|
if key in GenericExtractor.booleans:
|
||||||
|
if isinstance(val, str) and val.lower() == "true":
|
||||||
|
setattr(self, key, True)
|
||||||
|
elif isinstance(val, str) and val.lower() == "false":
|
||||||
|
setattr(self, key, False)
|
||||||
|
elif key in GenericExtractor.numerics:
|
||||||
|
setattr(self, key, int(val))
|
||||||
|
elif key in GenericExtractor.strings:
|
||||||
|
setattr(self, key, val)
|
||||||
|
if not self.project:
|
||||||
|
sys.stderr.write("irkerhook.py: no project name set!\n")
|
||||||
|
raise SystemExit(1)
|
||||||
|
if not self.repo:
|
||||||
|
self.repo = self.project.lower()
|
||||||
|
if not self.channels:
|
||||||
|
self.channels = default_channels % self.__dict__
|
||||||
|
if self.color and self.color.lower() != "none":
|
||||||
|
self.activate_color(self.color)
|
||||||
|
|
||||||
|
def has(dirname, paths):
|
||||||
|
"Test for existence of a list of paths."
|
||||||
|
# all() is a python2.5 construct
|
||||||
|
for exists in [os.path.exists(os.path.join(dirname, x)) for x in paths]:
|
||||||
|
if not exists:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# VCS-dependent code begins here
|
||||||
|
|
||||||
|
class GitExtractor(GenericExtractor):
|
||||||
|
"Metadata extraction for the git version control system."
|
||||||
|
@staticmethod
|
||||||
|
def is_repository(dirname):
|
||||||
|
# Must detect both ordinary and bare repositories
|
||||||
|
return has(dirname, [".git"]) or \
|
||||||
|
has(dirname, ["HEAD", "refs", "objects"])
|
||||||
|
def __init__(self, arguments):
|
||||||
|
GenericExtractor.__init__(self, arguments)
|
||||||
|
# Get all global config variables
|
||||||
|
self.project = do("git config --get irker.project")
|
||||||
|
self.repo = do("git config --get irker.repo")
|
||||||
|
self.server = do("git config --get irker.server")
|
||||||
|
self.channels = do("git config --get irker.channels")
|
||||||
|
self.email = do("git config --get irker.email")
|
||||||
|
self.tcp = do("git config --bool --get irker.tcp")
|
||||||
|
self.template = do("git config --get irker.template") or u'%(bold)s%(project)s:%(reset)s %(green)s%(author)s%(reset)s %(repo)s:%(yellow)s%(branch)s%(reset)s * %(bold)s%(rev)s%(reset)s / %(bold)s%(files)s%(reset)s: %(logmsg)s %(brown)s%(url)s%(reset)s'
|
||||||
|
self.tinyifier = do("git config --get irker.tinyifier") or default_tinyifier
|
||||||
|
self.color = do("git config --get irker.color")
|
||||||
|
self.urlprefix = do("git config --get irker.urlprefix") or u"gitweb"
|
||||||
|
self.cialike = do("git config --get irker.cialike")
|
||||||
|
self.filtercmd = do("git config --get irker.filtercmd")
|
||||||
|
# These are git-specific
|
||||||
|
self.refname = do("git symbolic-ref HEAD 2>/dev/null")
|
||||||
|
self.revformat = do("git config --get irker.revformat")
|
||||||
|
# The project variable defaults to the name of the repository toplevel.
|
||||||
|
if not self.project:
|
||||||
|
bare = do("git config --bool --get core.bare")
|
||||||
|
if bare.lower() == "true":
|
||||||
|
keyfile = "HEAD"
|
||||||
|
else:
|
||||||
|
keyfile = ".git/HEAD"
|
||||||
|
here = os.getcwd()
|
||||||
|
while True:
|
||||||
|
if os.path.exists(os.path.join(here, keyfile)):
|
||||||
|
self.project = os.path.basename(here)
|
||||||
|
if self.project.endswith('.git'):
|
||||||
|
self.project = self.project[0:-4]
|
||||||
|
break
|
||||||
|
elif here == '/':
|
||||||
|
sys.stderr.write("irkerhook.py: no git repo below root!\n")
|
||||||
|
sys.exit(1)
|
||||||
|
here = os.path.dirname(here)
|
||||||
|
# Get overrides
|
||||||
|
self.do_overrides()
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
def head(self):
|
||||||
|
"Return a symbolic reference to the tip commit of the current branch."
|
||||||
|
return "HEAD"
|
||||||
|
def commit_factory(self, commit_id):
|
||||||
|
"Make a Commit object holding data for a specified commit ID."
|
||||||
|
commit = Commit(self, commit_id)
|
||||||
|
commit.branch = re.sub(r"^refs/[^/]*/", "", self.refname)
|
||||||
|
# Compute a description for the revision
|
||||||
|
if self.revformat == 'raw':
|
||||||
|
commit.rev = commit.commit
|
||||||
|
elif self.revformat == 'short':
|
||||||
|
commit.rev = ''
|
||||||
|
else: # self.revformat == 'describe'
|
||||||
|
commit.rev = do("git describe %s 2>/dev/null" % shellquote(commit.commit))
|
||||||
|
if not commit.rev:
|
||||||
|
# Query git for the abbreviated hash
|
||||||
|
commit.rev = do("git log -1 '--pretty=format:%h' " + shellquote(commit.commit))
|
||||||
|
if self.urlprefix in ('gitweb', 'cgit'):
|
||||||
|
# Also truncate the commit used for the announced urls
|
||||||
|
commit.commit = commit.rev
|
||||||
|
# Extract the meta-information for the commit
|
||||||
|
commit.files = do("git diff-tree -r --name-only " + shellquote(commit.commit))
|
||||||
|
commit.files = " ".join(commit.files.strip().split("\n")[1:])
|
||||||
|
# Design choice: for git we ship only the first message line, which is
|
||||||
|
# conventionally supposed to be a summary of the commit. Under
|
||||||
|
# other VCSes a different choice may be appropriate.
|
||||||
|
commit.author_name, commit.mail, commit.logmsg = \
|
||||||
|
do("git log -1 '--pretty=format:%an%n%ae%n%s' " + shellquote(commit.commit)).split("\n")
|
||||||
|
# This discards the part of the author's address after @.
|
||||||
|
# Might be be nice to ship the full email address, if not
|
||||||
|
# for spammers' address harvesters - getting this wrong
|
||||||
|
# would make the freenode #commits channel into harvester heaven.
|
||||||
|
commit.author = commit.mail.split("@")[0]
|
||||||
|
commit.author_date, commit.commit_date = \
|
||||||
|
do("git log -1 '--pretty=format:%ai|%ci' " + shellquote(commit.commit)).split("|")
|
||||||
|
return commit
|
||||||
|
|
||||||
|
class SvnExtractor(GenericExtractor):
|
||||||
|
"Metadata extraction for the svn version control system."
|
||||||
|
@staticmethod
|
||||||
|
def is_repository(dirname):
|
||||||
|
return has(dirname, ["format", "hooks", "locks"])
|
||||||
|
def __init__(self, arguments):
|
||||||
|
GenericExtractor.__init__(self, arguments)
|
||||||
|
# Some things we need to have before metadata queries will work
|
||||||
|
self.repository = '.'
|
||||||
|
for tok in arguments:
|
||||||
|
if tok.startswith("--repository="):
|
||||||
|
self.repository = tok[13:]
|
||||||
|
self.project = os.path.basename(self.repository)
|
||||||
|
self.template = '%(bold)s%(project)s%(reset)s: %(green)s%(author)s%(reset)s %(repo)s * %(bold)s%(rev)s%(reset)s / %(bold)s%(files)s%(reset)s: %(logmsg)s %(brown)s%(url)s%(reset)s'
|
||||||
|
self.urlprefix = "viewcvs"
|
||||||
|
self.id = None
|
||||||
|
self.load_preferences(os.path.join(self.repository, "irker.conf"))
|
||||||
|
self.do_overrides()
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
def head(self):
|
||||||
|
sys.stderr.write("irker: under svn, hook requires a commit argument.\n")
|
||||||
|
raise SystemExit(1)
|
||||||
|
def commit_factory(self, commit_id):
|
||||||
|
self.id = commit_id
|
||||||
|
commit = Commit(self, commit_id)
|
||||||
|
commit.branch = ""
|
||||||
|
commit.rev = "r%s" % self.id
|
||||||
|
commit.author = self.svnlook("author")
|
||||||
|
commit.commit_date = self.svnlook("date").partition('(')[0]
|
||||||
|
commit.files = self.svnlook("dirs-changed").strip().replace("\n", " ")
|
||||||
|
commit.logmsg = self.svnlook("log").strip()
|
||||||
|
return commit
|
||||||
|
def svnlook(self, info):
|
||||||
|
return do("svnlook %s %s --revision %s" % (shellquote(info), shellquote(self.repository), shellquote(self.id)))
|
||||||
|
|
||||||
|
class HgExtractor(GenericExtractor):
|
||||||
|
"Metadata extraction for the Mercurial version control system."
|
||||||
|
@staticmethod
|
||||||
|
def is_repository(directory):
|
||||||
|
return has(directory, [".hg"])
|
||||||
|
def __init__(self, arguments):
|
||||||
|
from mercurial.encoding import unifromlocal, unitolocal
|
||||||
|
# This fiddling with arguments is necessary since the Mercurial hook can
|
||||||
|
# be run in two different ways: either directly via Python (in which
|
||||||
|
# case hg should be pointed to the hg_hook function below) or as a
|
||||||
|
# script (in which case the normal __main__ block at the end of this
|
||||||
|
# file is exercised). In the first case, we already get repository and
|
||||||
|
# ui objects from Mercurial, in the second case, we have to create them
|
||||||
|
# from the root path.
|
||||||
|
self.repository = None
|
||||||
|
if arguments and isinstance(arguments[0], tuple):
|
||||||
|
# Called from hg_hook function
|
||||||
|
ui, self.repository = arguments[0]
|
||||||
|
arguments = [] # Should not be processed further by do_overrides
|
||||||
|
else:
|
||||||
|
# Called from command line: create repo/ui objects
|
||||||
|
from mercurial import hg, ui as uimod
|
||||||
|
|
||||||
|
repopath = b'.'
|
||||||
|
for tok in arguments:
|
||||||
|
if tok.startswith('--repository='):
|
||||||
|
repopath = unitolocal(tok[13:])
|
||||||
|
ui = uimod.ui()
|
||||||
|
ui.readconfig(os.path.join(repopath, b'.hg', b'hgrc'), repopath)
|
||||||
|
self.repository = hg.repository(ui, repopath)
|
||||||
|
|
||||||
|
GenericExtractor.__init__(self, arguments)
|
||||||
|
# Extract global values from the hg configuration file(s)
|
||||||
|
self.project = unifromlocal(ui.config(b'irker', b'project') or b'')
|
||||||
|
self.repo = unifromlocal(ui.config(b'irker', b'repo') or b'')
|
||||||
|
self.server = unifromlocal(ui.config(b'irker', b'server') or b'')
|
||||||
|
self.channels = unifromlocal(ui.config(b'irker', b'channels') or b'')
|
||||||
|
self.email = unifromlocal(ui.config(b'irker', b'email') or b'')
|
||||||
|
self.tcp = str(ui.configbool(b'irker', b'tcp')) # converted to bool again in do_overrides
|
||||||
|
self.template = unifromlocal(ui.config(b'irker', b'template') or b'')
|
||||||
|
if not self.template:
|
||||||
|
self.template = '%(bold)s%(project)s:%(reset)s %(green)s%(author)s%(reset)s %(repo)s:%(yellow)s%(branch)s%(reset)s * %(bold)s%(rev)s%(reset)s / %(bold)s%(files)s%(reset)s: %(logmsg)s %(brown)s%(url)s%(reset)s'
|
||||||
|
self.tinyifier = unifromlocal(ui.config(
|
||||||
|
b'irker', b'tinyifier',
|
||||||
|
default=default_tinyifier.encode('utf-8')))
|
||||||
|
self.color = unifromlocal(ui.config(b'irker', b'color') or b'')
|
||||||
|
self.urlprefix = unifromlocal(ui.config(
|
||||||
|
b'irker', b'urlprefix', default=ui.config(b'web', b'baseurl')))
|
||||||
|
if self.urlprefix:
|
||||||
|
# self.commit is appended to this by do_overrides
|
||||||
|
self.urlprefix = (
|
||||||
|
self.urlprefix.rstrip('/')
|
||||||
|
+ '/%s/rev/' % unifromlocal(self.repository.root).rstrip('/'))
|
||||||
|
self.cialike = unifromlocal(ui.config(b'irker', b'cialike') or b'')
|
||||||
|
self.filtercmd = unifromlocal(ui.config(b'irker', b'filtercmd') or b'')
|
||||||
|
if not self.project:
|
||||||
|
self.project = os.path.basename(unifromlocal(self.repository.root).rstrip('/'))
|
||||||
|
self.do_overrides()
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
def head(self):
|
||||||
|
"Return a symbolic reference to the tip commit of the current branch."
|
||||||
|
return "-1"
|
||||||
|
def commit_factory(self, commit_id):
|
||||||
|
"Make a Commit object holding data for a specified commit ID."
|
||||||
|
from mercurial.node import short
|
||||||
|
from mercurial.templatefilters import person
|
||||||
|
from mercurial.encoding import unifromlocal, unitolocal
|
||||||
|
if isinstance(commit_id, str) and not isinstance(commit_id, bytes):
|
||||||
|
commit_id = unitolocal(commit_id)
|
||||||
|
ctx = self.repository[commit_id]
|
||||||
|
commit = Commit(self, unifromlocal(short(ctx.hex())))
|
||||||
|
# Extract commit-specific values from a "context" object
|
||||||
|
commit.rev = '%d:%s' % (ctx.rev(), commit.commit)
|
||||||
|
commit.branch = unifromlocal(ctx.branch())
|
||||||
|
commit.author = unifromlocal(person(ctx.user()))
|
||||||
|
commit.author_date = \
|
||||||
|
datetime.datetime.fromtimestamp(ctx.date()[0]).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
commit.logmsg = unifromlocal(ctx.description())
|
||||||
|
# Extract changed files from status against first parent
|
||||||
|
st = self.repository.status(ctx.p1().node(), ctx.node())
|
||||||
|
commit.files = unifromlocal(b' '.join(st.modified + st.added + st.removed))
|
||||||
|
return commit
|
||||||
|
|
||||||
|
def hg_hook(ui, repo, **kwds):
|
||||||
|
# To be called from a Mercurial "commit", "incoming" or "changegroup" hook.
|
||||||
|
# Example configuration:
|
||||||
|
# [hooks]
|
||||||
|
# incoming.irker = python:/path/to/irkerhook.py:hg_hook
|
||||||
|
extractor = HgExtractor([(ui, repo)])
|
||||||
|
start = repo[kwds['node']].rev()
|
||||||
|
end = len(repo)
|
||||||
|
if start != end:
|
||||||
|
# changegroup with multiple commits, so we generate a notification
|
||||||
|
# for each one
|
||||||
|
for rev in range(start, end):
|
||||||
|
ship(extractor, rev, False)
|
||||||
|
else:
|
||||||
|
ship(extractor, kwds['node'], False)
|
||||||
|
|
||||||
|
# The files we use to identify a Subversion repo might occur as content
|
||||||
|
# in a git or hg repo, but the special subdirectories for those are more
|
||||||
|
# reliable indicators. So test for Subversion last.
|
||||||
|
extractors = [GitExtractor, HgExtractor, SvnExtractor]
|
||||||
|
|
||||||
|
# VCS-dependent code ends here
|
||||||
|
|
||||||
|
def convert_message(message):
|
||||||
|
"""Convert the message to bytes to send to the socket"""
|
||||||
|
return message.encode(locale.getlocale()[1] or 'UTF-8') + b'\n'
|
||||||
|
|
||||||
|
def ship(extractor, commit, debug):
|
||||||
|
"Ship a notification for the specified commit."
|
||||||
|
metadata = extractor.commit_factory(commit)
|
||||||
|
|
||||||
|
# This is where we apply filtering
|
||||||
|
if extractor.filtercmd:
|
||||||
|
cmd = '%s %s' % (shellquote(extractor.filtercmd),
|
||||||
|
shellquote(json.dumps(metadata.__dict__)))
|
||||||
|
data = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read()
|
||||||
|
try:
|
||||||
|
metadata.__dict__.update(json.loads(data))
|
||||||
|
except ValueError:
|
||||||
|
sys.stderr.write("irkerhook.py: could not decode JSON: %s\n" % data)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
# Rewrite the file list if too long. The objective here is only
|
||||||
|
# to be easier on the eyes.
|
||||||
|
if extractor.cialike \
|
||||||
|
and extractor.cialike.lower() != "none" \
|
||||||
|
and len(metadata.files) > int(extractor.cialike):
|
||||||
|
files = metadata.files.split()
|
||||||
|
dirs = {d.rpartition('/')[0] for d in files}
|
||||||
|
if len(dirs) == 1:
|
||||||
|
metadata.files = "(%s files)" % (len(files),)
|
||||||
|
else:
|
||||||
|
metadata.files = "(%s files in %s dirs)" % (len(files), len(dirs))
|
||||||
|
# Message reduction. The assumption here is that IRC can't handle
|
||||||
|
# lines more than 510 characters long. If we exceed that length, we
|
||||||
|
# try knocking out the file list, on the theory that for notification
|
||||||
|
# purposes the commit text is more important. If it's still too long
|
||||||
|
# there's nothing much can be done other than ship it expecting the IRC
|
||||||
|
# server to truncate.
|
||||||
|
privmsg = string_type(metadata)
|
||||||
|
if len(privmsg) > 510:
|
||||||
|
metadata.files = ""
|
||||||
|
privmsg = string_type(metadata)
|
||||||
|
|
||||||
|
# Anti-spamming guard. It's deliberate that we get maxchannels not from
|
||||||
|
# the user-filtered metadata but from the extractor data - means repo
|
||||||
|
# administrators can lock in that setting.
|
||||||
|
channels = metadata.channels.split(",")
|
||||||
|
if extractor.maxchannels != 0:
|
||||||
|
channels = channels[:extractor.maxchannels]
|
||||||
|
|
||||||
|
# Ready to ship.
|
||||||
|
message = json.dumps({"to": channels, "privmsg": privmsg})
|
||||||
|
if debug:
|
||||||
|
print(message)
|
||||||
|
elif channels:
|
||||||
|
try:
|
||||||
|
if extractor.email:
|
||||||
|
# We can't really figure out what our SF username is without
|
||||||
|
# exploring our environment. The mail pipeline doesn't care
|
||||||
|
# about who sent the mail, other than being from sourceforge.
|
||||||
|
# A better way might be to simply call mail(1)
|
||||||
|
sender = "irker@users.sourceforge.net"
|
||||||
|
msg = """From: %(sender)s
|
||||||
|
Subject: irker json
|
||||||
|
|
||||||
|
%(message)s""" % {"sender":sender, "message":message}
|
||||||
|
import smtplib
|
||||||
|
smtp = smtplib.SMTP()
|
||||||
|
smtp.connect()
|
||||||
|
smtp.sendmail(sender, extractor.email, msg)
|
||||||
|
smtp.quit()
|
||||||
|
elif extractor.tcp:
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.connect((extractor.server or default_server, IRKER_PORT))
|
||||||
|
sock.sendall(convert_message(message))
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.sendto(convert_message(message), (extractor.server or default_server, IRKER_PORT))
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
except socket.error as e:
|
||||||
|
sys.stderr.write("%s\n" % e)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
notify = True
|
||||||
|
repository = os.getcwd()
|
||||||
|
commits = []
|
||||||
|
for arg in sys.argv[1:]:
|
||||||
|
if arg == '-n':
|
||||||
|
notify = False
|
||||||
|
elif arg == '-V':
|
||||||
|
print("irkerhook.py: version", version)
|
||||||
|
sys.exit(0)
|
||||||
|
elif arg.startswith("--repository="):
|
||||||
|
repository = arg[13:]
|
||||||
|
elif not arg.startswith("--"):
|
||||||
|
commits.append(arg)
|
||||||
|
|
||||||
|
# Figure out which extractor we should be using
|
||||||
|
for candidate in extractors:
|
||||||
|
if candidate.is_repository(repository):
|
||||||
|
cls = candidate
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
sys.stderr.write("irkerhook: cannot identify a repository type.\n")
|
||||||
|
raise SystemExit(1)
|
||||||
|
extractor = cls(sys.argv[1:])
|
||||||
|
|
||||||
|
# And apply it.
|
||||||
|
if not commits:
|
||||||
|
commits = [extractor.head()]
|
||||||
|
for commit in commits:
|
||||||
|
ship(extractor, commit, not notify)
|
||||||
|
|
||||||
|
# The following sets edit modes for GNU EMACS
|
||||||
|
# Local Variables:
|
||||||
|
# mode:python
|
||||||
|
# End:
|
417
irkerhook.xml
Normal file
417
irkerhook.xml
Normal file
|
@ -0,0 +1,417 @@
|
||||||
|
<!DOCTYPE refentry PUBLIC
|
||||||
|
"-//OASIS//DTD DocBook XML V4.1.2//EN"
|
||||||
|
"docbook/docbookx.dtd">
|
||||||
|
<refentry id='irkerhook.1'>
|
||||||
|
<refmeta>
|
||||||
|
<refentrytitle>irkerhook</refentrytitle>
|
||||||
|
<manvolnum>1</manvolnum>
|
||||||
|
<refmiscinfo class='date'>Aug 27 2012</refmiscinfo>
|
||||||
|
<refmiscinfo class='source'>irker</refmiscinfo>
|
||||||
|
<refmiscinfo class='product'>irker</refmiscinfo>
|
||||||
|
<refmiscinfo class='manual'>Commands</refmiscinfo>
|
||||||
|
</refmeta>
|
||||||
|
<refnamediv id='name'>
|
||||||
|
<refname>irkerhook</refname>
|
||||||
|
<refpurpose>repository hook script issuing irker notifications</refpurpose>
|
||||||
|
</refnamediv>
|
||||||
|
<refsynopsisdiv id='synopsis'>
|
||||||
|
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>irkerhook.py</command>
|
||||||
|
<arg>-n</arg>
|
||||||
|
<arg>-V</arg>
|
||||||
|
<group><arg rep='repeat'><replaceable>--variable=value</replaceable></arg></group>
|
||||||
|
<group><arg rep='repeat'><replaceable>commit-id</replaceable></arg></group>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</refsynopsisdiv>
|
||||||
|
|
||||||
|
<refsect1 id='description'><title>DESCRIPTION</title>
|
||||||
|
|
||||||
|
<para><application>irkerhook.py</application> is a Python script intended
|
||||||
|
to be called from the post-commit hook of a version-control repository. Its
|
||||||
|
job is to collect information about the commit that fired the hook (and
|
||||||
|
possibly preferences set by the repository owner) and ship that information
|
||||||
|
to an instance of <application>irkerd</application> for forwarding to
|
||||||
|
various announcement channels.</para>
|
||||||
|
|
||||||
|
<para>The proper invocation and behavior of
|
||||||
|
<application>irkerhook.py</application> varies depending on which
|
||||||
|
VCS (version-control system) is calling it. There are four different places
|
||||||
|
from which it may extract information:</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem><para>Calls to VCS utilities.</para></listitem>
|
||||||
|
<listitem><para>In VCSes like git that support user-settable configuration
|
||||||
|
variables, variables with the prefix "irker.".</para></listitem>
|
||||||
|
<listitem><para>In other VCSes, a configuration file, "irker.conf", in the
|
||||||
|
repository's internals directory.</para></listitem>
|
||||||
|
<listitem><para>Command-line arguments of the form
|
||||||
|
--variable=value.</para></listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<para>The following variables are general to all supported VCSes:</para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>project</term>
|
||||||
|
<listitem>
|
||||||
|
<para>The name of the project. Should be a relatively short identifier;
|
||||||
|
will usually appear at the very beginning of a notification.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>repo</term>
|
||||||
|
<listitem>
|
||||||
|
<para>The name of the repository top-level directory. If not
|
||||||
|
specified, defaults to a lowercased copy of the project name.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>channels</term>
|
||||||
|
<listitem>
|
||||||
|
<para>An IRC channel URL, or comma-separated list of same, identifying
|
||||||
|
channels to which notifications are to be sent. If not specified, the
|
||||||
|
default is the freenode #commits channel.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>server</term>
|
||||||
|
<listitem>
|
||||||
|
<para>The host on which the notification-relaying irker daemon is expected
|
||||||
|
to reside. Defaults to "localhost".</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>email</term>
|
||||||
|
<listitem>
|
||||||
|
<para>If set, use email for communication rather than TCP or UDP.
|
||||||
|
The value is used as the target mail address.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>tcp</term>
|
||||||
|
<listitem>
|
||||||
|
<para>If "true", use TCP for communication; if "false", use UDP.
|
||||||
|
Defaults to "false".</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>urlprefix</term>
|
||||||
|
<listitem>
|
||||||
|
<para>Changeset URL prefix for your repo. When the commit ID is appended
|
||||||
|
to this, it should point at a CGI that will display the commit
|
||||||
|
through cgit, gitweb or something similar. The defaults will probably
|
||||||
|
work if you have a typical gitweb/cgit setup.</para>
|
||||||
|
|
||||||
|
<para>If the value of this variable is "None", generation of the URL
|
||||||
|
field in commit notifications will be suppressed. Other magic values
|
||||||
|
are "cgit", "gitweb", and "viewcvs", which expand to URL templates
|
||||||
|
that will usually work with those systems.</para>
|
||||||
|
|
||||||
|
<para>The magic cookies "%(host)s" and %(repo)s" may occur in this
|
||||||
|
URL. The former is expanded to the FQDN of the host on which
|
||||||
|
<application>irkerhook.py</application> is running; the latter is
|
||||||
|
expanded to the value of the "repo" variable.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>tinyifier</term>
|
||||||
|
<listitem>
|
||||||
|
<para>URL template pointing to a service for compressing URLs so they
|
||||||
|
will take up less space in the notification line. If the value of this
|
||||||
|
variable is "None", no compression will be attempted.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>color</term>
|
||||||
|
<listitem>
|
||||||
|
<para>If "mIRC", highlight notification fields with mIRC color codes.
|
||||||
|
If "ANSI", highlight notification fields with ANSI color escape
|
||||||
|
sequences. Defaults to "none" (no colors). ANSI codes are supported
|
||||||
|
in Chatzilla, irssi, ircle, and BitchX; mIRC codes only are recognized
|
||||||
|
in mIRC, XChat, KVirc, Konversation, or weechat.</para>
|
||||||
|
|
||||||
|
<para>Note: if you turn this on and notifications stop appearing on
|
||||||
|
your channel, you need to turn off IRC's color filter on that channel.
|
||||||
|
To do this you will need op privileges; issue the command "/mode
|
||||||
|
<channel> -c" with <channel> replaced by your channel name.
|
||||||
|
You may need to first issue the command "/msg chanserv set
|
||||||
|
<channel> MLOCK +nt-slk".</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>maxchannels</term>
|
||||||
|
<listitem>
|
||||||
|
<para>Interpreted as an integer. If not zero, limits the number of
|
||||||
|
channels the hook will interpret from the "channels" variable.</para>
|
||||||
|
|
||||||
|
<para>This variable cannot be set through VCS configuration variables
|
||||||
|
or <filename>irker.conf</filename>; it can only be set with a command-line
|
||||||
|
argument. Thus, on a forge site in which repository owners are not
|
||||||
|
allowed to modify their post-commit scripts, a site administrator can set it
|
||||||
|
to prevent shotgun spamming by malicious project owners. Setting it to
|
||||||
|
a value less than 2, however, would probably be unwise.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>cialike</term>
|
||||||
|
<listitem>
|
||||||
|
<para>If not empty and not "None" (the default), this emulates the old
|
||||||
|
CIA behavior of dropping long lists of files in favor of a summary of
|
||||||
|
the form (N files in M directories). The value must be numeric giving
|
||||||
|
a threshold value for the length of the file list in
|
||||||
|
characters.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>template</term>
|
||||||
|
<listitem>
|
||||||
|
<para>Set the template used to generate notification messages. Only
|
||||||
|
available in VCses with config variables; presently this means git or
|
||||||
|
hg. All basic commit and extractor fields, including color switches,
|
||||||
|
are available as %() substitutions.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
<para>irkerhook.py will run under both python 2 and python 3, but it does
|
||||||
|
not support mercurial repositories under python 3 yet.</para>
|
||||||
|
|
||||||
|
<refsect2 id="git"><title>git</title>
|
||||||
|
|
||||||
|
<para>Under git, the normal way to invoke this hook (from within the
|
||||||
|
update hook) passes it a refname followed by a list of commits. Because
|
||||||
|
<command>git rev-list</command> normally lists from most recent to oldest,
|
||||||
|
you'll want to use --reverse to make notifications be omitted in chronological
|
||||||
|
order. In a normal update script, the invocation should look like this</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
refname=$1
|
||||||
|
old=$2
|
||||||
|
new=$3
|
||||||
|
irkerhook.py --refname=${refname} $(git rev-list --reverse ${old}..${new})
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>except that you'll need an absolute path for irkerhook.py.</para>
|
||||||
|
|
||||||
|
<para>For testing purposes and backward compatibility, if you invoke
|
||||||
|
<application>irkerhook.py</application> with no arguments (as in a
|
||||||
|
post-commit hook) it will behave as though it had been called like
|
||||||
|
this:</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
irkerhook.py --refname=refs/heads/master HEAD
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>However, this will not give the right result when you push to
|
||||||
|
a non-default branch of a bare repo.</para>
|
||||||
|
|
||||||
|
<para>A typical way to install this hook is actually in the
|
||||||
|
<filename>post-receive</filename> hook, because it gets all the
|
||||||
|
necessary details and will not abort the push on failure. Use the
|
||||||
|
following script:</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "sending IRC notification"
|
||||||
|
while read old new refname; do
|
||||||
|
irkerhook --refname=${refname} $(git rev-list --reverse ${old}..${new})
|
||||||
|
done
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>Preferences may be set in the repo <filename>config</filename>
|
||||||
|
file in an [irker] section. Here is an example of what that can look
|
||||||
|
like:</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
[irker]
|
||||||
|
project = gpsd
|
||||||
|
color = ANSI
|
||||||
|
channels = irc://chat.freenode.net/gpsd,irc://chat.freenode.net/commits
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para> You should not set the "repository" variable (an equivalent
|
||||||
|
will be computed). No attempt is made to interpret an
|
||||||
|
<filename>irker.conf</filename> file.</para>
|
||||||
|
|
||||||
|
<para>The default value of the "project" variable is the basename
|
||||||
|
of the repository directory. The default value of the "urlprefix"
|
||||||
|
variable is "cgit".</para>
|
||||||
|
|
||||||
|
<para>There is one git-specific variable, "revformat", controlling
|
||||||
|
the format of the commit identifier in a notification. It
|
||||||
|
may have the following values:</para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>raw</term>
|
||||||
|
<listitem><para>full hex ID of commit</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>short</term>
|
||||||
|
<listitem><para>first 12 chars of hex ID</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>describe</term>
|
||||||
|
<listitem><para>describe relative to last tag, falling back to short</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
<para>The default is 'describe'.</para>
|
||||||
|
</refsect2>
|
||||||
|
|
||||||
|
<refsect2 id="svn"><title>Subversion</title>
|
||||||
|
|
||||||
|
<para>Under Subversion, <application>irkerhook.py</application>
|
||||||
|
accepts a --repository option with value (the absolute pathname of the
|
||||||
|
Subversion repository) and a commit argument (the numeric revision level of
|
||||||
|
the commit). The defaults are the current working directory and HEAD,
|
||||||
|
respectively.</para>
|
||||||
|
|
||||||
|
<para>Note, however, that you <emphasis>cannot</emphasis> default the
|
||||||
|
repository argument inside a Subversion post-commit hook; this is
|
||||||
|
because of a limitation of Subversion, which is that getting the
|
||||||
|
current directory is not reliable inside these hooks. Instead, the
|
||||||
|
values must be the two arguments that Subversion passes to that hook
|
||||||
|
as arguments. Thus, a typical invocation in the post-commit script
|
||||||
|
will look like this:</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
REPO=$1
|
||||||
|
REV=$2
|
||||||
|
irkerhook.py --repository=$REPO $REV
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>Other --variable=value settings may also be
|
||||||
|
given on the command line, and will override any settings in an
|
||||||
|
<filename>irker.conf</filename> file.</para>
|
||||||
|
|
||||||
|
<para>The default for the project variable is the basename of the
|
||||||
|
repository. The default value of the "urlprefix" variable is
|
||||||
|
"viewcvs".</para>
|
||||||
|
|
||||||
|
<para>If an <filename>irker.conf</filename> file exists in the repository
|
||||||
|
root directory (not the checkout directory but where internals such as the
|
||||||
|
"format" file live) the hook will interpret variable settings from it. Here
|
||||||
|
is an example of what such a file might look like:</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
# irkerhook variable settings for the irker project
|
||||||
|
project = irker
|
||||||
|
channels = irc://chat.freenode/irker,irc://chat.freenode/commits
|
||||||
|
tcp = false
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>Don't set the "repository" or "commit" variables in this file;
|
||||||
|
that would have unhappy results.</para>
|
||||||
|
|
||||||
|
<para>There are no Subversion-specific variables.</para>
|
||||||
|
|
||||||
|
</refsect2>
|
||||||
|
|
||||||
|
<refsect2 id="hg"><title>Mercurial</title>
|
||||||
|
|
||||||
|
<para>Under Mercurial, <application>irkerhook.py</application> can be
|
||||||
|
invoked in two ways: either as a Python hook (preferred) or as a
|
||||||
|
script.</para>
|
||||||
|
|
||||||
|
<para>To call it as a Python hook, add the collowing to the
|
||||||
|
"commit" or "incoming" hook declaration in your Mercurial
|
||||||
|
repository:</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
[hooks]
|
||||||
|
incoming.irker = python:/path/to/irkerhook.py:hg_hook
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>When called as a script, the hook accepts a --repository option
|
||||||
|
with value (the absolute pathname of the Mercurial repository) and can
|
||||||
|
take a commit argument (the Mercurial hash ID of the commit or a
|
||||||
|
reference to it). The default for the repository argument is the
|
||||||
|
current directory. The default commit argument is '-1', designating
|
||||||
|
the current tip commit.</para>
|
||||||
|
|
||||||
|
<para>As for git, in both cases all variables may be set in the repo
|
||||||
|
<filename>hgrc</filename> file in an [irker] section. Command-line
|
||||||
|
variable=value arguments are accepted but not required for script
|
||||||
|
invocation. No attempt is made to interpret an
|
||||||
|
<filename>irker.conf</filename> file.</para>
|
||||||
|
|
||||||
|
<para>The default value of the "project" variable is the basename
|
||||||
|
of the repository directory. The default value of the "urlprefix"
|
||||||
|
variable is the value of the "web.baseurl" config value, if it
|
||||||
|
exists.</para>
|
||||||
|
|
||||||
|
</refsect2>
|
||||||
|
|
||||||
|
<refsect2 id="filter"><title>Filtering</title>
|
||||||
|
|
||||||
|
<para>It is possible to filter commits before sending them to
|
||||||
|
<application>irkerd</application>.</para>
|
||||||
|
|
||||||
|
<para>You have to specify the <option>filtercmd</option> option, which
|
||||||
|
will be the command <application>irkerhook.py</application> will
|
||||||
|
run. This command should accept one arguments, which is a JSON
|
||||||
|
representation of commit and extractor metadata (including the
|
||||||
|
channels variable). The command should emit to standard output a JSON
|
||||||
|
representation of (possibly altered) metadata.</para>
|
||||||
|
|
||||||
|
<para>Below is an example filter:</para>
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# This is a trivial example of a metadata filter.
|
||||||
|
# All it does is change the name of the commit's author.
|
||||||
|
#
|
||||||
|
import sys, json
|
||||||
|
metadata = json.loads(sys.argv[1])
|
||||||
|
|
||||||
|
metadata['author'] = "The Great and Powerful Oz"
|
||||||
|
|
||||||
|
print json.dumps(metadata)
|
||||||
|
# end
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>Standard error is available to the hook for progress and
|
||||||
|
error messages.</para>
|
||||||
|
|
||||||
|
</refsect2>
|
||||||
|
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='options'><title>OPTIONS</title>
|
||||||
|
|
||||||
|
<para><application>irkerhook.py</application> takes the following
|
||||||
|
options:</para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-n</term>
|
||||||
|
<listitem><para>Suppress transmission to a daemon. Instead, dump the
|
||||||
|
generated JSON request to standard output. Useful for
|
||||||
|
debugging.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>-V</term>
|
||||||
|
<listitem><para>Write the program version to stdout and
|
||||||
|
terminate.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='see_also'><title>SEE ALSO</title>
|
||||||
|
<para>
|
||||||
|
<citerefentry><refentrytitle>irkerd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||||
|
</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id='authors'><title>AUTHOR</title>
|
||||||
|
<para>Eric S. Raymond <email>esr@snark.thyrsus.com</email>. See the
|
||||||
|
project page at <ulink
|
||||||
|
url='http://www.catb.org/~esr/irker'>http://www.catb.org/~esr/irker</ulink>
|
||||||
|
for updates and other resources.</para>
|
||||||
|
</refsect1>
|
||||||
|
</refentry>
|
||||||
|
|
20
org.catb.irkerd.plist
Normal file
20
org.catb.irkerd.plist
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>org.catb.irkerd</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/usr/bin/irkerd</string>
|
||||||
|
</array>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string>nobody</string>
|
||||||
|
<key>GroupName</key>
|
||||||
|
<string>nobody</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
PySocks==1.5.6
|
268
security.adoc
Normal file
268
security.adoc
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
= Security analysis of irker =
|
||||||
|
|
||||||
|
This is an analysis of security and DoS vulnerabilities associated
|
||||||
|
with irker, exploring and explaining certain design choices. Much of
|
||||||
|
it derives from a code audit and report by Daniel Franke.
|
||||||
|
|
||||||
|
== Assumptions and Goals ==
|
||||||
|
|
||||||
|
We begin by stating some assumptions about how irker will be deployed,
|
||||||
|
and articulating a set of security goals.
|
||||||
|
|
||||||
|
Communication flow in an irker deployment will look like this:
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
Committers
|
||||||
|
|
|
||||||
|
|
|
||||||
|
Version-control repositories
|
||||||
|
|
|
||||||
|
|
|
||||||
|
irkerhook.py
|
||||||
|
|
|
||||||
|
|
|
||||||
|
irkerd
|
||||||
|
|
|
||||||
|
|
|
||||||
|
IRC servers
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Here are our assumptions:
|
||||||
|
|
||||||
|
1. The repositories are hosted on a public forge sites such as
|
||||||
|
SourceForge, GitHub, Gitorious, Savannah, or Gna and must be
|
||||||
|
accessible to untrusted users.
|
||||||
|
|
||||||
|
2. Repository project owners can set properties on their repositories
|
||||||
|
(including but not limited to irker.*), and may be able to set custom
|
||||||
|
post-commit hooks which can execute arbitrary code on the repository
|
||||||
|
server. In particular, these people my be able to modify the local
|
||||||
|
copy of irkerhook.py.
|
||||||
|
|
||||||
|
3. The machine which hosts irkerd has the same owner as the machine which
|
||||||
|
hosts the the repo; these machines are possibly but not necessarily
|
||||||
|
one and the same.
|
||||||
|
|
||||||
|
4. The network is protected by a perimeter firewall, and only a
|
||||||
|
trusted group is able to emit arbitrary packets from inside the
|
||||||
|
perimeter; committers are not necessarily part of this group.
|
||||||
|
|
||||||
|
5. irkerd communicates with IRC servers over the open internet,
|
||||||
|
and an IRC server's administrator is assumed to hold no position of
|
||||||
|
trust with any other party.
|
||||||
|
|
||||||
|
We can, accordingly, identify the following groups of security
|
||||||
|
principals:
|
||||||
|
|
||||||
|
A. irker administrators.
|
||||||
|
B. Project committers.
|
||||||
|
C. Project owners
|
||||||
|
D. IRC server administrators.
|
||||||
|
E. Other people on irker's internal network.
|
||||||
|
F. irkerd-IRC men-in-the-middle (i.e. people who control the network path
|
||||||
|
between irkerd and the IRC server).
|
||||||
|
G. Random people on the internet.
|
||||||
|
|
||||||
|
Our security goals for irker can be enumerated as follows:
|
||||||
|
|
||||||
|
* Control: We don't want anyone outside group A gaining control of
|
||||||
|
the machines which host irkerd or the git repos.
|
||||||
|
|
||||||
|
* Availability: Only group A should be able to to deny or degrade
|
||||||
|
irkerd's ability to receive commit messages and relay them to the
|
||||||
|
IRC server. We recognize and accept as inevitable that MITMs (groups
|
||||||
|
E and F) can do this too (by ARP spoofing, cable-cutting, etc.).
|
||||||
|
But, in particular, we would like irker-mediated services to be
|
||||||
|
resilient against DoS (denial of service) attacks.
|
||||||
|
|
||||||
|
* Authentication/integrity: Notifications should be truthful, i.e.,
|
||||||
|
commit messages sent to IRC channels should actually reflect that a
|
||||||
|
corresponding commit has taken place. We accept that groups A, C,
|
||||||
|
D, and E can violate this property.
|
||||||
|
|
||||||
|
* Secrecy: irker shouldn't aid spammers (group G) in harvesting
|
||||||
|
committers' email addresses.
|
||||||
|
|
||||||
|
* Auditability: If people abuse irkerd, we want to be able to identify
|
||||||
|
the abusive account or IP address.
|
||||||
|
|
||||||
|
== Control Issues ==
|
||||||
|
|
||||||
|
We have audited the irker and irkerhook.py code for exploitable
|
||||||
|
vulnerabilities. We have not found any in the code itself, and the
|
||||||
|
use of Python gives us confidence in the absence of large classes of errors
|
||||||
|
(such as buffer overruns) that afflict C programs.
|
||||||
|
|
||||||
|
However, the fact that irkerhook.py relies on external binaries to
|
||||||
|
mine data out of its repository opens up a well-known set of
|
||||||
|
vulnerabilities if a malicious user is able to insert binaries in a
|
||||||
|
carelessly-set execution path. Normal precautions against this should
|
||||||
|
be taken.
|
||||||
|
|
||||||
|
== Availability ==
|
||||||
|
|
||||||
|
=== Solved problems ===
|
||||||
|
|
||||||
|
When the original implementation of irkerd saw a nick collision it
|
||||||
|
generated new nicks in a predictable sequence. A malicious IRC user
|
||||||
|
could have continuously changed his own nick to the next one that
|
||||||
|
irkerd is going to try. Some randomness has been added to nick
|
||||||
|
generation to prevent this.
|
||||||
|
|
||||||
|
=== Unsolved problems ===
|
||||||
|
|
||||||
|
DoS attacks on any networked application can never completely
|
||||||
|
prevented, only mitigated by forcing attackers to invest more
|
||||||
|
resources. Here we consider the easiest attack paths against irker,
|
||||||
|
and possible countermeasures.
|
||||||
|
|
||||||
|
irker handles each connection to a particular IRC server in a separate
|
||||||
|
thread - actually, due to server limits on open channels per
|
||||||
|
connection, there may be multiple sessions per server. This may not
|
||||||
|
scale well, especially on 32-bit architectures.
|
||||||
|
|
||||||
|
Thread instance overhead, combined with the lack of any restriction on
|
||||||
|
how many URLs can appear in the 'to' list, is a DoS vulnerability. If
|
||||||
|
a repository's properties specify that notifications should go to more
|
||||||
|
than about 500 unique hostnames, then on 32-bit architectures we'll
|
||||||
|
hit the 4GB cap on virtual memory (even while the resident set size
|
||||||
|
remains small).
|
||||||
|
|
||||||
|
Another ceiling to watch out for is the ulimit on file descriptors,
|
||||||
|
which defaults to 1024 on many Linux systems but can safely be set
|
||||||
|
much larger. Each connection instance costs a file descriptor.
|
||||||
|
|
||||||
|
We consider some possible ways of addressing the problem:
|
||||||
|
|
||||||
|
1. Limit the number of URLs in a request. Pretty painless - it will
|
||||||
|
be very rare that anyone wants to specify a larger set than a project
|
||||||
|
channel plus freenode #commits - but also ineffective. A malicious
|
||||||
|
hook could achieve DoS simply by spamming lots of requests.
|
||||||
|
|
||||||
|
2. Limit the total number of requests than can be queued. Completely
|
||||||
|
ineffective - just sets a target for the DoS attack.
|
||||||
|
|
||||||
|
3. Limit the number of requests that can be queued by source IP address.
|
||||||
|
This might be worth doing; it would stymie a single-source DoS attack through
|
||||||
|
a publicly-exposed irkerd, though not a DDoS by a botnet. But there isn't
|
||||||
|
a lot of win here for a properly installed irker (e.g. behind a firewall),
|
||||||
|
which is typically going to get all its requests from a single repo host
|
||||||
|
anyway.
|
||||||
|
|
||||||
|
4. Rate-limit requests by source IP address - that is, after any request
|
||||||
|
discard additional ones during some timeout period. Again, good for
|
||||||
|
stopping a single-source DoS against an exposed irker, won't stop a
|
||||||
|
DDoS. The real problem though, is that any such rate limit might interfere
|
||||||
|
with legitimate high-volume use by a very active repo site.
|
||||||
|
|
||||||
|
After this we appear to have run out of easy options, as source IP address
|
||||||
|
is the only thing irkerd can see that an attacker can't spoof.
|
||||||
|
|
||||||
|
We mitigate some availability risks by reaping old sessions when we're
|
||||||
|
near resource limits. An ordinary DoS attack would then be prevented
|
||||||
|
from completely blocking all message traffic; the cost would be a
|
||||||
|
whole lot of join/leave spam due to connection churn.
|
||||||
|
|
||||||
|
== Authentication/Integrity ==
|
||||||
|
|
||||||
|
One way to help prevent DoS attacks would be in-band authentication -
|
||||||
|
requiring irkerd submitters to present a credential along with each
|
||||||
|
message submission. In principle this, if it existed, could also be used
|
||||||
|
to verify that a submitter is authorized to issue notifications with
|
||||||
|
respect to a given project.
|
||||||
|
|
||||||
|
We rejected this approach. The design goal for irker was to make
|
||||||
|
submissions fast, cheap, and stateless; baking an authentication
|
||||||
|
system directly into the irkerd codebase would have conflicted with
|
||||||
|
these objectives, not to mention probably becoming the camel's nose
|
||||||
|
for a godawful amount of code bloat.
|
||||||
|
|
||||||
|
The deployment advice in the installation instructions assumes that
|
||||||
|
irkerd submitters are "authenticated" by being inside a firewall - that is,
|
||||||
|
mesages are issued from an intranet and it can be trusted that anyone
|
||||||
|
issuing messages from within a given intranet is authorized to do so.
|
||||||
|
This fits the assumption that irker instances will run on forge sites
|
||||||
|
receiving requests from instances of irkerhook.py.
|
||||||
|
|
||||||
|
One larger issue (not unique to irker) is that because of the
|
||||||
|
insecured nature of IRC it is essentially impossible to secure
|
||||||
|
#commits against commit notifications that are either garbled by
|
||||||
|
software errors and misconfigurations or maliciously crafted to
|
||||||
|
confuse anyone attempting to gather statistics from that channel. The
|
||||||
|
lesson here is that IRC monitoring isn't a good method for that
|
||||||
|
purpose; going direct to the repositories via a toolkit such as Ohloh
|
||||||
|
is a far better idea.
|
||||||
|
|
||||||
|
When this analysis was originally written, we recommended using spiped
|
||||||
|
or stunnel to solve the problem of passing notifications from irkerd
|
||||||
|
to IRC servers over a potentially hostile network that might interfere
|
||||||
|
with them. Later, SSL/TLS support proved easy to add and is now in
|
||||||
|
irkerd itself.
|
||||||
|
|
||||||
|
== Secrecy ==
|
||||||
|
|
||||||
|
irkerd has no inherent secrecy risks.
|
||||||
|
|
||||||
|
The distributed version of irkerhook.py removes the host part of
|
||||||
|
author addresses specifically in order to prevent address harvesting
|
||||||
|
from the notifications.
|
||||||
|
|
||||||
|
== Auditability ==
|
||||||
|
|
||||||
|
We previously noted that source IP address is the only thing irker can
|
||||||
|
see that an attacker can't spoof. This makes auditability difficult
|
||||||
|
unless we impose conventions on the notifications passing though it.
|
||||||
|
|
||||||
|
The irkerhook.py that we ship inherits an auditability property from
|
||||||
|
the CIA service it was designed to replace: the first field of every
|
||||||
|
notification (terminated by a colon) is the name of the issuing
|
||||||
|
project. The only other competitor to replace CIA known to us
|
||||||
|
(kgb_bot) shares this property.
|
||||||
|
|
||||||
|
In the general case we cannot guarantee this property against
|
||||||
|
groups A and F.
|
||||||
|
|
||||||
|
== Risks relative to centralized services ==
|
||||||
|
|
||||||
|
irker and irkerhook.py were written as a replacement for the
|
||||||
|
now-defunct CIA notification service. The author has written
|
||||||
|
a critique of that service: "CIA and the perils of overengineering"
|
||||||
|
at <http://esr.ibiblio.org/?p=4540>. It is thus worth considering how
|
||||||
|
a risk assessment of CIA compares to this one.
|
||||||
|
|
||||||
|
The principal advantages of CIA from a security point of view were (a)
|
||||||
|
it provided a single point at which spam filtering and source blocking
|
||||||
|
could be done with benefit to all projects using the service, and (b)
|
||||||
|
since it had to have a database anyway for routing messages to project
|
||||||
|
channels, the incremental overhead for an authentication feature would
|
||||||
|
have been relatively low.
|
||||||
|
|
||||||
|
As a matter of fact rather than theory CIA never fully exploited
|
||||||
|
either possibility. Anyone could create a CIA project entry with
|
||||||
|
fanout to any desired set of IRC channels. Notifications were not
|
||||||
|
authenticated, so anyone could masquerade as a member of any project.
|
||||||
|
The only check on abuse was human intervention to source-block
|
||||||
|
spammers, and this was by no means completely effective - spam shipped
|
||||||
|
via CIA was occasionally seen on on the freenode #commits channel.
|
||||||
|
|
||||||
|
The principal security disadvantage of CIA was that it meant the
|
||||||
|
entire notification system was subject to single-point failure due
|
||||||
|
to software or hosting failures on cia.vc, or to DoS attacks
|
||||||
|
against the server. While there is no evidence that the site
|
||||||
|
was ever deliberately DoSed, failures were sufficiently common
|
||||||
|
that a half-hearted DoS attack might not have been even noticed.
|
||||||
|
|
||||||
|
Despite the absence of authentication, irker instances on
|
||||||
|
properly firewalled intranets do not obviously pose additional
|
||||||
|
spamming risks beyond those incurred by the CIA service. The
|
||||||
|
overall robustness of the notification system as a whole should
|
||||||
|
be greatly improved.
|
||||||
|
|
||||||
|
== Conclusions ==
|
||||||
|
|
||||||
|
The security and DoS issues irker has are not readily addressable by
|
||||||
|
changing the irker codebase itself, short of a complete (much more
|
||||||
|
complex and heavyweight) redesign. They are largely implicit risks of
|
||||||
|
its operating environment and must be managed by properly controlling
|
||||||
|
access to irker instances.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue