14 Commits

16 changed files with 216 additions and 72 deletions

2
.gitignore vendored
View File

@ -1,5 +1,6 @@
/reaction /reaction
/ip46tables /ip46tables
/nft46
/reaction*.db /reaction*.db
/reaction*.sock /reaction*.sock
/result /result
@ -7,3 +8,4 @@
/deb /deb
*.deb *.deb
*.minisig *.minisig
*.qcow2

View File

@ -2,8 +2,18 @@ Package: reaction
Version: LAST_TAG Version: LAST_TAG
Architecture: amd64 Architecture: amd64
Maintainer: ppom <> Maintainer: ppom <>
Sections: utils Section: utils
Package-Type: deb Package-Type: deb
Priority: Optional Priority: Optional
Homepage: https://framagit.org/ppom/reaction Homepage: https://framagit.org/ppom/reaction
Description: A daemon that scans program outputs for repeated patterns, and takes action. Description: A daemon that scans program outputs for repeated patterns, and takes action
A common use of reaction is to scan ssh and web server logs,
and ban hosts that cause multiple authentication errors.
reaction doesn't have all the features of the honorable fail2ban,
but it's ~10x faster and easier to configure.
Tag: admin::automation, admin::logging, admin::monitoring,
interface::commandline, interface::daemon,
network::firewall, protocol::ip, role::program,
security::authentication, security::firewall, security::ids,
security::log-analyzer, use::login, use::monitor,
works-with-format::plaintext, works-with::logfile, works-with::text

View File

@ -3,22 +3,27 @@ PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin BINDIR = $(PREFIX)/bin
SYSTEMDDIR ?= /etc/systemd SYSTEMDDIR ?= /etc/systemd
all: reaction ip46tables all: reaction.linux reaction.freebsd ip46tables nft46
clean: clean:
rm -f reaction ip46tables reaction.deb deb reaction.minisig ip46tables.minisig reaction.deb.minisig rm -f reaction.linux reaction.freebsd ip46tables nft46 reaction.deb deb reaction.minisig ip46tables.minisig reaction.deb.minisig nft46.minisig
ip46tables: ip46tables.d/ip46tables.c ip46tables: helpers_c/ip46tables.c
$(CC) -s -static ip46tables.d/ip46tables.c -o ip46tables $(CC) -s -static helpers_c/ip46tables.c -o ip46tables
reaction: app/* reaction.go go.mod go.sum nft46: helpers_c/nft46.c
CGO_ENABLED=0 go build -buildvcs=false -ldflags "-s -X main.version=`git tag --sort=v:refname | tail -n1` -X main.commit=`git rev-parse --short HEAD`" $(CC) -s -static helpers_c/nft46.c -o nft46
reaction.deb: reaction ip46tables reaction.linux: app/* reaction.go go.mod go.sum
chmod +x reaction ip46tables GOOS=linux CGO_ENABLED=0 go build -buildvcs=false -ldflags "-s -X main.version=`git tag --sort=v:refname | tail -n1` -X main.commit=`git rev-parse --short HEAD`" -o reaction.linux
reaction.freebsd: app/* reaction.go go.mod go.sum
GOOS=freebsd CGO_ENABLED=0 go build -buildvcs=false -ldflags "-s -X main.version=`git tag --sort=v:refname | tail -n1` -X main.commit=`git rev-parse --short HEAD`" -o reaction.freebsd
reaction.deb: reaction ip46tables nft46
chmod +x reaction ip46tables nft46
mkdir -p deb/reaction/usr/bin/ deb/reaction/usr/sbin/ deb/reaction/lib/systemd/system/ mkdir -p deb/reaction/usr/bin/ deb/reaction/usr/sbin/ deb/reaction/lib/systemd/system/
cp reaction deb/reaction/usr/bin/ cp reaction ip46tables nft46 deb/reaction/usr/bin/
cp ip46tables deb/reaction/usr/sbin/
cp config/reaction.debian.service deb/reaction/lib/systemd/system/reaction.service cp config/reaction.debian.service deb/reaction/lib/systemd/system/reaction.service
cp -r DEBIAN/ deb/reaction/DEBIAN cp -r DEBIAN/ deb/reaction/DEBIAN
sed -e "s/LAST_TAG/`git tag --sort=v:refname | tail -n1`/" -e "s/Version: v/Version: /" -i deb/reaction/DEBIAN/* sed -e "s/LAST_TAG/`git tag --sort=v:refname | tail -n1`/" -e "s/Version: v/Version: /" -i deb/reaction/DEBIAN/*
@ -26,12 +31,14 @@ reaction.deb: reaction ip46tables
mv deb/reaction.deb reaction.deb mv deb/reaction.deb reaction.deb
rm -rf deb/ rm -rf deb/
signatures: reaction.deb reaction ip46tables signatures: reaction.deb reaction ip46tables nft46
minisign -Sm ip46tables reaction reaction.deb minisign -Sm ip46tables nft46 reaction reaction.deb
install: all install: all
@install -m755 reaction $(DESTDIR)$(BINDIR) install -m755 reaction $(DESTDIR)$(BINDIR)
@install -m755 ip46tables $(DESTDIR)$(BINDIR) install -m755 ip46tables $(DESTDIR)$(BINDIR)
install -m755 nft46 $(DESTDIR)$(BINDIR)
install_systemd: install install_systemd: install
@install -m644 config/reaction.debian.service $(SYSTEMDDIR)/system/reaction.service install -m644 config/reaction.debian.service $(SYSTEMDDIR)/system/reaction.service
sed -i 's#/usr/bin#$(DESTDIR)$(BINDIR)#' $(SYSTEMDDIR)/system/reaction.service

View File

@ -142,7 +142,7 @@ It will execute `iptables` when detecting ipv4, `ip6tables` when detecting ipv6
## Wiki ## Wiki
You'll find more ressources, service configurations, etc. on the [Wiki](https://framagit.org/ppom/reaction-wiki)! You'll find more ressources, service configurations, etc. on the [Wiki](https://reaction.ppom.me)!
## Installation ## Installation
@ -154,6 +154,11 @@ Executables are provided [here](https://framagit.org/ppom/reaction/-/releases/),
A standard place to put such executables is `/usr/local/bin/`. A standard place to put such executables is `/usr/local/bin/`.
> Provided binaries in the previous section are compiled this way:
```shell
$ docker run -it --rm -e HOME=/tmp/ -v $(pwd):/tmp/code -w /tmp/code -u $(id -u) golang:1.20 make clean reaction.deb
$ make signaturese
```
#### Signature verification #### Signature verification
Starting at v1.0.3, all binaries are signed with public key `RWSpLTPfbvllNqRrXUgZzM7mFjLUA7PQioAItz80ag8uU4A2wtoT2DzX`. You can check their authenticity with minisign: Starting at v1.0.3, all binaries are signed with public key `RWSpLTPfbvllNqRrXUgZzM7mFjLUA7PQioAItz80ag8uU4A2wtoT2DzX`. You can check their authenticity with minisign:
@ -179,27 +184,34 @@ ExecStart=
ExecStart=/usr/bin/reaction start -c /etc/reaction.yml ExecStart=/usr/bin/reaction start -c /etc/reaction.yml
``` ```
#### NixOS
- [ package ](https://framagit.org/ppom/nixos/-/blob/main/pkgs/reaction/default.nix)
- [ module ](https://framagit.org/ppom/nixos/-/blob/main/modules/common/reaction.nix)
### Compilation ### Compilation
You'll need the go (>= 1.20) toolchain for reaction and a c compiler for ip46tables. You'll need the go (>= 1.20) toolchain for reaction and a c compiler for ip46tables.
```shell ```shell
$ make $ make
``` ```
Don't hesitate to take a look at the `Makefile` to understand what's happening!
Alternatively, ### Installation
To install the binaries
```shell ```shell
# creates ./reaction make install
$ go build .
# creates ./ip46tables
$ gcc ip46tables.d/ip46tables.c -o ip46tables
``` ```
Provided binaries in the previous section are compiled this way: To install the systemd file as well
```shell ```shell
$ docker run -it --rm -e HOME=/tmp/ -v $(pwd):/tmp/code -w /tmp/code -u $(id -u) golang:1.20 make clean reaction.deb make install_systemd
``` ```
### NixOS ## Development
- [ package ](https://framagit.org/ppom/nixos/-/blob/main/pkgs/reaction/default.nix) Contributions are welcome. For any substantial feature, please file an issue first, to be assured that we agree on the feature, and to avoid unnecessary work.
- [ module ](https://framagit.org/ppom/nixos/-/blob/main/modules/common/reaction.nix)
This is a free time project, so I'm not working on schedule.
However, if you're willing to fund the project, I can priorise and plan paid work. This includes features, documentation and specific JSONnet configurations.

View File

@ -62,6 +62,12 @@ func runCommands(commands [][]string, moment string) bool {
} }
func (p *Pattern) notAnIgnore(match *string) bool { func (p *Pattern) notAnIgnore(match *string) bool {
for _, regex := range p.compiledIgnoreRegex {
if regex.MatchString(*match) {
return false
}
}
for _, ignore := range p.Ignore { for _, ignore := range p.Ignore {
if ignore == *match { if ignore == *match {
return false return false
@ -105,7 +111,7 @@ func (a *Action) exec(match string) {
var computedCommand []string var computedCommand []string
if a.filter.pattern != nil { if a.filter.pattern != nil {
computedCommand := make([]string, 0, len(a.Cmd)) computedCommand = make([]string, 0, len(a.Cmd))
for _, item := range a.Cmd { for _, item := range a.Cmd {
computedCommand = append(computedCommand, strings.ReplaceAll(item, a.filter.pattern.nameWithBraces, match)) computedCommand = append(computedCommand, strings.ReplaceAll(item, a.filter.pattern.nameWithBraces, match))

View File

@ -1,4 +1,11 @@
--- ---
# This example configuration file is a good starting point, but you're
# strongly encouraged to take a look at the full documentation: https://reaction.ppom.me
#
# This file is using the well-established YAML configuration language.
# Note that the more powerful JSONnet configuration language is also supported
# and that the documentation uses JSONnet
# definitions are just a place to put chunks of conf you want to reuse in another place # definitions are just a place to put chunks of conf you want to reuse in another place
# using YAML anchors `&name` and pointers `*name` # using YAML anchors `&name` and pointers `*name`
# definitions are not readed by reaction # definitions are not readed by reaction
@ -23,15 +30,20 @@ patterns:
ignore: ignore:
- 127.0.0.1 - 127.0.0.1
- ::1 - ::1
# Patterns can be ignored based on regexes, it will try to match the whole string detected by the pattern
# ignoreregex:
# - '10\.0\.[0-9]{1,3}\.[0-9]{1,3}'
# Those commands will be executed in order at start, before everything else # Those commands will be executed in order at start, before everything else
start: start:
- [ 'ip46tables', '-w', '-N', 'reaction' ] - [ 'ip46tables', '-w', '-N', 'reaction' ]
- [ 'ip46tables', '-w', '-I', 'INPUT', '-p', 'all', '-j', 'reaction' ] - [ 'ip46tables', '-w', '-I', 'INPUT', '-p', 'all', '-j', 'reaction' ]
- [ 'ip46tables', '-w', '-I', 'FORWARD', '-p', 'all', '-j', 'reaction' ]
# Those commands will be executed in order at stop, after everything else # Those commands will be executed in order at stop, after everything else
stop: stop:
- [ 'ip46tables', '-w,', '-D', 'INPUT', '-p', 'all', '-j', 'reaction' ] - [ 'ip46tables', '-w,', '-D', 'INPUT', '-p', 'all', '-j', 'reaction' ]
- [ 'ip46tables', '-w,', '-D', 'FORWARD', '-p', 'all', '-j', 'reaction' ]
- [ 'ip46tables', '-w', '-F', 'reaction' ] - [ 'ip46tables', '-w', '-F', 'reaction' ]
- [ 'ip46tables', '-w', '-X', 'reaction' ] - [ 'ip46tables', '-w', '-X', 'reaction' ]

View File

@ -115,7 +115,7 @@ cat FILE | ` + bold + `reaction test-regex` + reset + ` REGEX # test REGEX again
# print version information # print version information
see usage examples, service configurations and good practices see usage examples, service configurations and good practices
on the ` + bold + `wiki` + reset + `: https://framagit.org/ppom/reaction-wiki on the ` + bold + `wiki` + reset + `: https://reaction.ppom.me
`) `)
} }

View File

@ -39,6 +39,17 @@ func (c *Conf) setup() {
logger.Fatalf("Bad configuration: pattern ignore '%v' doesn't match pattern %v! It should be fixed or removed.", ignore, pattern.nameWithBraces) logger.Fatalf("Bad configuration: pattern ignore '%v' doesn't match pattern %v! It should be fixed or removed.", ignore, pattern.nameWithBraces)
} }
} }
// Compile ignore regexes
for _, regex := range pattern.IgnoreRegex {
// Enclose the regex to make sure that it matches the whole detected string
compiledRegex, err := regexp.Compile("^" + regex + "$")
if err != nil {
log.Fatalf("%vBad configuration: in ignoreregex of pattern %s: %v", logger.FATAL, pattern.name, err)
}
pattern.compiledIgnoreRegex = append(pattern.compiledIgnoreRegex, *compiledRegex)
}
} }
if len(c.Streams) == 0 { if len(c.Streams) == 0 {

View File

@ -19,6 +19,9 @@ type Pattern struct {
Regex string `json:"regex"` Regex string `json:"regex"`
Ignore []string `json:"ignore"` Ignore []string `json:"ignore"`
IgnoreRegex []string `json:"ignoreregex"`
compiledIgnoreRegex []regexp.Regexp `json:"-"`
name string `json:"-"` name string `json:"-"`
nameWithBraces string `json:"-"` nameWithBraces string `json:"-"`
} }

View File

@ -1,11 +1,15 @@
// This file is using JSONNET, a complete configuration language based on JSON // This file is using JSONnet, a complete configuration language based on JSON
// See https://jsonnet.org // See https://jsonnet.org
// JSONNET is a superset of JSON, so one can write plain JSON files if wanted. // JSONnet is a superset of JSON, so one can write plain JSON files if wanted.
// Note that YAML is also supported, see ./example.yml // Note that YAML is also supported, see ./example.yml
// JSONNET functions // This example configuration file is a good starting point, but you're
// strongly encouraged to take a look at the full documentation: https://reaction.ppom.me
// JSONnet functions
local iptables(args) = ['ip46tables', '-w'] + args; local iptables(args) = ['ip46tables', '-w'] + args;
// ip46tables is a minimal C program (only POSIX dependencies) present in a subdirectory of this repo. // ip46tables is a minimal C program (only POSIX dependencies) present in a
// subdirectory of this repo.
// it permits to handle both ipv4/iptables and ipv6/ip6tables commands // it permits to handle both ipv4/iptables and ipv6/ip6tables commands
// See meaning and usage of this function around L106 // See meaning and usage of this function around L106
@ -29,6 +33,8 @@ local banFor(time) = {
// simple version: regex: @'(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})', // simple version: regex: @'(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})',
regex: @'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))', regex: @'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))',
ignore: ['127.0.0.1', '::1'], ignore: ['127.0.0.1', '::1'],
// Patterns can be ignored based on regexes, it will try to match the whole string detected by the pattern
// ignoreregex: [@'10\.0\.[0-9]{1,3}\.[0-9]{1,3}'],
}, },
}, },
@ -41,14 +47,16 @@ local banFor(time) = {
start: [ start: [
// Create an iptables chain for reaction // Create an iptables chain for reaction
iptables(['-N', 'reaction']), iptables(['-N', 'reaction']),
// Insert this chain as the first item of the INPUT chain (for incoming connections) // Insert this chain as the first item of the INPUT & FORWARD chains (for incoming connections)
iptables(['-I', 'INPUT', '-p', 'all', '-j', 'reaction']), iptables(['-I', 'INPUT', '-p', 'all', '-j', 'reaction']),
iptables(['-I', 'FORWARD', '-p', 'all', '-j', 'reaction']),
], ],
// Those commands will be executed in order at stop, after everything else // Those commands will be executed in order at stop, after everything else
stop: [ stop: [
// Remove the chain from the INPUT chain // Remove the chain from the INPUT & FORWARD chains
iptables(['-D', 'INPUT', '-p', 'all', '-j', 'reaction']), iptables(['-D', 'INPUT', '-p', 'all', '-j', 'reaction']),
iptables(['-D', 'FORWARD', '-p', 'all', '-j', 'reaction']),
// Empty the chain // Empty the chain
iptables(['-F', 'reaction']), iptables(['-F', 'reaction']),
// Delete the chain // Delete the chain

View File

@ -1,6 +1,8 @@
[Unit] [Unit]
Description=A daemon that scans program outputs for repeated patterns, and takes action. Description=A daemon that scans program outputs for repeated patterns, and takes action.
Documentation=https://framagit.org/ppom/reaction-wiki Documentation=https://framagit.org/ppom/reaction-wiki
# Ensure reaction will insert its chain after docker has inserted theirs. Only useful when iptables & docker are used
# After=docker.service
[Service] [Service]
ExecStart=/usr/bin/reaction start -c /etc/reaction.jsonnet ExecStart=/usr/bin/reaction start -c /etc/reaction.jsonnet

View File

@ -1,6 +1,8 @@
# vim: ft=systemd # vim: ft=systemd
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
# Ensure reaction will insert its chain after docker has inserted theirs. Only useful when iptables & docker are used
# After=docker.service
# See `man systemd.exec` and `man systemd.service` for most options below # See `man systemd.exec` and `man systemd.service` for most options below
[Service] [Service]

View File

@ -2,24 +2,14 @@
patterns: { patterns: {
num: { num: {
regex: '[0-9]+', regex: '[0-9]+',
ignore: ['1'],
ignoreregex: ['2.?'],
}, },
}, },
start: [
['err'],
['sleep', '1'],
],
stop: [
['sleep', '1'],
// ['false'],
['true'],
],
streams: { streams: {
tailDown1: { tailDown1: {
cmd: ['sh', '-c', "echo 1 2 3 4 5 5 | tr ' ' '\n' | while read i; do sleep 1; echo found $(($i % 10)); done"], cmd: ['sh', '-c', "echo 1 2 3 4 5 11 12 21 22 33 | tr ' ' '\n' | while read i; do sleep 1; echo found $i; done"],
// cmd: ['sh', '-c', "echo 1 2 3 4 5 1 2 3 4 5 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 | tr ' ' '\n' | while read i; do sleep 2; echo found $(($i % 10)); done"],
filters: { filters: {
findIP: { findIP: {
regex: ['^found <num>$'], regex: ['^found <num>$'],
@ -38,25 +28,5 @@
}, },
}, },
}, },
// tailDown2: {
// cmd: ['sh', '-c', 'echo coucou; sleep 2m'],
// filters: {
// findIP: {
// regex: ['^found <num>$'],
// retry: 3,
// retryperiod: '30s',
// actions: {
// damn: {
// cmd: ['echo', '<num>'],
// },
// undamn: {
// cmd: ['echo', 'undamn', '<num>'],
// after: '30s',
// onexit: true,
// },
// },
// },
// },
// },
}, },
} }

97
helpers_c/nft46.c Normal file
View File

@ -0,0 +1,97 @@
#include<ctype.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
// nft46 'add element inet reaction ipvXbans { 1.2.3.4 }' → nft 'add element inet reaction ipv4bans { 1.2.3.4 }'
// nft46 'add element inet reaction ipvXbans { a:b::c:d }' → nft 'add element inet reaction ipv6bans { a:b::c:d }'
//
// the character X is replaced by 4 or 6 depending on the address family of the specified IP
//
// Limitations:
// - nft46 must receive exactly one argument
// - only one IP must be given per command
// - the IP must be between { braces }
int isIPv4(char *tab, int len) {
int i;
// IPv4 addresses are at least 7 chars long
if (len < 7 || !isdigit(tab[0]) || !isdigit(tab[len-1])) {
return 0;
}
// Each char must be a digit or a dot between 2 digits
for (i=1; i<len-1; i++) {
if (!isdigit(tab[i]) && !(tab[i] == '.' && isdigit(tab[i-1]) && isdigit(tab[i+1]))) {
return 0;
}
}
return 1;
}
int isIPv6(char *tab, int len) {
int i;
// IPv6 addresses are at least 3 chars long
if (len < 3) {
return 0;
}
// Each char must be a digit, :, a-f, or A-F
for (i=0; i<len; i++) {
if (!isdigit(tab[i]) && tab[i] != ':' && tab[i] != '.' && !(tab[i] >= 'a' && tab[i] <= 'f') && !(tab[i] >= 'A' && tab[i] <= 'F')) {
return 0;
}
}
return 1;
}
int findchar(char *tab, char c, int i, int len) {
while (i < len && tab[i] != c) i++;
if (i == len) {
printf("nft46: one %c must be present", c);
exit(1);
}
return i;
}
void adapt_args(char *tab) {
int i, len, X, startIP, endIP, startedIP;
X = startIP = endIP = -1;
startedIP = 0;
len = strlen(tab);
i = 0;
X = i = findchar(tab, 'X', i, len);
startIP = i = findchar(tab, '{', i, len);
while (startIP + 1 <= (i = findchar(tab, ' ', i, len))) startIP = i + 1;
i = startIP;
endIP = i = findchar(tab, ' ', i, len) - 1;
if (isIPv4(tab+startIP, endIP-startIP+1)) {
tab[X] = '4';
return;
}
if (isIPv6(tab+startIP, endIP-startIP+1)) {
tab[X] = '6';
return;
}
printf("nft46: no IP address found\n");
exit(1);
}
int exec(char *str, char **argv) {
argv[0] = str;
execvp(str, argv);
// returns only if fails
printf("nft46: exec failed %d\n", errno);
}
int main(int argc, char **argv) {
if (argc != 2) {
printf("nft46: Exactly one argument must be given\n");
exit(1);
}
adapt_args(argv[1]);
exec("nft", argv);
}

View File

@ -2,29 +2,31 @@
set -exu set -exu
git push --tags
docker run -it --rm -e HOME=/tmp/ -v "$(pwd)":/tmp/code -w /tmp/code -u "$(id -u)" golang:1.20 make reaction.deb docker run -it --rm -e HOME=/tmp/ -v "$(pwd)":/tmp/code -w /tmp/code -u "$(id -u)" golang:1.20 make reaction.deb
make signatures make signatures
TAG="$(git tag --sort=v:refname | tail -n1)" TAG="$(git tag --sort=v:refname | tail -n1)"
rsync -avz -e 'ssh -J pica01' ./ip46tables ./reaction ./reaction.deb ./ip46tables.minisig ./reaction.minisig ./reaction.deb.minisig akesi:/var/www/static/reaction/releases/"$TAG" rsync -avz -e 'ssh -J pica01' ./ip46tables ./nft46 ./reaction ./reaction.deb ./nft46.minisig ./ip46tables.minisig ./reaction.minisig ./reaction.deb.minisig akesi:/var/www/static/reaction/releases/"$TAG"
TOKEN="$(rbw get framagit.org token)" TOKEN="$(rbw get framagit.org token)"
DATA='{ DATA='{
"tag_name":"'"$TAG"'", "tag_name":"'"$TAG"'",
"assets":{"links":[ "assets":{"links":[
{"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/nft46", "name": "nft46 (x86-64)", "link_type": "package"},
{"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/reaction", "name": "reaction (x86-64)", "link_type": "package"}, {"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/reaction", "name": "reaction (x86-64)", "link_type": "package"},
{"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/ip46tables", "name": "ip46tables (x86-64)", "link_type": "package"}, {"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/ip46tables", "name": "ip46tables (x86-64)", "link_type": "package"},
{"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/reaction.deb", "name": "reaction.deb (x86-64)", "link_type": "package"}, {"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/reaction.deb", "name": "reaction.deb (x86-64)", "link_type": "package"},
{"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/nft46.minisig", "name": "nft46.minisig", "link_type": "other"},
{"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/reaction.minisig", "name": "reaction.minisig", "link_type": "other"}, {"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/reaction.minisig", "name": "reaction.minisig", "link_type": "other"},
{"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/ip46tables.minisig", "name": "ip46tables.minisig", "link_type": "other"}, {"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/ip46tables.minisig", "name": "ip46tables.minisig", "link_type": "other"},
{"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/reaction.deb.minisig", "name": "reaction.deb.minisig", "link_type": "other"} {"url": "https://static.ppom.me/reaction/releases/'"$TAG"'/reaction.deb.minisig", "name": "reaction.deb.minisig", "link_type": "other"}
]}}' ]}}'
DATA="$(echo "$DATA" | tr '\n' ' ')"
curl \ curl \
--fail-with-body \ --fail-with-body \
--location \ --location \