-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b96dbe7
Showing
29 changed files
with
896 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"editor.formatOnSave": true, | ||
"editor.codeActionsOnSave": { | ||
"source.fixAll": true | ||
}, | ||
"editor.defaultFormatter": "esbenp.prettier-vscode" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright 2021 NiceLabs | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Local hook driven server | ||
|
||
## Worker Service | ||
|
||
1. [FTP worker](./cmd/ftp-worker) | ||
1. [TFTP worker](./cmd/tftp-worker) | ||
1. [SMTP worker](./cmd/smtp-worker) | ||
|
||
## LICENSE | ||
|
||
[LICENSE](LICENSE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# JunOS Configuration Archival | ||
|
||
Based [Backup Configurations to an Archive Site][backup-configuration] | ||
|
||
[backup-configuration]: https://www.juniper.net/documentation/en_US/junos/topics/task/configuration/junos-software-system-management-router-configuration-archiving.html | ||
|
||
## Local side | ||
|
||
```bash | ||
ftp-worker \ | ||
-on-write ./on-write-junos-archive.py \ | ||
-workdir path/to/git-repo | ||
``` | ||
|
||
## JunOS side | ||
|
||
Based `transfer-on-commit` command automate backup JunOS configuration to remote-side | ||
|
||
```plain | ||
set system archival configuration transfer-on-commit | ||
set system archival configuration archive-sites ftp://<username>@<host>:<port>/<path> password <password> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# FTP Worker | ||
|
||
The [FTP](https://www.rfc-editor.org/rfc/rfc959) worker | ||
|
||
Redirect upload (write operation) and download (read operation) as local program calling | ||
|
||
## Usage | ||
|
||
### Read hook | ||
|
||
Equivalent to: | ||
|
||
```plain | ||
read-hook-program <filename> | <file-content> | ||
``` | ||
|
||
Environions: | ||
|
||
| Field Name | Type | | ||
| ----------------- | --------- | | ||
| `FTP_ACTION` | `READ` | | ||
| `FTP_PATH` | file path | | ||
| `FTP_READ_OFFSET` | integer | | ||
|
||
### Write hook | ||
|
||
Equivalent to: | ||
|
||
```plain | ||
<file-content> | write-hook-program <filename> | ||
``` | ||
|
||
Environments: | ||
|
||
| Field Name | Type | | ||
| ------------ | --------- | | ||
| `FTP_ACTION` | `WRITE` | | ||
| `FTP_PATH` | file path | | ||
|
||
## Example | ||
|
||
- [JunOS Configuration Archival](EXAMPLE-JUNOS.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"io" | ||
"log" | ||
"os" | ||
"os/exec" | ||
"strconv" | ||
|
||
"github.com/NiceLabs/hook-driven-server/utils" | ||
"goftp.io/server/core" | ||
) | ||
|
||
type HookDriver struct { | ||
Workdir string | ||
ReadHook string | ||
WriteHook string | ||
writeMaps map[string]io.ReaderFrom | ||
} | ||
|
||
func (h *HookDriver) Stat(path string) (core.FileInfo, error) { | ||
return DummyFileInfo(path), nil | ||
} | ||
|
||
func (h *HookDriver) ListDir(string, func(core.FileInfo) error) error { | ||
return errors.New("ftp-worker: list directory operation not permitted") | ||
} | ||
|
||
func (h *HookDriver) DeleteDir(string) error { | ||
return errors.New("ftp-worker: delete directory operation not permitted") | ||
} | ||
|
||
func (h *HookDriver) DeleteFile(string) error { | ||
return errors.New("ftp-worker: delete file operation not permitted") | ||
} | ||
|
||
func (h *HookDriver) Rename(string, string) error { | ||
return errors.New("ftp-worker: rename operation not permitted") | ||
} | ||
|
||
func (h *HookDriver) MakeDir(string) error { | ||
return errors.New("ftp-worker: make directory operation not permitted") | ||
} | ||
|
||
func (h *HookDriver) GetFile(path string, offset int64) (n int64, stdout io.ReadCloser, err error) { | ||
log.Printf("Read %q with %d offset\n", path, offset) | ||
if h.ReadHook == "" { | ||
err = errors.New("ftp-worker: read operation not permitted") | ||
return | ||
} | ||
cmd := exec.Command(h.ReadHook, path, strconv.FormatInt(offset, 64)) | ||
utils.AddEnv(cmd, map[string]string{ | ||
"FTP_ACTION": "READ", | ||
"FTP_PATH": path, | ||
"FTP_READ_OFFSET": strconv.FormatInt(offset, 64), | ||
}) | ||
cmd.Dir = h.Workdir | ||
cmd.Stderr = os.Stderr | ||
if err = cmd.Start(); err != nil { | ||
return | ||
} | ||
stdout, err = cmd.StdoutPipe() | ||
return | ||
} | ||
|
||
func (h *HookDriver) PutFile(destPath string, data io.Reader, appendData bool) (n int64, err error) { | ||
if appendData { | ||
log.Printf("Write %q\n with append data", destPath) | ||
} else { | ||
log.Printf("Write %q\n", destPath) | ||
} | ||
if h.WriteHook == "" { | ||
err = errors.New("ftp-worker: write operation not permitted") | ||
return | ||
} | ||
if appendData && h.writeMaps[destPath] != nil { | ||
return h.writeMaps[destPath].ReadFrom(data) | ||
} | ||
cmd := exec.Command(h.WriteHook, destPath) | ||
utils.AddEnv(cmd, map[string]string{ | ||
"FTP_ACTION": "WRITE", | ||
"FTP_PATH": destPath, | ||
}) | ||
cmd.Dir = h.Workdir | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
var stdin bytes.Buffer | ||
n, err = stdin.ReadFrom(data) | ||
cmd.Stdin = &stdin | ||
h.writeMaps[destPath] = &stdin | ||
go func() { | ||
_ = cmd.Run() | ||
delete(h.writeMaps, destPath) | ||
}() | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package main | ||
|
||
import ( | ||
"io/fs" | ||
"os" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type DummyFileInfo string | ||
|
||
func (d DummyFileInfo) Name() string { return string(d) } | ||
func (d DummyFileInfo) Size() int64 { return 0 } | ||
func (d DummyFileInfo) Mode() fs.FileMode { return os.ModeTemporary } | ||
func (d DummyFileInfo) ModTime() time.Time { return time.UnixMilli(0) } | ||
func (d DummyFileInfo) IsDir() bool { return strings.HasSuffix(string(d), "/") } | ||
func (d DummyFileInfo) Sys() any { return nil } | ||
func (d DummyFileInfo) Owner() string { return "nobody" } | ||
func (d DummyFileInfo) Group() string { return "nobody" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
|
||
"goftp.io/server/core" | ||
) | ||
|
||
type HookDriverFactory struct { | ||
Workdir string | ||
ReadHook string | ||
WriteHook string | ||
} | ||
|
||
func (h *HookDriverFactory) NewDriver() (driver core.Driver, err error) { | ||
driver = &HookDriver{ | ||
Workdir: h.Workdir, | ||
ReadHook: h.ReadHook, | ||
WriteHook: h.WriteHook, | ||
writeMaps: make(map[string]io.ReaderFrom), | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"log" | ||
|
||
. "goftp.io/server" | ||
) | ||
|
||
var ( | ||
hostname string | ||
port int | ||
factory = new(HookDriverFactory) | ||
auth = new(SimpleAuth) | ||
certFile string | ||
keyFile string | ||
explicitFTPS bool | ||
) | ||
|
||
func init() { | ||
flag.StringVar(&hostname, "hostname", "", "FTP listen hostname") | ||
flag.IntVar(&port, "port", 21, "FTP listen port") | ||
flag.StringVar(&certFile, "tls-cert-file", "", "TLS Certificate file") | ||
flag.StringVar(&keyFile, "tls-key-file", "", "TLS Key file") | ||
flag.BoolVar(&explicitFTPS, "tls-explicit", false, "Explicit FTPS") | ||
flag.StringVar(&auth.Name, "username", "user", "Username") | ||
flag.StringVar(&auth.Password, "password", "pass", "Password") | ||
flag.StringVar(&factory.Workdir, "workdir", "", "Work directory") | ||
flag.StringVar(&factory.ReadHook, "on-read", "", "Read operation local hook program") | ||
flag.StringVar(&factory.WriteHook, "on-write", "", "Write operation local hook program") | ||
flag.Parse() | ||
} | ||
|
||
func main() { | ||
serve := NewServer(&ServerOpts{ | ||
Name: "FTP Hook Service", | ||
Auth: auth, | ||
Hostname: hostname, | ||
Port: port, | ||
TLS: certFile != "" && keyFile != "", | ||
CertFile: certFile, | ||
KeyFile: keyFile, | ||
ExplicitFTPS: explicitFTPS, | ||
Factory: factory, | ||
}) | ||
if err := serve.ListenAndServe(); err != nil { | ||
log.Fatal("Error starting server:", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#!/usr/bin/env python3 | ||
import gzip | ||
import os | ||
import re | ||
import sys | ||
from subprocess import call | ||
|
||
# pattern: <router-name>_YYYYMMDD_HHMMSS_juniper.conf<.n>.gz | ||
re_filename = re.compile(r'^(\S+)?_\d{8}_\d{6}_juniper\.conf(?:\.\d+)?\.gz$') | ||
|
||
|
||
def main(filename: str): | ||
matched = re_filename.match(os.path.basename(filename)) | ||
if not matched: | ||
sys.exit(1) | ||
saved_filename = '%s.conf' % (matched.group(1) or 'unnamed') | ||
decompressed = gzip.decompress(sys.stdin.buffer.read()).decode() | ||
if not has_changed(saved_filename, decompressed): | ||
return | ||
with open(os.path.join(saved_filename), 'w') as fp: | ||
fp.write(decompressed) | ||
call(['git', 'add', saved_filename]) | ||
call(['git', 'commit', '-m', filename]) | ||
call(['git', 'push']) | ||
|
||
|
||
def has_changed(filename: str, decompressed: str): | ||
if not os.path.exists(filename): | ||
return True | ||
with open(filename, 'r') as fp: | ||
original = fp.read() | ||
return original.splitlines()[1:] != decompressed.splitlines()[1:] | ||
|
||
|
||
if __name__ == '__main__': | ||
main(sys.argv[1]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# SMTP worker | ||
|
||
The SMTP worker | ||
|
||
Redirect SMTP request to local hook program. | ||
|
||
## Usage | ||
|
||
<!-- markdownlint-disable --> | ||
|
||
```plain | ||
Usage of smtp-worker: | ||
-addr string | ||
SMTP listen address | ||
-on-request string | ||
SMTP request handler | ||
-tls-cert-file string | ||
TLS Certificate file | ||
-tls-key-file string | ||
TLS Key file | ||
-username string | ||
Username (default "user") | ||
-password string | ||
Password (default "pass") | ||
``` | ||
|
||
<!-- markdownlint-restore --> | ||
|
||
### On Request | ||
|
||
Equivalent to: | ||
|
||
```plain | ||
<body> | hook-program <to-address> <from-address> | ||
``` | ||
|
||
Environments: | ||
|
||
| Field Name | Type | | ||
| --------------------- | ------------------- | | ||
| `SMTP_FROM` | string | | ||
| `SMTP_FROM_USERNAME` | string | | ||
| `SMTP_FROM_DOMAIN` | string (fqdn) | | ||
| `SMTP_TO` | string | | ||
| `SMTP_TO_USERNAME` | string | | ||
| `SMTP_TO_DOMAIN` | string (fqdn) | | ||
| `SMTP_TO_DOMAIN_TYPE` | string | | ||
| `SMTP_HOSTNAME` | string | | ||
| `SMTP_LOCAL_ADDR` | string (ip address) | | ||
| `SMTP_REMOTE_ADDR` | string (ip address) | | ||
| `SMTP_UTF8` | boolean | | ||
| `SMTP_REQUIRE_TLS` | boolean | | ||
| `SMTP_BODY_TYPE` | string | | ||
| `SMTP_BODY_SIZE` | interge | | ||
|
||
`SMTP_TO_DOMAIN_TYPE`: | ||
|
||
- `DISPOSABLE_MAIL`, disposable email provides | ||
- `FREE_MAIL`, free email provides | ||
- `SWOT_MAIL`, education email domains | ||
- `DDNS`, dynamic dns provides | ||
|
||
`SMTP_BODY_TYPE`: | ||
|
||
- `7BIT`, 7bit, e.g: UTF-7, Base64 or Quoted-Printable Encoding | ||
- `8BITMIME`, 8bit MIME | ||
- `BINARYMIME`, Binary MIME |
Oops, something went wrong.