Compare commits
270 Commits
1528657e9c
...
1e617bb8be
Author | SHA1 | Date | |
---|---|---|---|
1e617bb8be | |||
b43784574c | |||
364815e8af | |||
3d43a256ba | |||
b460cd0196 | |||
643f1197d6 | |||
7ef9f2dbd0 | |||
b102fc4d2a | |||
e15331ec35 | |||
3a594acc03 | |||
800593d034 | |||
65b2a2d519 | |||
bf21671a1f | |||
fe18968c57 | |||
b6639c7bfc | |||
7e0178d183 | |||
03bfc9dbfc | |||
77b9bb30c4 | |||
f8cb6ccc37 | |||
0e1d5b3239 | |||
9f3b9f692a | |||
391e9e1e39 | |||
3555e5a91c | |||
2f24e13940 | |||
87f495b326 | |||
1615b413a7 | |||
86c27a3f17 | |||
a687b5fd1c | |||
27956146e3 | |||
a547e29e56 | |||
de34052c3b | |||
cb63ec50d2 | |||
8b00d29db3 | |||
f082bb0ebd | |||
4f8448563d | |||
710adad634 | |||
5fc69067fb | |||
dc212d35fb | |||
3ce6a8ed61 | |||
b398163cfd | |||
a5e9a28673 | |||
49e40f4fb8 | |||
924d760c79 | |||
58dc090c83 | |||
b20878e378 | |||
b443762739 | |||
18f5354d0c | |||
17e3c69f07 | |||
51784df6a8 | |||
8df6384736 | |||
752462d20f | |||
72d68fa1ab | |||
aaec5f8f4a | |||
ab2e288f06 | |||
b30ab2f999 | |||
b4a42f6780 | |||
f279d85b08 | |||
46f52dd56d | |||
59ec5f5a0c | |||
29710c37c2 | |||
1650d07d5c | |||
0457b3df25 | |||
731fdb0a44 | |||
6216ada5e5 | |||
b9b5cf4214 | |||
f912e81ee6 | |||
4fe5dc6ad0 | |||
556b95c7c1 | |||
14c505c15b | |||
dd8558487c | |||
3ad20f969b | |||
4d13199848 | |||
cd2efbd1d4 | |||
edee0643ec | |||
23159d19d5 | |||
2765a27db8 | |||
c8ef7d065b | |||
7540ddf8f4 | |||
b17fe1d2ee | |||
5389739920 | |||
92f964f572 | |||
9abfd88e3d | |||
e9895aee45 | |||
79b180f453 | |||
6e376100a5 | |||
1a5c1eff7b | |||
f42fe97902 | |||
ac02f37c67 | |||
6a41536d57 | |||
527cc1d4ab | |||
d90f431925 | |||
ceffc7ff14 | |||
001618d719 | |||
bba6f93fbc | |||
1c270025cf | |||
8bf58f3daa | |||
3f5668292f | |||
20d3776490 | |||
7b225d8fc0 | |||
ff7ab68a54 | |||
a2ff2df9f3 | |||
9c9fd84e0a | |||
5cb70da458 | |||
3e561ab068 | |||
42cc50512f | |||
bb42f418e0 | |||
ae0bd09a47 | |||
9299697ec1 | |||
bac41969a5 | |||
d6e23b9a90 | |||
2edc41b11e | |||
03ca4c10b1 | |||
1923eb429f | |||
1e08c2f6f7 | |||
77df5746be | |||
60964e27a7 | |||
c496d131cf | |||
2ba656b1d9 | |||
cb09016539 | |||
0c17391dec | |||
c1a1797778 | |||
9376c6de11 | |||
713615d8d5 | |||
01107cd3dc | |||
0e5a04596a | |||
c9d04b64ac | |||
90206f2bb5 | |||
ac2ee7df0c | |||
24f6af3d3b | |||
9c8e546765 | |||
13e51724c0 | |||
1ded66990c | |||
6fb7d165ae | |||
fd07fb2be7 | |||
e1c9fa12aa | |||
7b9ab388d8 | |||
7af67de2a8 | |||
6d9179ed37 | |||
27c050dc38 | |||
301fc33d2f | |||
5d88a5e10e | |||
aae551aacf | |||
26c80dc1c5 | |||
ba0d612889 | |||
f6a6c13ca2 | |||
b9bc413b05 | |||
caa6e629f4 | |||
541f612446 | |||
247cf614f3 | |||
ab5476d373 | |||
0526500ff0 | |||
8fc0336314 | |||
5a74386f5a | |||
e56ecaa999 | |||
ddccc44261 | |||
fa2e74eaca | |||
ee15274478 | |||
1890b66dc7 | |||
8f3d77b04d | |||
049b2c7204 | |||
5b7ec1a629 | |||
535a082edd | |||
9cf0f1e2f4 | |||
27a3e3e24e | |||
8590bb8acc | |||
a19eaa3291 | |||
4a0e6e67fc | |||
1ac3583479 | |||
6e7e09ab50 | |||
2b2482ba71 | |||
b5e5decfdf | |||
6abf10ab0b | |||
4070a060c3 | |||
a13497df5f | |||
82cee02fa8 | |||
e337a29003 | |||
eab35d4c18 | |||
4c91ed54c0 | |||
bed5bf8acc | |||
9e870858da | |||
0800c48928 | |||
455fed52ee | |||
19338edcb6 | |||
b3dfca5b89 | |||
76dc90ceb3 | |||
a13028808a | |||
e5de4970d1 | |||
7847728e52 | |||
b8670a5593 | |||
fbcbe2c5a8 | |||
5f11f81be3 | |||
fcc2c23894 | |||
9fd8264c3f | |||
5b24ff944f | |||
3221d7f679 | |||
f639e3ffab | |||
e8f4bf93bd | |||
116f726885 | |||
a74c5d5f5c | |||
3a0847f13a | |||
509cdd7ea6 | |||
45d78233b2 | |||
4a870b6587 | |||
67747c062a | |||
250e584c03 | |||
7087fe9bbb | |||
3b99c409e0 | |||
ea42a347eb | |||
85243d128e | |||
7d82cbcf12 | |||
a886a389a8 | |||
f9d24db9f8 | |||
8ce3ce8164 | |||
a7e8cc7568 | |||
c8ab8d3db3 | |||
953d3725b2 | |||
8332f485d1 | |||
b32020e60f | |||
2c504c3d66 | |||
df5cc8c2d4 | |||
4616feacda | |||
7a9b6d5015 | |||
654e7bd2aa | |||
b25e6f432c | |||
27985dbf0b | |||
fda68bba04 | |||
c86d8d8952 | |||
614d332fae | |||
0cabdcd3c1 | |||
495a27cfed | |||
32c0088339 | |||
b854cf9fe0 | |||
ce7a96ca2a | |||
28e30a05e6 | |||
e75be34afd | |||
d82f8ac8b3 | |||
0fcb628c11 | |||
c7e707c143 | |||
62f0c1a909 | |||
073d95f605 | |||
2a50a1d795 | |||
782acad560 | |||
c0c8861c08 | |||
d11a2a5b8d | |||
ff04de52b5 | |||
56544b89e1 | |||
2ae24b9955 | |||
22c8b3df74 | |||
2e7aecff57 | |||
b7c1c97cf7 | |||
612efda945 | |||
7fedf88d8c | |||
11f9adf11a | |||
eebe8a159d | |||
3a35bce9e7 | |||
5e67be5ba1 | |||
f315c71ca9 | |||
4343774079 | |||
eb40f94e37 | |||
cc04ddd7b3 | |||
e611dcbe11 | |||
073fa466d6 | |||
708d9a9f67 | |||
c1d6d48a3c | |||
6e133770fc | |||
a991db788e | |||
096e37ef35 | |||
2f878b6e64 | |||
ec5c28a03e | |||
ff8c370d86 |
@ -2,7 +2,8 @@ APP_NAME="Clearing Houz"
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
APP_URL=http://clrghouz
|
||||
APP_TIMEZONE=
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=info
|
||||
@ -10,8 +11,8 @@ LOG_LEVEL=info
|
||||
DB_CONNECTION=pgsql
|
||||
DB_HOST=postgres
|
||||
DB_PORT=5432
|
||||
DB_DATABASE=laravel
|
||||
DB_USERNAME=laravel
|
||||
DB_DATABASE=clrghouz
|
||||
DB_USERNAME=clrghouz
|
||||
DB_PASSWORD=
|
||||
#DB_SSLMODE=prefer
|
||||
#DB_SSLROOTCERT=/var/www/html/config/ssl/ca.crt
|
||||
@ -19,6 +20,7 @@ DB_PASSWORD=
|
||||
#DB_SSLKEY=/var/www/html/config/ssl/client.key
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
MEMCACHED_HOST=memcached
|
||||
CACHE_DRIVER=memcached
|
||||
QUEUE_CONNECTION=database
|
||||
SESSION_DRIVER=file
|
||||
@ -46,6 +48,7 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
FIDO_DIR=fido
|
||||
FIDO_PACKET_KEEP=
|
||||
FIDO_STRICT=false
|
||||
|
||||
FILESYSTEM_DISK=s3
|
||||
AWS_ACCESS_KEY_ID=
|
||||
|
@ -2,7 +2,7 @@ APP_NAME="Clearing Houz Testing"
|
||||
APP_ENV=testing
|
||||
APP_KEY=base64:FiMSvv4J7jDfy6W/sHrQ9YImuUYaxynYCcXQJwp/6Tc=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
APP_URL=http://clrghouz
|
||||
APP_TIMEZONE=Australia/Melbourne
|
||||
|
||||
LOG_CHANNEL=stderr
|
||||
@ -41,7 +41,8 @@ PUSHER_APP_CLUSTER=mt1
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
FIDO_DIR=fido
|
||||
FIDO_DIR=test
|
||||
FIDO_DIR_FILES=local
|
||||
FIDO_PACKET_KEEP=true
|
||||
|
||||
FILESYSTEM_DISK=s3
|
||||
|
147
.gitea/workflows/build_docker.yaml
Normal file
147
.gitea/workflows/build_docker.yaml
Normal file
@ -0,0 +1,147 @@
|
||||
name: Create Docker Image
|
||||
run-name: ${{ gitea.actor }} Building Docker Image 🐳
|
||||
on: [push]
|
||||
env:
|
||||
VERSION: latest
|
||||
DOCKER_HOST: tcp://127.0.0.1:2375
|
||||
|
||||
jobs:
|
||||
# test:
|
||||
# strategy:
|
||||
# matrix:
|
||||
# arch:
|
||||
# - x86_64
|
||||
# # arm64
|
||||
#
|
||||
# name: Test Application
|
||||
# runs-on: docker-${{ matrix.arch }}
|
||||
# container:
|
||||
# image: gitea.dege.au/docker/php:8.3-fpm-pgsql-server-test
|
||||
#
|
||||
# steps:
|
||||
# - name: Environment Setup
|
||||
# run: |
|
||||
# # If we have a proxy use it
|
||||
# if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
|
||||
# # Some pre-reqs
|
||||
# apk add git nodejs
|
||||
# ## Some debugging info
|
||||
# # env|sort
|
||||
#
|
||||
# - name: Code Checkout
|
||||
# uses: actions/checkout@v4
|
||||
#
|
||||
# - name: Run Tests
|
||||
# run: |
|
||||
# mv .env.testing .env
|
||||
# # Install Composer and project dependencies.
|
||||
# mkdir -p ${COMPOSER_HOME}
|
||||
# if [ -n "${{ secrets.COMPOSER_GITHUB_TOKEN }}" ]; then echo ${{ secrets.COMPOSER_GITHUB_TOKEN }} > ${COMPOSER_HOME}/auth.json; fi
|
||||
# composer install
|
||||
# # Generate an application key. Re-cache.
|
||||
# php artisan key:generate
|
||||
# php artisan migrate
|
||||
# php artisan db:seed
|
||||
# # run laravel tests
|
||||
# touch storage/app/test/*ZIP storage/app/test/file/*
|
||||
# XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text --colors=never
|
||||
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- arm64
|
||||
# needs: [test]
|
||||
|
||||
name: Build Docker Image
|
||||
runs-on: docker-${{ matrix.arch }}
|
||||
container:
|
||||
image: docker:dind
|
||||
privileged: true
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
VERSIONARCH: ${{ env.VERSION }}-${{ env.ARCH }}
|
||||
|
||||
steps:
|
||||
- name: Environment Setup
|
||||
run: |
|
||||
# If we have a proxy use it
|
||||
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
|
||||
# Some pre-reqs
|
||||
apk add git curl nodejs
|
||||
# Start docker
|
||||
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
|
||||
## Some debugging info
|
||||
# docker info && docker version
|
||||
env|sort
|
||||
echo "PRT: ${{ secrets.PKG_WRITE_TOKEN }}"
|
||||
|
||||
- name: Registry FQDN Setup
|
||||
id: registry
|
||||
run: |
|
||||
registry=${{ github.server_url }}
|
||||
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Container Registry Login
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ steps.registry.outputs.registry }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.PKG_WRITE_TOKEN }}
|
||||
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Record version
|
||||
run: |
|
||||
pwd
|
||||
ls -al
|
||||
echo ${GITHUB_SHA::8} > VERSION
|
||||
cat VERSION
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
push: true
|
||||
tags: "${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSIONARCH }}"
|
||||
|
||||
manifest:
|
||||
name: Final Docker Image Manifest
|
||||
runs-on: docker-x86_64
|
||||
container:
|
||||
image: docker:dind
|
||||
privileged: true
|
||||
needs: [build]
|
||||
|
||||
steps:
|
||||
- name: Environment Setup
|
||||
run: |
|
||||
# If we have a proxy use it
|
||||
if [ -n "${HTTP_PROXY}" ]; then echo "HTTP PROXY [${HTTP_PROXY}]"; sed -i -e s'/https/http/' /etc/apk/repositories; fi
|
||||
# Some pre-reqs
|
||||
apk add git curl nodejs
|
||||
# Start docker
|
||||
( dockerd --host=tcp://0.0.0.0:2375 --tls=false & ) && sleep 3
|
||||
|
||||
- name: Registry FQDN Setup
|
||||
id: registry
|
||||
run: |
|
||||
registry=${{ github.server_url }}
|
||||
echo "registry=${registry##http*://}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Container Registry Login
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ steps.registry.outputs.registry }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.PKG_WRITE_TOKEN }}
|
||||
|
||||
- name: Build Docker Manifest
|
||||
run: |
|
||||
docker manifest create ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }} \
|
||||
${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}-x86_64 \
|
||||
${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}-arm64
|
||||
docker manifest push --purge ${{ steps.registry.outputs.registry }}/${{ env.GITHUB_REPOSITORY }}:${{ env.VERSION }}
|
@ -1,14 +0,0 @@
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
|
||||
# This folder is cached between builds
|
||||
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
|
||||
cache:
|
||||
key: ${CI_JOB_NAME_SLUG}-${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- vendor/
|
||||
|
||||
include:
|
||||
- .gitlab-test.yml
|
||||
- .gitlab-docker-x86_64.yml
|
@ -1,27 +0,0 @@
|
||||
docker:
|
||||
variables:
|
||||
VERSION: latest
|
||||
DOCKER_HOST: tcp://docker:2375
|
||||
|
||||
stage: build
|
||||
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:dind
|
||||
|
||||
before_script:
|
||||
- docker info && docker version
|
||||
- echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin
|
||||
- if [ -n "$GITHUB_TOKEN" ]; then cat $GITHUB_TOKEN |base64 -d > auth.json; fi
|
||||
|
||||
script:
|
||||
- if [ -f init ]; then chmod 500 init; fi
|
||||
- echo -n ${CI_COMMIT_SHORT_SHA} > VERSION
|
||||
- rm -rf vendor/ database/schema database/seeders database/factories/*
|
||||
- docker build -t ${CI_REGISTRY_IMAGE}:${VERSION} .
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${VERSION}
|
||||
tags:
|
||||
- docker
|
||||
- x86_64
|
||||
only:
|
||||
- master
|
@ -1,42 +0,0 @@
|
||||
test:
|
||||
image: ${CI_REGISTRY}/leenooks/php:8.1-fpm-alpine-pgsql-server-test
|
||||
|
||||
stage: test
|
||||
|
||||
# NOTE: This service is dependant on project file configuration, which is not there if the cache was deleted
|
||||
# resulting in the testing to fail on the first run.
|
||||
services:
|
||||
- name: postgres:15-alpine
|
||||
alias: postgres-test
|
||||
|
||||
variables:
|
||||
POSTGRES_USER: test
|
||||
POSTGRES_PASSWORD: test
|
||||
|
||||
tags:
|
||||
- php
|
||||
only:
|
||||
- master
|
||||
|
||||
before_script:
|
||||
- mv .env.testing .env
|
||||
|
||||
# Install Composer and project dependencies.
|
||||
- mkdir -p ${COMPOSER_HOME}
|
||||
- if [ -n "$GITHUB_TOKEN" ]; then cat $GITHUB_TOKEN |base64 -d > ${COMPOSER_HOME}/auth.json; fi
|
||||
- composer install
|
||||
|
||||
# Generate an application key. Re-cache.
|
||||
- php artisan key:generate
|
||||
- php artisan migrate
|
||||
- php artisan db:seed
|
||||
|
||||
script:
|
||||
# run laravel tests
|
||||
- XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text --colors=never
|
||||
|
||||
# run frontend tests
|
||||
# if you have any task for testing frontend
|
||||
# set it in your package.json script
|
||||
# comment this out if you don't have a frontend test
|
||||
# npm test
|
@ -28,7 +28,13 @@ class CompressedString implements CastsAttributes
|
||||
? stream_get_contents($value)
|
||||
: $value;
|
||||
|
||||
// If we get an error decompressing, it might not be zstd (or its already been done)
|
||||
try {
|
||||
return $value ? zstd_uncompress(base64_decode($value)) : '';
|
||||
|
||||
} catch (\ErrorException $e) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
12
app/Classes/Dynamic.php
Normal file
12
app/Classes/Dynamic.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
|
||||
/**
|
||||
* Dynamic files that are sent to systems during a mailer session
|
||||
*/
|
||||
abstract class Dynamic
|
||||
{
|
||||
abstract public function __toString(): string;
|
||||
abstract public function getName(): string;
|
||||
}
|
125
app/Classes/Dynamic/HubStats.php
Normal file
125
app/Classes/Dynamic/HubStats.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Dynamic;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\Dynamic;
|
||||
use App\Models\Address;
|
||||
|
||||
/**
|
||||
* This method will generate the hub status for an upstream Host/RC/ZC
|
||||
*
|
||||
* Arg is a collection of arguments. We only understand
|
||||
* + name - name of file to give to remote
|
||||
*/
|
||||
class HubStats extends Dynamic
|
||||
{
|
||||
private const LOGKEY = 'DHS';
|
||||
|
||||
private string $name = '';
|
||||
|
||||
public function __construct(private Address $ao,Collection $arg)
|
||||
{
|
||||
Log::debug(sprintf('%s:- Generating Hub Stats for [%s] with arguments',self::LOGKEY,$ao->ftn),['args'=>$arg]);
|
||||
$this->name = $arg->get('name');
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$date = Carbon::now()->yesterday()->endOfday();
|
||||
|
||||
$r = Address::select([
|
||||
'a.id',
|
||||
'addresses.system_id',
|
||||
'addresses.zone_id',
|
||||
'addresses.region_id',
|
||||
'addresses.host_id',
|
||||
'addresses.node_id',
|
||||
'addresses.point_id',
|
||||
'addresses.hub_id',
|
||||
'addresses.role',
|
||||
DB::raw('sum(a.uncollected_echomail) as uncollected_echomail'),
|
||||
DB::raw('sum(a.uncollected_netmail) as uncollected_netmail'),
|
||||
DB::raw('sum(a.uncollected_files) as uncollected_files')
|
||||
])
|
||||
->from(
|
||||
Address::UncollectedEchomailTotal()
|
||||
->where('echomails.created_at','<',$date)
|
||||
->union(Address::UncollectedNetmailTotal()
|
||||
->where('netmails.created_at','<',$date)
|
||||
)
|
||||
->union(Address::UncollectedFilesTotal()
|
||||
->where('files.created_at','<',$date)
|
||||
),'a')
|
||||
->where('systems.active',true)
|
||||
->where('addresses.active',TRUE)
|
||||
->where('zones.active',TRUE)
|
||||
->where('domains.active',TRUE)
|
||||
->where('zones.id',$this->ao->zone_id)
|
||||
->join('addresses',['addresses.id'=>'a.id'])
|
||||
->join('systems',['systems.id'=>'addresses.system_id'])
|
||||
->join('zones',['zones.id'=>'addresses.zone_id'])
|
||||
->join('domains',['domains.id'=>'zones.domain_id'])
|
||||
->ftnOrder()
|
||||
->groupBy('addresses.system_id','a.id','addresses.zone_id','addresses.region_id','addresses.host_id','addresses.node_id','addresses.point_id','addresses.hub_id','addresses.role')
|
||||
->with(['system','zone.domain']);
|
||||
|
||||
$header = "| %-12s | %4d | %3d | %3d | %16s | %5s | %5s |\r\n";
|
||||
|
||||
$output = sprintf("Hub Status for [%s] as at [%s]\r\n",our_address($this->ao)->ftn,$date);
|
||||
$output .= "\r";
|
||||
$output .= "+--------------+------+-----+-----+------------------+-------+-------+\r\n";
|
||||
$output .= "| FTN | ECHO | NET |FILES| LAST SESSION | MODE |AUTOHLD|\r\n";
|
||||
$output .= "+--------------+------+-----+-----+------------------+-------+-------+\r\n";
|
||||
|
||||
$havedown = FALSE;
|
||||
$havehold = FALSE;
|
||||
|
||||
foreach($r->get() as $o) {
|
||||
if ($o->uncollected_echomail > 10000)
|
||||
$o->uncollected_echomail = 9999;
|
||||
|
||||
if ($o->uncollected_netmail > 10000)
|
||||
$o->uncollected_netmail = 9999;
|
||||
|
||||
if ($o->uncollected_files > 10000)
|
||||
$o->uncollected_files = 9999;
|
||||
|
||||
if ((! $havedown) && $o->is_down)
|
||||
$havedown = TRUE;
|
||||
|
||||
if ((! $havehold) && $o->is_hold)
|
||||
$havehold = TRUE;
|
||||
|
||||
$output .= sprintf($header,
|
||||
sprintf('%s %s',$o->ftn4d,$o->is_down ? 'd' : ($o->is_hold ? 'h' : ' ')),
|
||||
$o->uncollected_echomail ?? 0,
|
||||
$o->uncollected_netmail ?? 0,
|
||||
$o->uncollected_files ?? 0,
|
||||
$o->system->last_session?->format('Y-m-d H:i'),
|
||||
is_null($o->system->pollmode) ? 'HOLD' : ($o->system->pollmode ? 'CRASH' : 'DAILY'),
|
||||
$o->system->autohold ? 'YES' : 'NO');
|
||||
}
|
||||
|
||||
$output .= "+--------------+------+-----+-----+------------------+-------+-------+\r\n";
|
||||
|
||||
$output .= "\r\n";
|
||||
|
||||
if ($havehold)
|
||||
$output .= "(h) Node is on HOLD status.\r\n";
|
||||
|
||||
if ($havedown)
|
||||
$output .= "(d) Node is on DOWN status.\r\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name ?: 'hubstats.txt';
|
||||
}
|
||||
}
|
162
app/Classes/Dynamic/NodelistSegment.php
Normal file
162
app/Classes/Dynamic/NodelistSegment.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Dynamic;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\Dynamic;
|
||||
use App\Models\{Address,System};
|
||||
|
||||
/**
|
||||
* This method will generate a nodelist for an upstream Host/RC/ZC/NC
|
||||
*
|
||||
* // @todo Only generate if nodes have been updated, added or removed
|
||||
*/
|
||||
class NodelistSegment extends Dynamic
|
||||
{
|
||||
private const LOGKEY = 'DNL';
|
||||
|
||||
private string $name = '';
|
||||
private Address $our_address;
|
||||
private Carbon $now;
|
||||
|
||||
public function __construct(Address $ao,Collection $arg)
|
||||
{
|
||||
$this->our_address = our_address($ao->zone->domain)->first();
|
||||
$this->now = Carbon::now();
|
||||
|
||||
$this->name = sprintf('z%dn%d.%d',
|
||||
$this->our_address->zone->zone_id,
|
||||
$this->our_address->host_id,
|
||||
$this->now->format('z'),
|
||||
);
|
||||
|
||||
Log::debug(sprintf('%s:- Generating Nodelist for [%s] from [%s] as [%s] with arguments',self::LOGKEY,$ao->ftn,$this->our_address->ftn,$this->our_address->role_name),['args'=>$arg]);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->crc($this->generate($this->our_address));
|
||||
}
|
||||
|
||||
private function crc(string $text): string
|
||||
{
|
||||
return sprintf(";A %s Nodelist for %s -- Day number %d : %s\r\n",
|
||||
$this->our_address->role_name,
|
||||
$this->now->format('l, M d, Y'),
|
||||
$this->now->format('z'),
|
||||
crc16($text)
|
||||
).$text;
|
||||
}
|
||||
|
||||
private function flags(Address $ao): Collection
|
||||
{
|
||||
$result = collect();
|
||||
|
||||
if (($ao->system->pollmode === TRUE) || in_array($ao->role_name,['ZC','RC','NC','HC']))
|
||||
$result->push('CM');
|
||||
|
||||
if ($ao->system->address) {
|
||||
$result->push(sprintf('INA:%s',$ao->system->address));
|
||||
|
||||
if (($x=$ao->system->mailers->pluck('name')->search('BINKP')) !== FALSE)
|
||||
$result->push(sprintf('IBN%s',(($y=$ao->system->mailers->get($x)->pivot->port) !== 24554) ? ':'.$y : ''));
|
||||
|
||||
if (($x=$ao->system->mailers->pluck('name')->search('EMSI')) !== FALSE)
|
||||
$result->push(sprintf('ITN%s',(($y=$ao->system->mailers->get($x)->pivot->port) !== 23) ? ':'.$y : ''));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function entry(Address $ao): string
|
||||
{
|
||||
$format = '%s,%d,%s,%s,%s,%s,300';
|
||||
|
||||
$prefix = '';
|
||||
$address = $ao->node_id;
|
||||
|
||||
if ((! $ao->system->address) || $ao->isPrivate) {
|
||||
$prefix = 'Pvt';
|
||||
|
||||
} elseif ($ao->isHold) {
|
||||
$prefix = 'Hold';
|
||||
|
||||
} elseif ($ao->isDown) {
|
||||
$prefix = 'Down';
|
||||
|
||||
} else
|
||||
switch ($ao->role_id) {
|
||||
case Address::NODE_ZC:
|
||||
$prefix = 'Zone';
|
||||
$address = $ao->zone->zone_id;
|
||||
break;
|
||||
|
||||
case Address::NODE_RC:
|
||||
$prefix = 'Region';
|
||||
$address = $ao->region_id;
|
||||
break;
|
||||
|
||||
case Address::NODE_NC:
|
||||
$prefix = 'Host';
|
||||
$address = $ao->host_id;
|
||||
break;
|
||||
|
||||
case Address::NODE_HC:
|
||||
$prefix = 'Hub';
|
||||
break;
|
||||
|
||||
case Address::NODE_NN:
|
||||
break;
|
||||
|
||||
case Address::NODE_POINT:
|
||||
throw new \Exception(sprintf('We have no method to include points in the nodelist [%s]',$ao->ftn));
|
||||
|
||||
default:
|
||||
throw new \Exception(sprintf('Unknown role [%d] for [%s]',$ao->role,$ao->ftn));
|
||||
}
|
||||
|
||||
return sprintf($format,
|
||||
$prefix,
|
||||
$address,
|
||||
$this->format($ao->system->name),
|
||||
$this->format($ao->system->location),
|
||||
$this->format($ao->system->sysop),
|
||||
$ao->system->phone ?: '-Unpublished-',
|
||||
).(($x=$this->flags($ao)->join(',')) ? sprintf(',%s',$x) : '');
|
||||
}
|
||||
|
||||
private function format(string $string): string
|
||||
{
|
||||
return str_replace(' ','_',str_replace(',','',$string));
|
||||
}
|
||||
|
||||
private function generate(Address $ao): string
|
||||
{
|
||||
$result = collect();
|
||||
$so = System::createUnknownSystem();
|
||||
|
||||
$result->push($this->entry($ao));
|
||||
|
||||
foreach ($ao->children() as $oo) {
|
||||
// We dont include points in the ndoelist
|
||||
if ($oo->point_id)
|
||||
continue;
|
||||
|
||||
// We exclude discovered systems
|
||||
if ($oo->system_id == $so->id)
|
||||
continue;
|
||||
|
||||
$result->push($this->generate($oo) ?: $this->entry($oo));
|
||||
}
|
||||
|
||||
return $result->join("\n");
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ abstract class FTN
|
||||
public function __get(string $key)
|
||||
{
|
||||
switch ($key) {
|
||||
case 'fftn':
|
||||
case 'fftn_t':
|
||||
return sprintf('%d:%d/%d.%d',
|
||||
$this->fz,
|
||||
$this->fn,
|
||||
@ -19,7 +19,7 @@ abstract class FTN
|
||||
$this->fp,
|
||||
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
|
||||
|
||||
case 'tftn':
|
||||
case 'tftn_t':
|
||||
return sprintf('%d:%d/%d.%d',
|
||||
$this->tz,
|
||||
$this->tn,
|
||||
@ -27,30 +27,16 @@ abstract class FTN
|
||||
$this->tp,
|
||||
).($this->zone ? sprintf('@%s',$this->zone->domain->name) : '');
|
||||
|
||||
case 'fftn_o':
|
||||
return Address::findFTN($this->fftn);
|
||||
case 'tftn_o':
|
||||
return Address::findFTN($this->tftn);
|
||||
case 'fftn':
|
||||
return Address::findFTN($this->fftn_t);
|
||||
case 'tftn':
|
||||
return Address::findFTN($this->tftn_t);
|
||||
|
||||
default:
|
||||
throw new \Exception('Unknown key: '.$key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a line is a kludge line.
|
||||
*
|
||||
* @param string $kludge
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected function kludge(string $kludge,string $string)
|
||||
{
|
||||
return (preg_match("/^{$kludge}/",$string))
|
||||
? chop(preg_replace("/^{$kludge}/",'',$string),"\r")
|
||||
: FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates our unpack header
|
||||
*
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,23 +5,38 @@ namespace App\Classes\FTN;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
use App\Classes\FTN as FTNBase;
|
||||
use App\Models\{Address,Software,System,Zone};
|
||||
use App\Exceptions\InvalidPacketException;
|
||||
use App\Models\{Address,Domain,Echomail,Netmail,Software,System,Zone};
|
||||
use App\Notifications\Netmails\EchomailBadAddress;
|
||||
|
||||
/**
|
||||
* Represents the structure of a message bundle
|
||||
* Represents a Fidonet Packet, that contains an array of messages.
|
||||
*
|
||||
* Thus this object is iterable as an array of Echomail::class or Netmail::class.
|
||||
*/
|
||||
class Packet extends FTNBase implements \Iterator, \Countable
|
||||
abstract class Packet extends FTNBase implements \Iterator, \Countable
|
||||
{
|
||||
private const LOGKEY = 'PKT';
|
||||
|
||||
private const BLOCKSIZE = 1024;
|
||||
protected const PACKED_MSG_LEAD = "\02\00";
|
||||
protected const PACKED_END = "\00\00";
|
||||
|
||||
public const MSG_TYPE2 = 1<<0;
|
||||
public const MSG_TYPE4 = 1<<2;
|
||||
|
||||
// @todo Rename this regex to something more descriptive, ie: FILENAME_REGEX
|
||||
public const regex = '([[:xdigit:]]{4})(?:-(\d{4,10}))?-(.+)';
|
||||
|
||||
/**
|
||||
* Packet types we support, in specific order for auto-detection to work
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public const PACKET_TYPES = [
|
||||
'2.2' => FTNBase\Packet\FSC45::class,
|
||||
'2+' => FTNBase\Packet\FSC48::class,
|
||||
@ -30,127 +45,17 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
];
|
||||
|
||||
protected array $header; // Packet Header
|
||||
protected ?string $name; // Packet name
|
||||
protected ?string $name = NULL; // Packet name
|
||||
|
||||
public File $file; // Packet filename
|
||||
public Collection $messages; // Messages in the Packet
|
||||
protected Address $fftn_p; // Address the packet is from (when packing messages)
|
||||
protected Address $tftn_p; // Address the packet is to (when packing messages)
|
||||
protected Collection $messages; // Messages in the Packet
|
||||
public Collection $errors; // Messages that fail validation
|
||||
public bool $use_cache = FALSE; // Use a cache for messages.
|
||||
protected int $index; // Our array index
|
||||
protected $pass_p = NULL; // Overwrite the packet password (when packing messages)
|
||||
|
||||
/**
|
||||
* @param string|null $header
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(string $header=NULL)
|
||||
{
|
||||
$this->messages = collect();
|
||||
$this->errors = collect();
|
||||
$this->domain = NULL;
|
||||
$this->name = NULL;
|
||||
|
||||
if ($header)
|
||||
$this->header = unpack(self::unpackheader(static::HEADER),$header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
switch ($key) {
|
||||
// From Addresses
|
||||
case 'fz': return Arr::get($this->header,'ozone');
|
||||
case 'fn': return Arr::get($this->header,'onet');
|
||||
case 'ff': return Arr::get($this->header,'onode');
|
||||
case 'fp': return Arr::get($this->header,'opoint');
|
||||
case 'fd': return rtrim(Arr::get($this->header,'odomain',"\x00"));
|
||||
|
||||
// To Addresses
|
||||
case 'tz': return Arr::get($this->header,'dzone');
|
||||
case 'tn': return Arr::get($this->header,'dnet');
|
||||
case 'tf': return Arr::get($this->header,'dnode');
|
||||
case 'tp': return Arr::get($this->header,'dpoint');
|
||||
case 'td': return rtrim(Arr::get($this->header,'ddomain',"\x00"));
|
||||
|
||||
case 'date':
|
||||
return Carbon::create(
|
||||
Arr::get($this->header,'y'),
|
||||
Arr::get($this->header,'m')+1,
|
||||
Arr::get($this->header,'d'),
|
||||
Arr::get($this->header,'H'),
|
||||
Arr::get($this->header,'M'),
|
||||
Arr::get($this->header,'S')
|
||||
);
|
||||
|
||||
case 'password':
|
||||
return rtrim(Arr::get($this->header,$key),"\x00");
|
||||
|
||||
case 'fftn':
|
||||
case 'fftn_o':
|
||||
case 'tftn':
|
||||
case 'tftn_o':
|
||||
return parent::__get($key);
|
||||
|
||||
case 'software':
|
||||
$code = Arr::get($this->header,'prodcode-hi')<<8|Arr::get($this->header,'prodcode-lo');
|
||||
Software::unguard();
|
||||
$o = Software::singleOrNew(['code'=>$code,'type'=>Software::SOFTWARE_TOSSER]);
|
||||
Software::reguard();
|
||||
|
||||
return $o;
|
||||
|
||||
case 'software_ver':
|
||||
return sprintf('%d.%d',Arr::get($this->header,'prodrev-maj'),Arr::get($this->header,'prodrev-min'));
|
||||
|
||||
case 'capability':
|
||||
// This needs to be defined in child classes, since not all children have it
|
||||
return NULL;
|
||||
|
||||
// Packet Type
|
||||
case 'type':
|
||||
return static::TYPE;
|
||||
|
||||
// Packet name:
|
||||
case 'name':
|
||||
return $this->{$key} ?: sprintf('%08x',timew());
|
||||
|
||||
default:
|
||||
throw new \Exception('Unknown key: '.$key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the packet
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$return = $this->header();
|
||||
|
||||
foreach ($this->messages as $o) {
|
||||
if ($o->packed)
|
||||
$return .= self::PACKED_MSG_LEAD.$o;
|
||||
}
|
||||
|
||||
$return .= "\00\00";
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/* STATIC */
|
||||
|
||||
/**
|
||||
* Site of the packet header
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function header_len(): int
|
||||
{
|
||||
return collect(static::HEADER)->sum(function($item) { return Arr::get($item,2); });
|
||||
}
|
||||
/* ABSTRACT */
|
||||
|
||||
/**
|
||||
* This function is intended to be implemented in child classes to test if the packet
|
||||
@ -160,23 +65,32 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
* @param string $header
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_type(string $header): bool
|
||||
abstract public static function is_type(string $header): bool;
|
||||
abstract protected function header(): string;
|
||||
|
||||
/* STATIC */
|
||||
|
||||
/**
|
||||
* Size of the packet header
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function header_len(): int
|
||||
{
|
||||
return FALSE;
|
||||
return collect(static::HEADER)->sum(function($item) { return Arr::get($item,2); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a packet file
|
||||
*
|
||||
* @param mixed $f
|
||||
* @param mixed $f File handler returning packet data
|
||||
* @param string $name
|
||||
* @param int $size
|
||||
* @param System|null $system
|
||||
* @param bool $use_cache
|
||||
* @param Domain|null $domain
|
||||
* @return Packet
|
||||
* @throws InvalidPacketException
|
||||
*/
|
||||
public static function process(mixed $f,string $name,int $size,System $system=NULL,bool $use_cache=FALSE): self
|
||||
public static function process(mixed $f,string $name,int $size,Domain $domain=NULL): self
|
||||
{
|
||||
Log::debug(sprintf('%s:+ Opening Packet [%s] with size [%d]',self::LOGKEY,$name,$size));
|
||||
|
||||
@ -207,123 +121,185 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
if (! $o)
|
||||
throw new InvalidPacketException('Cannot determine type of packet.');
|
||||
|
||||
$o->use_cache = $use_cache;
|
||||
$o->name = $name;
|
||||
|
||||
$x = fread($f,2);
|
||||
|
||||
if (strlen($x) === 2) {
|
||||
// End of Packet?
|
||||
if ((strlen($x) === 2) && ($x === "\00\00"))
|
||||
if ($x === "\00\00")
|
||||
return $o;
|
||||
|
||||
// Messages start with self::PACKED_MSG_LEAD
|
||||
if ((strlen($x) === 2) && ($x !== self::PACKED_MSG_LEAD))
|
||||
elseif ($x !== self::PACKED_MSG_LEAD)
|
||||
throw new InvalidPacketException('Not a valid packet: '.bin2hex($x));
|
||||
|
||||
// No message attached
|
||||
else if (! strlen($x))
|
||||
throw new InvalidPacketException('No message in packet: '.bin2hex($x));
|
||||
} else
|
||||
throw new InvalidPacketException('Not a valid packet, not EOP or SOM:'.bin2hex($x));
|
||||
|
||||
$o->zone = $system?->zones->firstWhere('zone_id',$o->fz);
|
||||
Log::info(sprintf('%s:- Packet [%s] is a [%s] packet, dated [%s]',self::LOGKEY,$o->name,get_class($o),$o->date));
|
||||
|
||||
// If zone is null, we'll take the zone from the packet
|
||||
if (! $o->zone)
|
||||
$o->zone = Zone::where('zone_id',$o->fz)->where('default',TRUE)->single();
|
||||
// Work out the packet zone
|
||||
if ($o->fz && ($o->fd || $domain)) {
|
||||
$o->zone = Zone::select('zones.*')
|
||||
->join('domains',['domains.id'=>'zones.domain_id'])
|
||||
->where('zone_id',$o->fz)
|
||||
->where('name',$o->fd ?: $domain->name)
|
||||
->single();
|
||||
}
|
||||
|
||||
$buf_ptr = 0;
|
||||
$message = '';
|
||||
$readbuf = '';
|
||||
$last = '';
|
||||
// If zone is not set, then we need to use a default zone - the messages may not be from this zone.
|
||||
if (empty($o->zone)) {
|
||||
Log::alert(sprintf('%s:! We couldnt work out the packet zone, so we have fallen back to the default for [%d]',self::LOGKEY,$o->fz));
|
||||
|
||||
while ($buf_ptr || (! feof($f) && ($readbuf=fread($f,self::BLOCKSIZE)))) {
|
||||
if (! $buf_ptr)
|
||||
$o->zone = Zone::where('zone_id',$o->fz)
|
||||
->where('default',TRUE)
|
||||
->singleOrFail();
|
||||
}
|
||||
|
||||
$message = ''; // Current message we are building
|
||||
$msgbuf = '';
|
||||
$leader = Message::header_len()+strlen(self::PACKED_MSG_LEAD);
|
||||
|
||||
// We loop through reading from the buffer, to find our end of message tag
|
||||
while ((! feof($f) && ($readbuf=fread($f,$leader)))) {
|
||||
$read_ptr = ftell($f);
|
||||
$msgbuf .= $readbuf;
|
||||
|
||||
// Packed messages are Message::HEADER_LEN, prefixed with self::PACKED_MSG_LEAD
|
||||
if (strlen($message) < (Message::HEADER_LEN+strlen(self::PACKED_MSG_LEAD))) {
|
||||
$addchars = (Message::HEADER_LEN+strlen(self::PACKED_MSG_LEAD))-strlen($message);
|
||||
$message .= substr($readbuf,$buf_ptr,$addchars);
|
||||
$buf_ptr += $addchars;
|
||||
// See if we have our EOM/EOP marker
|
||||
if ((($end=strpos($msgbuf,"\x00".self::PACKED_MSG_LEAD,$leader)) !== FALSE)
|
||||
|| (($end=strpos($msgbuf,"\x00".self::PACKED_END,$leader)) !== FALSE))
|
||||
{
|
||||
// Parse our message
|
||||
$o->parseMessage(substr($msgbuf,0,$end));
|
||||
|
||||
// If our buffer wasnt big enough...
|
||||
if ($buf_ptr >= strlen($readbuf)) {
|
||||
$buf_ptr = 0;
|
||||
$msgbuf = substr($msgbuf,$end+3);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Take 2 chars from the buffer and check if we have our end packet signature
|
||||
if ($last && ($buf_ptr === 0)) {
|
||||
$last .= substr($readbuf,0,2);
|
||||
|
||||
if (($end=strpos($last,"\x00".self::PACKED_MSG_LEAD,$buf_ptr)) !== FALSE) {
|
||||
$o->parseMessage(substr($message,0,$end-2));
|
||||
$last = '';
|
||||
$message = '';
|
||||
$buf_ptr = 1+$end;
|
||||
|
||||
// Loop to rebuild our header for the next message
|
||||
// If we have more to read
|
||||
} elseif ($read_ptr < $size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$last = '';
|
||||
// If we get here
|
||||
throw new InvalidPacketException(sprintf('Cannot determine END of message/packet: %s|%s',get_class($o),hex_dump($message)));;
|
||||
}
|
||||
|
||||
if (($end=strpos($readbuf,"\x00".self::PACKED_MSG_LEAD,$buf_ptr)) === FALSE) {
|
||||
// Just in case our packet break is at the end of the buffer
|
||||
$last = substr($readbuf,-2);
|
||||
|
||||
if ((str_contains($last,"\x00")) && ($size-$read_ptr > 2)) {
|
||||
$message .= substr($readbuf,$buf_ptr);
|
||||
$buf_ptr = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$last = '';
|
||||
$end = strpos($readbuf,"\x00\x00\x00",$buf_ptr);
|
||||
}
|
||||
|
||||
// See if we have found the end of the packet, if not read more.
|
||||
if ($end === FALSE && ($read_ptr < $size)) {
|
||||
$message .= substr($readbuf,$buf_ptr);
|
||||
$buf_ptr = 0;
|
||||
|
||||
continue;
|
||||
|
||||
} else {
|
||||
$message .= substr($readbuf,$buf_ptr,$end-$buf_ptr);
|
||||
$buf_ptr = $end+3;
|
||||
|
||||
if ($buf_ptr >= strlen($readbuf))
|
||||
$buf_ptr = 0;
|
||||
}
|
||||
|
||||
// Look for the next message
|
||||
$o->parseMessage($message);
|
||||
$message = '';
|
||||
}
|
||||
|
||||
// If our message is still set, then we have an unprocessed message
|
||||
if ($message)
|
||||
$o->parseMessage($message);
|
||||
if ($msgbuf)
|
||||
throw new InvalidPacketException(sprintf('Unprocessed data in packet: %s|%s',get_class($o),hex_dump($msgbuf)));
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of the version
|
||||
*
|
||||
* @return int
|
||||
* @param string|null $header
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function version_offset(): int
|
||||
public function __construct(string $header=NULL)
|
||||
{
|
||||
return Arr::get(collect(static::HEADER)->get('type'),0);
|
||||
$this->messages = collect();
|
||||
$this->errors = collect();
|
||||
|
||||
if ($header)
|
||||
$this->header = unpack(self::unpackheader(static::HEADER),$header);
|
||||
}
|
||||
|
||||
public static function version_offset_len(): int
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return Arr::get(collect(static::HEADER)->get('type'),2);
|
||||
//Log::debug(sprintf('%s:/ Requesting key for Packet::class [%s]',self::LOGKEY,$key));
|
||||
|
||||
switch ($key) {
|
||||
// From Addresses
|
||||
case 'fz': return Arr::get($this->header,'ozone');
|
||||
case 'fn': return Arr::get($this->header,'onet');
|
||||
case 'ff': return Arr::get($this->header,'onode');
|
||||
case 'fp': return Arr::get($this->header,'opoint');
|
||||
case 'fd': return rtrim(Arr::get($this->header,'odomain',"\x00"));
|
||||
|
||||
// To Addresses
|
||||
case 'tz': return Arr::get($this->header,'dzone');
|
||||
case 'tn': return Arr::get($this->header,'dnet');
|
||||
case 'tf': return Arr::get($this->header,'dnode');
|
||||
case 'tp': return Arr::get($this->header,'dpoint');
|
||||
case 'td': return rtrim(Arr::get($this->header,'ddomain',"\x00"));
|
||||
|
||||
case 'date':
|
||||
return Carbon::create(
|
||||
Arr::get($this->header,'y'),
|
||||
Arr::get($this->header,'m')+1,
|
||||
Arr::get($this->header,'d'),
|
||||
Arr::get($this->header,'H'),
|
||||
Arr::get($this->header,'M'),
|
||||
Arr::get($this->header,'S')
|
||||
);
|
||||
|
||||
case 'password':
|
||||
return rtrim(Arr::get($this->header,$key),"\x00");
|
||||
|
||||
case 'fftn_t':
|
||||
case 'fftn':
|
||||
case 'tftn_t':
|
||||
case 'tftn':
|
||||
return parent::__get($key);
|
||||
|
||||
case 'product':
|
||||
return Arr::get($this->header,'prodcode-hi')<<8|Arr::get($this->header,'prodcode-lo');
|
||||
|
||||
case 'software':
|
||||
Software::unguard();
|
||||
$o = Software::singleOrNew(['code'=>$this->product,'type'=>Software::SOFTWARE_TOSSER]);
|
||||
Software::reguard();
|
||||
|
||||
return $o;
|
||||
|
||||
case 'software_ver':
|
||||
return sprintf('%d.%d',Arr::get($this->header,'prodrev-maj'),Arr::get($this->header,'prodrev-min'));
|
||||
|
||||
case 'capability':
|
||||
// This needs to be defined in child classes, since not all children have it
|
||||
return NULL;
|
||||
|
||||
// Packet Type
|
||||
case 'type':
|
||||
return static::TYPE;
|
||||
|
||||
// Packet name:
|
||||
case 'name':
|
||||
return $this->{$key} ?: sprintf('%08x',timew());
|
||||
|
||||
case 'messages':
|
||||
return $this->{$key};
|
||||
|
||||
default:
|
||||
throw new \Exception('Unknown key: '.$key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the packet
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
if (empty($this->messages))
|
||||
throw new InvalidPacketException('Refusing to make an empty packet');
|
||||
|
||||
if (empty($this->tftn_p) || empty($this->fftn_p))
|
||||
throw new InvalidPacketException('Cannot generate a packet without a destination address');
|
||||
|
||||
$return = $this->header();
|
||||
|
||||
foreach ($this->messages as $o)
|
||||
$return .= self::PACKED_MSG_LEAD.$o->packet($this->tftn_p);
|
||||
|
||||
$return .= "\00\00";
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/* INTERFACE */
|
||||
@ -336,14 +312,14 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
return $this->messages->count();
|
||||
}
|
||||
|
||||
public function current(): Message
|
||||
public function current(): Echomail|Netmail
|
||||
{
|
||||
return $this->use_cache ? unserialize(Cache::pull($this->key())) : $this->messages->get($this->index);
|
||||
return $this->messages->get($this->index);
|
||||
}
|
||||
|
||||
public function key(): mixed
|
||||
{
|
||||
return $this->use_cache ? $this->messages->get($this->index) : $this->index;
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
@ -358,7 +334,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return (! is_null($this->key())) && ($this->use_cache ? Cache::has($this->key()) : $this->messages->has($this->key()));
|
||||
return (! is_null($this->key())) && $this->messages->has($this->key());
|
||||
}
|
||||
|
||||
/* METHODS */
|
||||
@ -369,6 +345,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
* @param Address $oo
|
||||
* @param Address $o
|
||||
* @param string|null $passwd Override the password used in the packet
|
||||
* @deprecated Use Packet::generate(), which should generate a packet of the right type
|
||||
*/
|
||||
public function addressHeader(Address $oo,Address $o,string $passwd=NULL): void
|
||||
{
|
||||
@ -394,7 +371,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
'H' => $date->format('H'), // Hour
|
||||
'M' => $date->format('i'), // Minute
|
||||
'S' => $date->format('s'), // Second
|
||||
'password' => (! is_null($passwd)) ? $passwd : $o->session('pktpass'), // Packet Password
|
||||
'password' => strtoupper((! is_null($passwd)) ? $passwd : $o->session('pktpass')), // Packet Password
|
||||
];
|
||||
}
|
||||
|
||||
@ -402,12 +379,38 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
* Add a message to this packet
|
||||
*
|
||||
* @param Message $o
|
||||
* @deprecated No longer used when Address::class is updated
|
||||
*/
|
||||
public function addMail(Message $o): void
|
||||
{
|
||||
$this->messages->push($o);
|
||||
}
|
||||
|
||||
public function for(Address $ao): self
|
||||
{
|
||||
$this->tftn_p = $ao;
|
||||
$this->fftn_p = our_address($ao);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a packet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate(): string
|
||||
{
|
||||
return (string)$this;
|
||||
}
|
||||
|
||||
public function mail(Collection $msgs): self
|
||||
{
|
||||
$this->messages = $msgs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a message in a mail packet
|
||||
*
|
||||
@ -416,113 +419,100 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
*/
|
||||
private function parseMessage(string $message): void
|
||||
{
|
||||
Log::info(sprintf('%s:Processing message [%d] bytes',self::LOGKEY,strlen($message)));
|
||||
Log::info(sprintf('%s:+ Processing packet message [%d] bytes',self::LOGKEY,strlen($message)));
|
||||
|
||||
$msg = Message::parseMessage($message,$this->zone);
|
||||
|
||||
// If the message is invalid, we'll ignore it
|
||||
if ($msg->errors) {
|
||||
Log::info(sprintf('%s:- Message [%s] has errors',self::LOGKEY,$msg->msgid));
|
||||
if ($msg->errors->count()) {
|
||||
Log::info(sprintf('%s:- Message [%s] has [%d] errors',self::LOGKEY,$msg->msgid ?: 'No ID',$msg->errors->count()));
|
||||
|
||||
// If the from address doenst exist, we'll create a new entry
|
||||
if ($msg->errors->messages()->has('to') && $msg->tzone) {
|
||||
try {
|
||||
// @todo Need to work out the correct region for the host_id
|
||||
Address::unguard();
|
||||
$ao = Address::firstOrNew([
|
||||
'zone_id' => $msg->tzone->id,
|
||||
//'region_id' => 0,
|
||||
'host_id' => $msg->tn,
|
||||
'node_id' => $msg->tf,
|
||||
'point_id' => $msg->tp,
|
||||
'active' => TRUE,
|
||||
]);
|
||||
Address::reguard();
|
||||
|
||||
if (is_null($ao->region_id))
|
||||
$ao->region_id = $ao->host_id;
|
||||
// If the messages is not for the right zone, we'll ignore it
|
||||
if ($msg->errors->has('invalid-zone')) {
|
||||
Log::alert(sprintf('%s:! Message [%s] is from an invalid zone [%s], packet is from [%s] - ignoring it',self::LOGKEY,$msg->msgid,$msg->fftn->zone->zone_id,$this->fftn->zone->zone_id));
|
||||
|
||||
if (! $msg->kludges->get('RESCANNED'))
|
||||
Notification::route('netmail',$this->fftn)->notify(new EchomailBadAddress($msg));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s:! Error finding/creating TO address [%s] for message',self::LOGKEY,$msg->tboss),['error'=>$e->getMessage()]);
|
||||
$this->errors->push($msg);
|
||||
return;
|
||||
}
|
||||
|
||||
$ao->role = Address::NODE_UNKNOWN;
|
||||
// If the $msg->fftn doesnt exist, we'll need to create it
|
||||
if ($msg->errors->has('from') && $this->fftn && $this->fftn->zone_id) {
|
||||
Log::debug(sprintf('%s:^ From address [%s] doesnt exist, it needs to be created',self::LOGKEY,$msg->set->get('set_fftn')));
|
||||
|
||||
System::unguard();
|
||||
$so = System::firstOrCreate([
|
||||
'name' => 'Discovered System',
|
||||
'sysop' => 'Unknown',
|
||||
'location' => '',
|
||||
'active' => TRUE,
|
||||
]);
|
||||
System::reguard();
|
||||
if ($so->id !== 443)
|
||||
Log::alert(sprintf('%s:? Just created Discovered System for MSGID [%s] A',self::LOGKEY,$msg->msgid));
|
||||
$ao = Address::findFTN($msg->set->get('set_fftn'),TRUE);
|
||||
|
||||
$so->addresses()->save($ao);
|
||||
if ($ao?->exists && ($ao->zone?->domain_id !== $this->fftn->zone->domain_id)) {
|
||||
Log::alert(sprintf('%s:! From address [%s] domain [%d] doesnt match packet domain [%d]?',self::LOGKEY,$msg->set->get('set_fftn'),$ao->zone?->domain_id,$this->fftn->zone->domain_id));
|
||||
|
||||
Log::alert(sprintf('%s: - To FTN is not defined, creating new entry for [%s] (%d)',self::LOGKEY,$msg->tboss,$ao->id));
|
||||
}
|
||||
|
||||
if ($msg->errors->messages()->has('from') && $msg->tzone) {
|
||||
try {
|
||||
// @todo Need to work out the correct region for the host_id
|
||||
Address::unguard();
|
||||
$ao = Address::firstOrNew([
|
||||
'zone_id' => $msg->fzone->id,
|
||||
//'region_id' => 0,
|
||||
'host_id' => $msg->fn,
|
||||
'node_id' => $msg->ff,
|
||||
'point_id' => $msg->fp,
|
||||
'active'=> TRUE,
|
||||
]);
|
||||
Address::reguard();
|
||||
|
||||
if (is_null($ao->region_id))
|
||||
$ao->region_id = $ao->host_id;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s:! Error finding/creating FROM address [%s] for message',self::LOGKEY,$msg->fboss),['error'=>$e->getMessage()]);
|
||||
$this->errors->push($msg);
|
||||
return;
|
||||
}
|
||||
|
||||
$ao->role = Address::NODE_UNKNOWN;
|
||||
|
||||
System::unguard();
|
||||
$so = System::firstOrCreate([
|
||||
'name' => 'Discovered System',
|
||||
'sysop' => 'Unknown',
|
||||
'location' => '',
|
||||
'active' => TRUE,
|
||||
]);
|
||||
System::reguard();
|
||||
if ($so->id !== 443)
|
||||
Log::alert(sprintf('%s:? Just created Discovered System for MSGID [%s] B',self::LOGKEY,$msg->msgid));
|
||||
|
||||
$so->addresses()->save($ao);
|
||||
|
||||
Log::alert(sprintf('%s: - From FTN is not defined, creating new entry for [%s] (%d)',self::LOGKEY,$msg->fboss,$ao->id));
|
||||
if (! $ao) {
|
||||
$so = System::createUnknownSystem();
|
||||
$ao = Address::createFTN($msg->set->get('set_fftn'),$so);
|
||||
}
|
||||
|
||||
if ($msg->errors->messages()->has('user_from') || $msg->errors->messages()->has('user_to')) {
|
||||
Log::error(sprintf('%s:! Skipping message [%s] due to errors (%s)...',self::LOGKEY,$msg->msgid,join(',',$msg->errors->messages()->keys())));
|
||||
$this->errors->push($msg);
|
||||
$msg->fftn_id = $ao->id;
|
||||
Log::alert(sprintf('%s:- From FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_fftn'),$ao->id));
|
||||
}
|
||||
|
||||
// If the $msg->tftn doesnt exist, we'll need to create it
|
||||
if ($msg->errors->has('to') && $this->tftn && $this->tftn->zone_id) {
|
||||
Log::debug(sprintf('%s:^ To address [%s] doesnt exist, it needs to be created',self::LOGKEY,$msg->set->get('set_tftn')));
|
||||
|
||||
$ao = Address::findFTN($msg->set->get('set_tftn'),TRUE);
|
||||
|
||||
if ($ao?->exists && ($ao->zone?->domain_id !== $this->tftn->zone->domain_id)) {
|
||||
Log::alert(sprintf('%s:! To address [%s] domain [%d] doesnt match packet domain [%d]?',self::LOGKEY,$msg->set->get('set_tftn'),$ao->zone?->domain_id,$this->fftn->zone->domain_id));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $ao) {
|
||||
$so = System::createUnknownSystem();
|
||||
$ao = Address::createFTN($msg->set->get('set_fftn'),$so);
|
||||
}
|
||||
|
||||
$msg->tftn_id = $ao->id;
|
||||
Log::alert(sprintf('%s:- To FTN [%s] is not defined, created new entry for (%d)',self::LOGKEY,$msg->set->get('set_tftn'),$ao->id));
|
||||
}
|
||||
|
||||
// If there is no fftn, then its from a system that we dont know about
|
||||
if (! $this->fftn) {
|
||||
Log::alert(sprintf('%s:! No further message processing, packet is from a system we dont know about [%s]',self::LOGKEY,$this->fftn_t));
|
||||
|
||||
$this->messages->push($msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->use_cache) {
|
||||
$key = urlencode($msg->msgid ?: sprintf('%s %s',$msg->fftn,Carbon::now()->timestamp));
|
||||
if (! Cache::forever($key,serialize($msg)))
|
||||
throw new \Exception(sprintf('Caching failed for key [%s]?',$key));
|
||||
// @todo If the message from domain (eg: $msg->fftn->zone->domain) is different to the packet address domain ($pkt->fftn->zone->domain), we'll skip this message
|
||||
Log::debug(sprintf('%s:^ Message [%s] - Packet from domain [%d], Message domain [%d]',self::LOGKEY,$msg->msgid,$this->fftn->zone->domain_id,$msg->fftn->zone->domain_id));
|
||||
|
||||
$this->messages->push($key);
|
||||
|
||||
} else {
|
||||
$this->messages->push($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the packet password
|
||||
*
|
||||
* @param string|null $password
|
||||
* @return self
|
||||
*/
|
||||
public function password(string $password=NULL): self
|
||||
{
|
||||
if ($password && (strlen($password) < 9))
|
||||
$this->pass_p = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @deprecated Is this used? */
|
||||
public function pluck(string $key): Collection
|
||||
{
|
||||
throw new \Exception(sprintf('%s:! This function is deprecated - [%s]',self::LOGKEY,$key));
|
||||
return $this->messages->pluck($key);
|
||||
}
|
||||
}
|
@ -30,14 +30,14 @@ final class FSC39 extends Packet
|
||||
'dnet' => [0x16,'v',2], // Dest Net
|
||||
'prodcode-lo' => [0x18,'C',1], // Product Code
|
||||
'prodrev-maj' => [0x19,'C',1], // Product Version Major
|
||||
'password' => [0x1a,'a8',8], // Packet Password
|
||||
'password' => [0x1a,'a8',8], // Packet Password - http://ftsc.org/docs/fsc-0039.004 packet passwords are A-Z,0-9
|
||||
'ozone' => [0x22,'v',2], // Orig Zone
|
||||
'dzone' => [0x24,'v',2], // Dest Zone
|
||||
'reserved' => [0x26,'a2',2], // Reserved
|
||||
'capvalid' => [0x28,'n',2], // fsc-0039.004 (copy of 0x2c)
|
||||
'prodcode-hi' => [0x2a,'C',1], // Product Code Hi
|
||||
'prodrev-min' => [0x2b,'C',1], // Product Version Minor
|
||||
'capword' => [0x2c,'v',2], // Capability Word
|
||||
'capword' => [0x2c,'v',2], // Capability Word fsc-0039.004/fsc-0048.002
|
||||
'dozone' => [0x2e,'v',2], // Orig Zone
|
||||
'ddzone' => [0x30,'v',2], // Dest Zone
|
||||
'opoint' => [0x32,'v',2], // Orig Point
|
||||
@ -46,6 +46,7 @@ final class FSC39 extends Packet
|
||||
];
|
||||
|
||||
public const TYPE = '2e';
|
||||
public const VERS = self::MSG_TYPE2; //|self::MSG_TYPE4;
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
@ -63,34 +64,36 @@ final class FSC39 extends Packet
|
||||
*/
|
||||
protected function header(): string
|
||||
{
|
||||
$oldest = $this->messages->sortBy('datetime')->last();
|
||||
|
||||
try {
|
||||
return pack(collect(self::HEADER)->pluck(1)->join(''),
|
||||
$this->ff, // Orig Node
|
||||
$this->tf, // Dest Node
|
||||
Arr::get($this->header,'y'), // Year
|
||||
Arr::get($this->header,'m'), // Month
|
||||
Arr::get($this->header,'d'), // Day
|
||||
Arr::get($this->header,'H'), // Hour
|
||||
Arr::get($this->header,'M'), // Minute
|
||||
Arr::get($this->header,'S'), // Second
|
||||
$this->fftn_p->node_id, // Orig Node
|
||||
$this->tftn_p->node_id, // Dest Node
|
||||
$oldest->datetime->format('Y'), // Year
|
||||
$oldest->datetime->format('m')-1, // Month
|
||||
$oldest->datetime->format('d'), // Day
|
||||
$oldest->datetime->format('H'), // Hour
|
||||
$oldest->datetime->format('i'), // Minute
|
||||
$oldest->datetime->format('s'), // Second
|
||||
0, // Baud
|
||||
2, // Packet Version (should be 2)
|
||||
$this->fn, // Orig Net
|
||||
$this->tn, // Dest Net
|
||||
$this->fftn_p->host_id, // Orig Net
|
||||
$this->tftn_p->host_id, // Dest Net
|
||||
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
|
||||
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
|
||||
$this->password, // Packet Password
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->pass_p ?: $this->tftn_p->session('pktpass'), // Packet Password
|
||||
$this->fftn_p->zone->zone_id, // Orig Zone
|
||||
$this->tftn_p->zone->zone_id, // Dest Zone
|
||||
'', // Reserved
|
||||
Arr::get($this->header,'capvalid',1<<0), // fsc-0039.004 (copy of 0x2c)
|
||||
static::VERS, // fsc-0039.004 (copy of 0x2c)
|
||||
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
|
||||
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
|
||||
Arr::get($this->header,'capword',1<<0), // Capability Word
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->fp, // Orig Point
|
||||
$this->tp, // Dest Point
|
||||
static::VERS, // Capability Word
|
||||
$this->fftn_p->zone->zone_id, // Orig Zone
|
||||
$this->tftn_p->zone->zone_id, // Dest Zone
|
||||
$this->fftn_p->point_id, // Orig Point
|
||||
$this->tftn_p->point_id, // Dest Point
|
||||
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
|
||||
);
|
||||
|
||||
|
@ -37,6 +37,20 @@ final class FSC45 extends Packet
|
||||
|
||||
public const TYPE = '2.2';
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
switch ($key) {
|
||||
case 'product':
|
||||
return Arr::get($this->header,'prodcode');
|
||||
|
||||
case 'software_ver':
|
||||
return Arr::get($this->header,'prodrev-maj');
|
||||
|
||||
default:
|
||||
return parent::__get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create our message packet header
|
||||
*/
|
||||
@ -44,22 +58,22 @@ final class FSC45 extends Packet
|
||||
{
|
||||
try {
|
||||
return pack(collect(self::HEADER)->pluck(1)->join(''),
|
||||
$this->ff, // Orig Node
|
||||
$this->tf, // Dest Node
|
||||
$this->fp, // Orig Point
|
||||
$this->tp, // Dest Point
|
||||
$this->fftn_p->node_id, // Orig Node
|
||||
$this->tftn_p->node_id, // Dest Node
|
||||
$this->fftn_p->point_id, // Orig Point
|
||||
$this->tftn_p->point_id, // Dest Point
|
||||
'', // Reserved
|
||||
2, // Sub Version (should be 2)
|
||||
2, // Packet Version (should be 2)
|
||||
$this->fn, // Orig Net
|
||||
$this->tn, // Dest Net
|
||||
$this->fftn_p->host_id, // Orig Net
|
||||
$this->tftn_p->host_id, // Dest Net
|
||||
(Setup::PRODUCT_ID & 0xff), // Product Code
|
||||
Setup::PRODUCT_VERSION_MAJ, // Product Version
|
||||
$this->password, // Packet Password
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->fd, // Orig Domain
|
||||
$this->td, // Dest Domain
|
||||
$this->pass_p ?: $this->tftn_p->session('pktpass'), // Packet Password
|
||||
$this->fftn_p->zone->zone_id, // Orig Zone
|
||||
$this->tftn_p->zone->zone_id, // Dest Zone
|
||||
$this->fftn_p->zone->domain->name, // Orig Domain
|
||||
$this->tftn_p->zone->domain->name, // Dest Domain
|
||||
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
|
||||
);
|
||||
|
||||
|
@ -11,6 +11,7 @@ use App\Models\Setup;
|
||||
* FSC-0048 http://ftsc.org/docs/fsc-0048.002
|
||||
*
|
||||
* Commonly known as Type 2+ packets, based on FSC-0039 with improved support for FTS-0001
|
||||
* @note: These packets will be detected as FSC-0039 packets unless it is addressed to a point
|
||||
*/
|
||||
final class FSC48 extends Packet
|
||||
{
|
||||
@ -36,7 +37,7 @@ final class FSC48 extends Packet
|
||||
'capvalid' => [0x28,'n',2], // fsc-0039.004 (copy of 0x2c)
|
||||
'prodcode-hi' => [0x2a,'C',1], // Product Code Hi
|
||||
'prodrev-min' => [0x2b,'C',1], // Product Version Minor
|
||||
'capword' => [0x2c,'v',2], // Capability Word
|
||||
'capword' => [0x2c,'v',2], // Capability Word fsc-0039.004/fsc-0048.002
|
||||
'dozone' => [0x2e,'v',2], // Orig Zone
|
||||
'ddzone' => [0x30,'v',2], // Dest Zone
|
||||
'opoint' => [0x32,'v',2], // Orig Point
|
||||
@ -45,6 +46,7 @@ final class FSC48 extends Packet
|
||||
];
|
||||
|
||||
public const TYPE = '2+';
|
||||
public const VERS = self::MSG_TYPE2; //|self::MSG_TYPE4;
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
@ -62,34 +64,36 @@ final class FSC48 extends Packet
|
||||
*/
|
||||
protected function header(): string
|
||||
{
|
||||
$oldest = $this->messages->sortBy('datetime')->last();
|
||||
|
||||
try {
|
||||
return pack(collect(self::HEADER)->pluck(1)->join(''),
|
||||
$this->ff, // Orig Node
|
||||
$this->tf, // Dest Node
|
||||
Arr::get($this->header,'y'), // Year
|
||||
Arr::get($this->header,'m'), // Month
|
||||
Arr::get($this->header,'d'), // Day
|
||||
Arr::get($this->header,'H'), // Hour
|
||||
Arr::get($this->header,'M'), // Minute
|
||||
Arr::get($this->header,'S'), // Second
|
||||
$this->fftn_p->node_id, // Orig Node
|
||||
$this->tftn_p->node_id, // Dest Node
|
||||
$oldest->datetime->format('Y'), // Year
|
||||
$oldest->datetime->format('m')-1, // Month
|
||||
$oldest->datetime->format('d'), // Day
|
||||
$oldest->datetime->format('H'), // Hour
|
||||
$oldest->datetime->format('i'), // Minute
|
||||
$oldest->datetime->format('s'), // Second
|
||||
0, // Baud
|
||||
2, // Packet Version (should be 2)
|
||||
$this->fp ? 0xffff : $this->fn, // Orig Net (0xFFFF when OrigPoint != 0)
|
||||
$this->tn, // Dest Net
|
||||
$this->fftn_p->point_id ? 0xffff : $this->fftn_p->host_id, // Orig Net (0xFFFF when OrigPoint != 0)
|
||||
$this->tftn_p->host_id, // Dest Net
|
||||
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
|
||||
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
|
||||
$this->password, // Packet Password
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->fp ? $this->fn : 0x00, // Aux Net
|
||||
Arr::get($this->header,'capvalid',1<<0), // fsc-0039.004 (copy of 0x2c)
|
||||
$this->pass_p ?: $this->tftn_p->session('pktpass'), // Packet Password
|
||||
$this->fftn_p->zone->zone_id, // Orig Zone
|
||||
$this->tftn_p->zone->zone_id, // Dest Zone
|
||||
$this->fftn_p->point_id ? $this->fftn_p->host_id : 0x00, // Aux Net
|
||||
static::VERS, // fsc-0039.004 (copy of 0x2c)
|
||||
((Setup::PRODUCT_ID >> 8) & 0xff), // Product Code Hi
|
||||
Setup::PRODUCT_VERSION_MIN, // Product Version Minor
|
||||
Arr::get($this->header,'capword',1<<0), // Capability Word
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->fp, // Orig Point
|
||||
$this->tp, // Dest Point
|
||||
static::VERS, // Capability Word
|
||||
$this->fftn_p->zone->zone_id, // Orig Zone
|
||||
$this->tftn_p->zone->zone_id, // Dest Zone
|
||||
$this->fftn_p->point_id, // Orig Point
|
||||
$this->tftn_p->point_id, // Dest Point
|
||||
strtoupper(hexstr(Setup::PRODUCT_ID)), // ProdData
|
||||
);
|
||||
|
||||
|
@ -38,30 +38,46 @@ final class FTS1 extends Packet
|
||||
|
||||
public const TYPE = '2';
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
switch ($key) {
|
||||
case 'product':
|
||||
return Arr::get($this->header,'prodcode');
|
||||
|
||||
case 'software_ver':
|
||||
return 'N/A';
|
||||
|
||||
default:
|
||||
return parent::__get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create our message packet header
|
||||
*/
|
||||
protected function header(): string
|
||||
{
|
||||
$oldest = $this->messages->sortBy('datetime')->last();
|
||||
|
||||
try {
|
||||
return pack(collect(self::HEADER)->pluck(1)->join(''),
|
||||
$this->ff, // Orig Node
|
||||
$this->tf, // Dest Node
|
||||
Arr::get($this->header,'y'), // Year
|
||||
Arr::get($this->header,'m'), // Month
|
||||
Arr::get($this->header,'d'), // Day
|
||||
Arr::get($this->header,'H'), // Hour
|
||||
Arr::get($this->header,'M'), // Minute
|
||||
Arr::get($this->header,'S'), // Second
|
||||
$this->fftn_p->node_id, // Orig Node
|
||||
$this->tftn_p->node_id, // Dest Node
|
||||
$oldest->datetime->format('Y'), // Year
|
||||
$oldest->datetime->format('m')-1, // Month
|
||||
$oldest->datetime->format('d'), // Day
|
||||
$oldest->datetime->format('H'), // Hour
|
||||
$oldest->datetime->format('i'), // Minute
|
||||
$oldest->datetime->format('s'), // Second
|
||||
0, // Baud
|
||||
2, // Packet Version (should be 2)
|
||||
$this->fn, // Orig Net
|
||||
$this->tn, // Dest Net
|
||||
$this->fftn_p->host_id, // Orig Net
|
||||
$this->tftn_p->host_id, // Dest Net
|
||||
(Setup::PRODUCT_ID & 0xff), // Product Code Lo
|
||||
Setup::PRODUCT_VERSION_MAJ, // Product Version Major
|
||||
$this->password, // Packet Password
|
||||
$this->fz, // Orig Zone
|
||||
$this->tz, // Dest Zone
|
||||
$this->pass_p ?: $this->tftn_p->session('pktpass'), // Packet Password
|
||||
$this->fftn_p->zone->zone_id, // Orig Zone
|
||||
$this->tftn_p->zone->zone_id, // Dest Zone
|
||||
'', // Reserved
|
||||
);
|
||||
|
||||
|
@ -2,25 +2,23 @@
|
||||
|
||||
namespace App\Classes\FTN;
|
||||
|
||||
use App\Models\Echoarea;
|
||||
use App\Models\{Echoarea,Echomail,Netmail};
|
||||
|
||||
/**
|
||||
* Abstract class to hold the common functions for automatic responding to echomail/netmail messages
|
||||
*/
|
||||
abstract class Process
|
||||
{
|
||||
public static function canProcess(string $echoarea): bool
|
||||
public static function canProcess(Echoarea $eao): bool
|
||||
{
|
||||
$eao = Echoarea::where('name',$echoarea)->single();
|
||||
|
||||
return $eao && $eao->automsgs;
|
||||
return $eao->automsgs ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return TRUE if the process class handled the message.
|
||||
*
|
||||
* @param Message $msg
|
||||
* @param Echomail|Netmail $mo
|
||||
* @return bool
|
||||
*/
|
||||
abstract public static function handle(Message $msg): bool;
|
||||
abstract public static function handle(Echomail|Netmail $mo): bool;
|
||||
}
|
@ -5,7 +5,8 @@ namespace App\Classes\FTN\Process\Echomail;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\FTN\{Message,Process};
|
||||
use App\Classes\FTN\Process;
|
||||
use App\Models\{Echomail,Netmail};
|
||||
use App\Notifications\Echomails\Test as TestNotification;
|
||||
|
||||
/**
|
||||
@ -19,16 +20,16 @@ final class Test extends Process
|
||||
|
||||
private const testing = ['test','testing'];
|
||||
|
||||
public static function handle(Message $msg): bool
|
||||
public static function handle(Echomail|Netmail $mo): bool
|
||||
{
|
||||
if (! self::canProcess($msg->echoarea)
|
||||
|| (strtolower($msg->user_to) !== 'all')
|
||||
|| (! in_array(strtolower($msg->subject),self::testing)))
|
||||
if (! self::canProcess($mo->echoarea)
|
||||
|| (strtolower($mo->to) !== 'all')
|
||||
|| (! in_array(strtolower($mo->subject),self::testing)))
|
||||
return FALSE;
|
||||
|
||||
Log::info(sprintf('%s:- Processing TEST message from (%s) [%s] in [%s]',self::LOGKEY,$msg->user_from,$msg->fftn,$msg->echoarea));
|
||||
Log::info(sprintf('%s:- Processing TEST message from (%s) [%s] in [%s]',self::LOGKEY,$mo->from,$mo->fftn->ftn,$mo->echoarea->name));
|
||||
|
||||
Notification::route('echomail',$msg->echoarea)->notify(new TestNotification($msg));
|
||||
Notification::route('echomail',$mo->echoarea->withoutRelations())->notify(new TestNotification($mo));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
37
app/Classes/FTN/Process/Netmail/Areafix.php
Normal file
37
app/Classes/FTN/Process/Netmail/Areafix.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\FTN\Process\Netmail;
|
||||
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\FTN\Process;
|
||||
use App\Models\{Echomail,Netmail};
|
||||
use App\Notifications\Netmails\Areafix as AreafixNotification;
|
||||
use App\Notifications\Netmails\Areafix\NotConfiguredHere as AreafixNotConfiguredHereNotification;
|
||||
|
||||
/**
|
||||
* Process messages to Ping
|
||||
*
|
||||
* @package App\Classes\FTN\Process
|
||||
*/
|
||||
final class Areafix extends Process
|
||||
{
|
||||
private const LOGKEY = 'RP-';
|
||||
|
||||
public static function handle(Echomail|Netmail $mo): bool
|
||||
{
|
||||
if (strtolower($mo->to) !== 'areafix')
|
||||
return FALSE;
|
||||
|
||||
Log::info(sprintf('%s:- Processing AREAFIX message from (%s) [%s]',self::LOGKEY,$mo->from,$mo->fftn));
|
||||
|
||||
// If this is not a node we manage, then respond with a sorry can help you
|
||||
if ($mo->fftn->system->sessions->count())
|
||||
Notification::route('netmail',$mo->fftn)->notify(new AreafixNotification($mo));
|
||||
else
|
||||
Notification::route('netmail',$mo->fftn)->notify(new AreafixNotConfiguredHereNotification($mo));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@ namespace App\Classes\FTN\Process\Netmail;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\FTN\{Message,Process};
|
||||
use App\Classes\FTN\Process;
|
||||
use App\Models\{Echomail,Netmail};
|
||||
use App\Notifications\Netmails\Ping as PingNotification;
|
||||
|
||||
/**
|
||||
@ -17,14 +18,14 @@ final class Ping extends Process
|
||||
{
|
||||
private const LOGKEY = 'RP-';
|
||||
|
||||
public static function handle(Message $msg): bool
|
||||
public static function handle(Echomail|Netmail $mo): bool
|
||||
{
|
||||
if (strtolower($msg->user_to) !== 'ping')
|
||||
if (strtolower($mo->to) !== 'ping')
|
||||
return FALSE;
|
||||
|
||||
Log::info(sprintf('%s:- Processing PING message from (%s) [%s]',self::LOGKEY,$msg->user_from,$msg->fftn));
|
||||
Log::info(sprintf('%s:- Processing PING message from (%s) [%s]',self::LOGKEY,$mo->from,$mo->fftn->ftn));
|
||||
|
||||
Notification::route('netmail',$msg->fftn_o)->notify(new PingNotification($msg));
|
||||
Notification::route('netmail',$mo->fftn)->notify(new PingNotification($mo));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -4,30 +4,30 @@ namespace App\Classes\FTN;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use League\Flysystem\UnableToWriteFile;
|
||||
use League\Flysystem\UnableToReadFile;
|
||||
|
||||
use App\Classes\FTN as FTNBase;
|
||||
use App\Exceptions\{InvalidCRCException,
|
||||
InvalidPasswordException,
|
||||
NodeNotSubscribedException,
|
||||
NoWriteSecurityException};
|
||||
use App\Exceptions\TIC\{NoFileAreaException,NotToMeException,SizeMismatchException};
|
||||
use App\Models\{Address,File,Filearea,Setup};
|
||||
use App\Traits\EncodeUTF8;
|
||||
|
||||
/**
|
||||
* Class TIC
|
||||
* Used create the structure of TIC files
|
||||
* This class handles the TIC files that accompany file transfers
|
||||
*
|
||||
* @package App\Classes
|
||||
*/
|
||||
class Tic extends FTNBase
|
||||
{
|
||||
use EncodeUTF8;
|
||||
|
||||
private const LOGKEY = 'FT-';
|
||||
|
||||
private const cast_utf8 = [
|
||||
];
|
||||
|
||||
// Single value kludge items and whether they are required
|
||||
// http://ftsc.org/docs/fts-5006.001
|
||||
private array $_kludge = [
|
||||
@ -52,75 +52,79 @@ class Tic extends FTNBase
|
||||
'pw' => FALSE, // Password
|
||||
];
|
||||
|
||||
private File $fo;
|
||||
private Filearea $area;
|
||||
private Collection $values;
|
||||
|
||||
private Address $origin; // Should be first address in Path
|
||||
private Address $from; // Should be last address in Path
|
||||
private File $file;
|
||||
private Address $to; // Should be me
|
||||
|
||||
public function __construct()
|
||||
public function __construct(File $file=NULL)
|
||||
{
|
||||
$this->fo = new File;
|
||||
$this->file = $file ?: new File;
|
||||
|
||||
$this->fo->kludges = collect();
|
||||
$this->fo->set_path = collect();
|
||||
$this->fo->set_seenby = collect();
|
||||
$this->fo->rogue_path = collect();
|
||||
$this->fo->rogue_seenby = collect();
|
||||
|
||||
$this->values = collect();
|
||||
$this->file->kludges = collect();
|
||||
$this->file->rogue_seenby = collect();
|
||||
$this->file->set_path = collect();
|
||||
$this->file->set_seenby = collect();
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
switch ($key) {
|
||||
case 'fo':
|
||||
case 'file':
|
||||
return $this->{$key};
|
||||
|
||||
case 'name':
|
||||
return $this->file->name;
|
||||
|
||||
default:
|
||||
return parent::__get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a TIC file for an address
|
||||
* Generate the TIC file
|
||||
*
|
||||
* @param Address $ao
|
||||
* @param File $fo
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function generate(Address $ao,File $fo): string
|
||||
public function __toString(): string
|
||||
{
|
||||
$sysaddress = Setup::findOrFail(config('app.id'))->system->match($ao->zone)->first();
|
||||
if (! $this->to)
|
||||
throw new \Exception('No to address defined');
|
||||
|
||||
$sysaddress = our_address($this->to);
|
||||
|
||||
$result = collect();
|
||||
|
||||
// Origin is the first address in our path
|
||||
$result->put('ORIGIN',$fo->path->first()->ftn3d);
|
||||
$result->put('ORIGIN',$this->file->path->first()->ftn3d);
|
||||
$result->put('FROM',$sysaddress->ftn3d);
|
||||
$result->put('TO',$ao->ftn3d);
|
||||
$result->put('FILE',$fo->name);
|
||||
$result->put('SIZE',$fo->size);
|
||||
if ($fo->description)
|
||||
$result->put('DESC',$fo->description);
|
||||
$result->put('AREA',$fo->filearea->name);
|
||||
$result->put('AREADESC',$fo->filearea->description);
|
||||
if ($x=$ao->session('ticpass'))
|
||||
$result->put('TO',$this->to->ftn3d);
|
||||
$result->put('FILE',$this->file->name);
|
||||
$result->put('SIZE',$this->file->size);
|
||||
if ($this->file->description)
|
||||
$result->put('DESC',$this->file->description);
|
||||
if ($this->file->replaces)
|
||||
$result->put('REPLACES',$this->file->replaces);
|
||||
$result->put('AREA',$this->file->filearea->name);
|
||||
$result->put('AREADESC',$this->file->filearea->description);
|
||||
if ($x=strtoupper($this->to->session('ticpass')))
|
||||
$result->put('PW',$x);
|
||||
$result->put('CRC',sprintf("%X",$fo->crc));
|
||||
$result->put('CRC',sprintf("%X",$this->file->crc));
|
||||
|
||||
$out = '';
|
||||
foreach ($result as $key=>$value)
|
||||
$out .= sprintf("%s %s\r\n",$key,$value);
|
||||
|
||||
foreach ($fo->path as $o)
|
||||
foreach ($this->file->path as $o)
|
||||
$out .= sprintf("PATH %s %s %s\r\n",$o->ftn3d,$o->pivot->datetime,$o->pivot->extra);
|
||||
|
||||
foreach ($fo->seenby as $o)
|
||||
// Add ourself to the path:
|
||||
$out .= sprintf("PATH %s %s\r\n",$sysaddress->ftn3d,Carbon::now());
|
||||
|
||||
foreach ($this->file->seenby as $o)
|
||||
$out .= sprintf("SEENBY %s\r\n",$o->ftn3d);
|
||||
|
||||
$out .= sprintf("SEENBY %s\r\n",$sysaddress->ftn3d);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
@ -131,180 +135,253 @@ class Tic extends FTNBase
|
||||
*/
|
||||
public function isNodelist(): bool
|
||||
{
|
||||
return (($this->fo->nodelist_filearea_id === $this->fo->filearea->domain->filearea_id)
|
||||
&& (preg_match(str_replace(['.','?'],['\.','.'],'#^'.$this->fo->filearea->domain->nodelist_filename.'$#i'),$this->fo->name)));
|
||||
Log::critical(sprintf('%s:D fo_nodelist_file_area [%d], fo_filearea_domain_filearea_id [%d], regex [%s] name [%s]',
|
||||
self::LOGKEY,
|
||||
$this->file->nodelist_filearea_id,
|
||||
$this->file->filearea->domain->filearea_id,
|
||||
str_replace(['.','?'],['\.','[0-9]'],'#^'.$this->file->filearea->domain->nodelist_filename.'$#i'),
|
||||
$this->file->name,
|
||||
));
|
||||
return (($this->file->nodelist_filearea_id === $this->file->filearea->domain->filearea_id)
|
||||
&& (preg_match(str_replace(['.','?'],['\.','[0-9]'],'#^'.$this->file->filearea->domain->nodelist_filename.'$#i'),$this->file->name)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a TIC file from an existing filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return void
|
||||
* @param string $filename Relative to filesystem
|
||||
* @return File
|
||||
* @throws FileNotFoundException
|
||||
* @throws InvalidCRCException
|
||||
* @throws InvalidPasswordException
|
||||
* @throws NoFileAreaException
|
||||
* @throws NoWriteSecurityException
|
||||
* @throws NodeNotSubscribedException
|
||||
* @throws NotToMeException
|
||||
* @throws SizeMismatchException
|
||||
*/
|
||||
public function load(string $filename): void
|
||||
public function load(string $filename): File
|
||||
{
|
||||
Log::info(sprintf('%s:+ Processing TIC file [%s]',self::LOGKEY,$filename));
|
||||
$fs = Storage::disk(config('fido.local_disk'));
|
||||
$rel_path_name = sprintf('%s/%s',config('fido.dir'),$filename);
|
||||
|
||||
if (str_contains($filename,'-')) {
|
||||
list($hex,$name) = explode('-',$filename);
|
||||
$hex = basename($hex);
|
||||
} else {
|
||||
$hex = '';
|
||||
if (! $fs->exists($rel_path_name))
|
||||
throw new FileNotFoundException(sprintf('File [%s] doesnt exist',$fs->path($rel_path_name)));
|
||||
|
||||
if ((! is_readable($fs->path($rel_path_name))) || ! ($f = $fs->readStream($rel_path_name)))
|
||||
throw new UnableToReadFile(sprintf('File [%s] is not readable',$fs->path($rel_path_name)));
|
||||
|
||||
/*
|
||||
* Filenames are in the format X-Y-N.tic
|
||||
* Where:
|
||||
* - X is the nodes address that sent us the file
|
||||
* - Y is the mtime of the TIC file from the sender
|
||||
* - N is the sender's filename
|
||||
*/
|
||||
|
||||
$aid = NULL;
|
||||
$mtime = NULL;
|
||||
$this->file->recv_tic = preg_replace('/\.[Tt][Ii][Cc]$/','',$filename);
|
||||
|
||||
$m = [];
|
||||
if (preg_match(sprintf('/^%s\.[Tt][Ii][Cc]$/',Packet::regex),$filename,$m)) {
|
||||
$aid = $m[1];
|
||||
$mtime = $m[2];
|
||||
$this->file->recv_tic = $m[3];
|
||||
}
|
||||
|
||||
if (! file_exists($filename))
|
||||
throw new FileNotFoundException(sprintf('File [%s] doesnt exist',$filename));
|
||||
|
||||
if (! is_readable($filename))
|
||||
throw new UnableToWriteFile(sprintf('File [%s] is not readable',realpath($filename)));
|
||||
|
||||
$f = fopen($filename,'rb');
|
||||
if (! $f) {
|
||||
Log::error(sprintf('%s:! Unable to open file [%s] for writing',self::LOGKEY,$filename));
|
||||
return;
|
||||
}
|
||||
$ldesc = '';
|
||||
|
||||
while (! feof($f)) {
|
||||
$line = chop(fgets($f));
|
||||
$matches = [];
|
||||
$m = [];
|
||||
|
||||
if (! $line)
|
||||
continue;
|
||||
|
||||
preg_match('/([a-zA-Z]+)\ (.*)/',$line,$matches);
|
||||
preg_match('/([a-zA-Z]+)\ ?(.*)?/',$line,$m);
|
||||
|
||||
if (in_array(strtolower($matches[1]),$this->_kludge)) {
|
||||
switch ($k=strtolower($matches[1])) {
|
||||
if (in_array(strtolower(Arr::get($m,1,'-')),$this->_kludge)) {
|
||||
switch ($k=strtolower($m[1])) {
|
||||
case 'area':
|
||||
$this->{$k} = Filearea::singleOrNew(['name'=>strtoupper($matches[2])]);
|
||||
try {
|
||||
if ($fo=Filearea::where('name',strtoupper($m[2]))->firstOrFail())
|
||||
$this->file->filearea_id = $fo->id;
|
||||
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// Rethrow this as No File Area
|
||||
throw new NoFileAreaException($e->getMessage());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'origin':
|
||||
case 'from':
|
||||
case 'to':
|
||||
$this->{$k} = Address::findFTN($matches[2]);
|
||||
if (($ao=Address::findFTN($m[2])) && ((! $aid) || ($ao->zone->domain_id === Address::findOrFail(hexdec($aid))->zone->domain_id)))
|
||||
$this->file->fftn_id = $ao->id;
|
||||
else
|
||||
throw new ModelNotFoundException(sprintf('FTN Address [%s] not found or sender mismatch',$m[2]));
|
||||
|
||||
break;
|
||||
|
||||
// The origin should be the first address in the path
|
||||
case 'origin':
|
||||
// Ignore
|
||||
case 'areadesc':
|
||||
case 'created':
|
||||
break;
|
||||
|
||||
// This should be one of my addresses
|
||||
case 'to':
|
||||
$ftns = our_address()->pluck('ftn3d');
|
||||
|
||||
if (! ($ftns->contains($m[2])))
|
||||
throw new NotToMeException(sprintf('FTN Address [%s] not found or not one of my addresses',$m[2]));
|
||||
|
||||
// @todo If $this->{$k} is null, we have discovered the system and it should be created
|
||||
break;
|
||||
|
||||
case 'file':
|
||||
if (! Storage::disk('local')->exists($x=sprintf('%s/%s-%s',config('app.fido'),$hex,$matches[2])))
|
||||
throw new FileNotFoundException(sprintf('File not found? [%s]',$x));
|
||||
$this->file->name = $m[2];
|
||||
|
||||
$this->fo->name = $matches[2];
|
||||
$this->fo->fullname = $x;
|
||||
break;
|
||||
|
||||
case 'areadesc':
|
||||
$areadesc = $matches[2];
|
||||
break;
|
||||
|
||||
case 'created':
|
||||
// ignored
|
||||
break;
|
||||
|
||||
case 'pw':
|
||||
$pw = $matches[2];
|
||||
$pw = $m[2];
|
||||
|
||||
break;
|
||||
|
||||
case 'lfile':
|
||||
$this->fo->lname = $matches[2];
|
||||
case 'fullname':
|
||||
$this->file->lname = $m[2];
|
||||
|
||||
break;
|
||||
|
||||
case 'desc':
|
||||
case 'magic':
|
||||
case 'replaces':
|
||||
case 'size':
|
||||
$this->fo->{$k} = $matches[2];
|
||||
break;
|
||||
$this->file->{$k} = $m[2];
|
||||
|
||||
case 'fullname':
|
||||
$this->fo->lfile = $matches[2];
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
$this->fo->datetime = Carbon::create($matches[2]);
|
||||
$this->file->datetime = Carbon::createFromTimestamp($m[2]);
|
||||
|
||||
break;
|
||||
|
||||
case 'ldesc':
|
||||
$this->fo->{$k} .= $matches[2];
|
||||
$ldesc .= ($ldesc ? "\r" : '').$m[2];
|
||||
|
||||
break;
|
||||
|
||||
case 'crc':
|
||||
$this->fo->{$k} = hexdec($matches[2]);
|
||||
$this->file->{$k} = hexdec($m[2]);
|
||||
|
||||
break;
|
||||
|
||||
case 'path':
|
||||
$x = [];
|
||||
preg_match(sprintf('#^[Pp]ath (%s)\ ?([0-9]+)\ ?(.*)$#',Address::ftn_regex),$line,$x);
|
||||
$ao = Address::findFTN($x[1]);
|
||||
|
||||
if (! $ao) {
|
||||
$this->fo->rogue_path->push($matches[2]);
|
||||
} else {
|
||||
$this->fo->set_path->push(['address'=>$ao,'datetime'=>Carbon::createFromTimestamp($x[8]),'extra'=>$x[9]]);
|
||||
}
|
||||
$this->file->set_path->push($m[2]);
|
||||
|
||||
break;
|
||||
|
||||
case 'seenby':
|
||||
$ao = Address::findFTN($matches[2]);
|
||||
|
||||
if (! $ao) {
|
||||
$this->fo->rogue_seenby->push($matches[2]);
|
||||
} else {
|
||||
$this->fo->set_seenby->push($ao->id);
|
||||
}
|
||||
$this->file->set_seenby->push($m[2]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->fo->kludges->push($line);
|
||||
$this->file->kludges->push($line);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($f);
|
||||
|
||||
$f = fopen($x=Storage::disk('local')->path($this->fo->fullname),'rb');
|
||||
$stat = fstat($f);
|
||||
fclose($f);
|
||||
if ($ldesc)
|
||||
$this->file->ldesc = $ldesc;
|
||||
|
||||
// @todo Check that origin is the first address in the path
|
||||
// @todo Make sure origin/from are in seenby
|
||||
// @todo Make sure origin/from are in the path
|
||||
|
||||
/*
|
||||
* Find our file and check the CRC
|
||||
* If there is more than 1 file, select files that within 24hrs of the TIC file.
|
||||
* If no files report file not found
|
||||
* If there is more than 1 check each CRC to match the right one.
|
||||
* If none match report, CRC error
|
||||
*/
|
||||
$found = FALSE;
|
||||
$crcOK = FALSE;
|
||||
foreach ($fs->files(config('fido.dir')) as $file) {
|
||||
if (abs($x=$fs->lastModified($rel_path_name)-$fs->lastModified($file)) > 86400) {
|
||||
Log::debug(sprintf('%s:/ Ignoring [%s] its mtime is outside of our scope [%d]',self::LOGKEY,$file,$x));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Our file should have the same prefix as the TIC file
|
||||
if (preg_match('#/'.($aid ? $aid.'-' : '').'.*'.$this->file->name.'$#',$file)) {
|
||||
$found = TRUE;
|
||||
|
||||
if (sprintf('%08x',$this->file->crc) === ($y=$fs->checksum($file,['checksum_algo'=>'crc32b']))) {
|
||||
$crcOK = TRUE;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (($found) && (! $crcOK))
|
||||
throw new InvalidCRCException(sprintf('TIC file CRC [%08x] doesnt match file [%s] (%s)',$this->file->crc,$fs->path($rel_path_name),$y));
|
||||
elseif (! $found)
|
||||
throw new FileNotFoundException(sprintf('File not found? [%s...%s] in [%s]',$aid,$this->file->name,$fs->path($rel_path_name)));
|
||||
|
||||
// @todo Add notifications back to the system if the replaces line doesnt match
|
||||
if ($this->file->replaces && (! preg_match('/^'.$this->file->replaces.'$/',$this->file->name))) {
|
||||
Log::alert(sprintf('%s:! Regex [%s] doesnt match file name [%s]',self::LOGKEY,$this->file->replaces,$this->file->name));
|
||||
|
||||
$this->file->replaces = NULL;
|
||||
}
|
||||
|
||||
// @todo Add notification back to the system if no replaces line and the file already exists
|
||||
|
||||
// @todo Add notifictions back to the system
|
||||
// Validate Size
|
||||
if ($this->fo->size !== ($y=$stat['size']))
|
||||
throw new \Exception(sprintf('TIC file size [%d] doesnt match file [%s] (%d)',$this->fo->size,$this->fo->fullname,$y));
|
||||
|
||||
// Validate CRC
|
||||
if (sprintf('%08x',$this->fo->crc) !== ($y=hash_file('crc32b',$x)))
|
||||
throw new \Exception(sprintf('TIC file CRC [%08x] doesnt match file [%s] (%s)',$this->fo->crc,$this->fo->fullname,$y));
|
||||
if ($this->file->size !== ($y=$fs->size($file)))
|
||||
throw new SizeMismatchException(sprintf('TIC file size [%d] doesnt match file [%s] (%d)',$this->file->size,$fs->path($rel_path_name),$y));
|
||||
|
||||
// Validate Password
|
||||
if ($pw !== ($y=$this->from->session('ticpass')))
|
||||
throw new \Exception(sprintf('TIC file PASSWORD [%s] doesnt match system [%s] (%s)',$pw,$this->from->ftn,$y));
|
||||
if (strtoupper($pw) !== ($y=strtoupper($this->file->fftn->session('ticpass'))))
|
||||
throw new InvalidPasswordException(sprintf('TIC file PASSWORD [%s] doesnt match system [%s] (%s)',$pw,$this->file->fftn->ftn,$y));
|
||||
|
||||
// Validate Sender is linked (and permitted to send)
|
||||
if ($this->from->fileareas->search(function($item) { return $item->id === $this->area->id; }) === FALSE)
|
||||
throw new \Exception(sprintf('Node [%s] is not subscribed to [%s]',$this->from->ftn,$this->area->name));
|
||||
// Validate Sender is linked
|
||||
if ($this->file->fftn->fileareas->search(function($item) { return $item->id === $this->file->filearea_id; }) === FALSE)
|
||||
throw new NodeNotSubscribedException(sprintf('Node [%s] is not subscribed to [%s]',$this->file->fftn->ftn,$this->file->filearea->name));
|
||||
|
||||
// If the filearea is to be autocreated, create it
|
||||
if (! $this->area->exists) {
|
||||
$this->area->description = $areadesc;
|
||||
$this->area->active = TRUE;
|
||||
$this->area->show = FALSE;
|
||||
$this->area->notes = 'Autocreated';
|
||||
$this->area->domain_id = $this->from->zone->domain_id;
|
||||
$this->area->save();
|
||||
}
|
||||
|
||||
$this->fo->filearea_id = $this->area->id;
|
||||
$this->fo->fftn_id = $this->origin->id;
|
||||
// Validate sender is permitted to write
|
||||
// @todo Send a notification
|
||||
if (! $this->file->filearea->can_write($this->file->fftn->security))
|
||||
throw new NoWriteSecurityException(sprintf('Node [%s] doesnt have enough security to write to [%s] (%d)',$this->file->fftn->ftn,$this->file->filearea->name,$this->file->fftn->security));
|
||||
|
||||
// If the file create time is blank, we'll take the files
|
||||
if (! $this->fo->datetime)
|
||||
$this->fo->datetime = Carbon::createFromTimestamp($stat['ctime']);
|
||||
if (! $this->file->datetime)
|
||||
$this->file->datetime = Carbon::createFromTimestamp($fs->lastModified($file));
|
||||
|
||||
$this->fo->save();
|
||||
$this->file->src_file = $file;
|
||||
$this->file->recv_tic = $filename;
|
||||
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
return $this->file->save();
|
||||
}
|
||||
|
||||
public function to(Address $ao): self
|
||||
{
|
||||
$this->to = $ao;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@ use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\File\File as FileBase;
|
||||
|
||||
use App\Classes\FTN\Packet;
|
||||
|
||||
class File extends FileBase implements \Iterator
|
||||
{
|
||||
private const LOGKEY = 'F--';
|
||||
@ -102,13 +104,13 @@ class File extends FileBase implements \Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the file, without a node ID prefix
|
||||
* Return the name of the file, without a node ID, mtime prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function rawName(): string
|
||||
{
|
||||
return preg_replace('/^[0-9A-F]{4}-/','',$this->getFilename());
|
||||
return preg_replace(sprintf('/^%s\.pkt$/i',Packet::regex),'\3\4',$this->getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,7 +131,7 @@ class File extends FileBase implements \Iterator
|
||||
return preg_replace('/.pkt$/i','',Arr::get(stream_get_meta_data($f),'uri'));
|
||||
|
||||
} else {
|
||||
return $this->isPacket() ? preg_replace('/.pkt$/i','',$this->rawName()) : NULL;
|
||||
return $this->isPacket() ? $this->rawName() : NULL;
|
||||
}
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ abstract class Base
|
||||
|
||||
// 4 BITS of type
|
||||
protected const IS_FILE = (1<<0);
|
||||
protected const IS_PKT = (1<<1);
|
||||
public const IS_PKT = (1<<1);
|
||||
protected const IS_ARC = (1<<2);
|
||||
protected const IS_REQ = (1<<3);
|
||||
protected const IS_TIC = (1<<4);
|
||||
@ -79,7 +79,7 @@ abstract class Base
|
||||
return ($this->ftype&0xff) & $type;
|
||||
}
|
||||
|
||||
protected function whatType(): int
|
||||
public function whatType(): int
|
||||
{
|
||||
static $ext = ['su','mo','tu','we','th','fr','sa','req'];
|
||||
|
||||
|
@ -86,7 +86,7 @@ final class File extends Send
|
||||
{
|
||||
// If sending file is a File::class, then our file is s3
|
||||
if ($this->nameas && $this->f instanceof FileModel) {
|
||||
$this->fd = Storage::readStream($this->f->full_storage_path);
|
||||
$this->fd = Storage::readStream($this->f->rel_name);
|
||||
|
||||
} else {
|
||||
$this->fd = fopen($this->full_name,'rb');
|
||||
|
@ -2,14 +2,13 @@
|
||||
|
||||
namespace App\Classes\File;
|
||||
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
use App\Models\Address;
|
||||
|
||||
final class Item extends Receive
|
||||
{
|
||||
private const LOCATION = 'local';
|
||||
|
||||
/** @var Address The address that sent us this item */
|
||||
private Address $ao;
|
||||
private string $recvas;
|
||||
@ -34,14 +33,14 @@ final class Item extends Receive
|
||||
public function __get($key) {
|
||||
switch ($key) {
|
||||
case 'exists':
|
||||
return Storage::disk(self::LOCATION)->exists($this->rel_name);
|
||||
return Storage::disk(config('fido.local_disk'))->exists($this->rel_name);
|
||||
|
||||
case 'stor_name':
|
||||
return sprintf('%04X-%s',$this->ao->id,$this->recvas);
|
||||
case 'pref_name':
|
||||
return sprintf('%04X-%d-%s',$this->ao->id,$this->recvmtime,$this->recvas);
|
||||
case 'rel_name':
|
||||
return sprintf('%s/%s',config('app.fido'),$this->stor_name);
|
||||
return sprintf('%s/%s',config('fido.dir'),$this->pref_name);
|
||||
case 'full_name':
|
||||
return Storage::disk(self::LOCATION)->path($this->rel_name);
|
||||
return Storage::disk(config('fido.local_disk'))->path($this->rel_name);
|
||||
|
||||
case 'match_mtime':
|
||||
return $this->mtime === $this->recvmtime;
|
||||
@ -59,10 +58,10 @@ final class Item extends Receive
|
||||
return sprintf('%s %lu %lu',$this->recvas,$this->recvsize,$this->recvmtime);
|
||||
|
||||
case 'mtime':
|
||||
return Storage::disk(self::LOCATION)->lastModified($this->rel_name);
|
||||
return Storage::disk(config('fido.local_disk'))->lastModified($this->rel_name);
|
||||
|
||||
case 'size':
|
||||
return Storage::disk(self::LOCATION)->size($this->rel_name);
|
||||
return Storage::disk(config('fido.local_disk'))->size($this->rel_name);
|
||||
|
||||
default:
|
||||
return parent::__get($key);
|
||||
|
@ -5,14 +5,18 @@ namespace App\Classes\File;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\Node;
|
||||
use App\Classes\FTN\{Message,Packet};
|
||||
|
||||
final class Mail extends Send
|
||||
{
|
||||
private const LOGKEY = 'IFM';
|
||||
|
||||
/** @var int Our internal position counter */
|
||||
private int $readpos;
|
||||
private ?string $content;
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
@ -29,7 +33,7 @@ final class Mail extends Send
|
||||
public function __get($key) {
|
||||
switch ($key) {
|
||||
case 'dbids':
|
||||
return $this->f->messages->pluck('dbid');
|
||||
return $this->f->messages->pluck('id');
|
||||
|
||||
case 'name':
|
||||
return sprintf('%08x',timew($this->youngest()));
|
||||
@ -56,6 +60,8 @@ final class Mail extends Send
|
||||
if ($successful) {
|
||||
$this->complete = TRUE;
|
||||
|
||||
Log::debug(sprintf('%s:- Successful close for [%d] - updating [%d] records.',self::LOGKEY,$this->type,$this->dbids->count()),['dbids'=>$this->dbids,'authd'=>$node->aka_remote_authed->pluck('id')]);
|
||||
|
||||
// Update netmail table
|
||||
if (($this->type === Send::T_NETMAIL)
|
||||
&& ($x=$this->dbids)->count())
|
||||
@ -79,6 +85,8 @@ final class Mail extends Send
|
||||
'sent_at'=>Carbon::now(),
|
||||
'sent_pkt'=>$this->name,
|
||||
]);
|
||||
|
||||
$this->content = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,12 +97,13 @@ final class Mail extends Send
|
||||
|
||||
public function open(string $compress=''): bool
|
||||
{
|
||||
$this->content = (string)$this->f;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function read(int $length): string
|
||||
{
|
||||
$result = substr((string)$this->f,$this->readpos,$length);
|
||||
$result = substr($this->content,$this->readpos,$length);
|
||||
$this->readpos += strlen($result);
|
||||
|
||||
return $result;
|
||||
@ -106,7 +115,7 @@ final class Mail extends Send
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function youngest(): Carbon
|
||||
private function youngest(): Carbon
|
||||
{
|
||||
return $this->f->messages->pluck('date')->sort()->last();
|
||||
}
|
||||
|
@ -5,15 +5,12 @@ namespace App\Classes\File;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
|
||||
use App\Classes\{File,Protocol};
|
||||
use App\Classes\FTN\{InvalidPacketException,Packet};
|
||||
use App\Classes\Protocol;
|
||||
use App\Exceptions\FileGrewException;
|
||||
use App\Jobs\{MessageProcess,TicProcess};
|
||||
use App\Jobs\{PacketProcess,TicProcess};
|
||||
use App\Models\Address;
|
||||
use App\Notifications\Netmails\PacketPasswordInvalid;
|
||||
|
||||
/**
|
||||
* Object representing the files we are receiving
|
||||
@ -37,6 +34,7 @@ class Receive extends Base
|
||||
private ?string $comp;
|
||||
/** @var string|null The compressed data received */
|
||||
private ?string $comp_data;
|
||||
private $queue = FALSE;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -63,7 +61,7 @@ class Receive extends Base
|
||||
case 'nameas':
|
||||
case 'size':
|
||||
case 'name_size_time':
|
||||
case 'stor_name':
|
||||
case 'pref_name':
|
||||
return $this->receiving->{$key};
|
||||
|
||||
case 'pos':
|
||||
@ -117,98 +115,25 @@ class Receive extends Base
|
||||
|
||||
fclose($this->f);
|
||||
// Set our mtime
|
||||
Log::info(sprintf('%s:= Setting file [%s] to time [%s]',self::LOGKEY,$this->receiving->full_name,$this->receiving->recvmtime));
|
||||
Log::debug(sprintf('%s:= Setting file [%s] to time [%s]',self::LOGKEY,$this->receiving->full_name,$this->receiving->recvmtime));
|
||||
touch($this->receiving->full_name,$this->receiving->recvmtime);
|
||||
$this->f = NULL;
|
||||
|
||||
// If we received a packet, we'll dispatch a job to process it, if we got it all
|
||||
if ($this->receiving->complete)
|
||||
switch ($x=$this->receiving->whatType()) {
|
||||
switch ($this->receiving->whatType()) {
|
||||
case self::IS_ARC:
|
||||
case self::IS_PKT:
|
||||
Log::info(sprintf('%s:- Processing mail %s [%s]',self::LOGKEY,$x === self::IS_PKT ? 'PACKET' : 'ARCHIVE',$this->receiving->nameas));
|
||||
|
||||
try {
|
||||
$f = new File($this->receiving->full_name);
|
||||
$processed = FALSE;
|
||||
|
||||
foreach ($f as $packet) {
|
||||
$po = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->system);
|
||||
|
||||
// Check the messages are from the uplink
|
||||
if ($this->ao->system->addresses->search(function($item) use ($po) { return $item->id === $po->fftn_o->id; }) === FALSE) {
|
||||
Log::error(sprintf('%s:! Packet [%s] is not from this link? [%d]',self::LOGKEY,$po->fftn_o->ftn,$this->ao->system_id));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Check the packet password
|
||||
if ($this->ao->session('pktpass') !== $po->password) {
|
||||
Log::error(sprintf('%s:! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$this->ao->ftn,$po->password));
|
||||
|
||||
Notification::route('netmail',$this->ao)->notify(new PacketPasswordInvalid($po->password,$this->receiving->nameas));
|
||||
break;
|
||||
}
|
||||
|
||||
Log::info(sprintf('%s:- Packet has [%d] messages',self::LOGKEY,$po->count()));
|
||||
|
||||
// Queue messages if there are too many in the packet.
|
||||
if ($queue = ($po->count() > config('app.queue_msgs')))
|
||||
Log::info(sprintf('%s:- Messages will be sent to the queue for processing',self::LOGKEY));
|
||||
|
||||
$count = 0;
|
||||
foreach ($po as $msg) {
|
||||
Log::info(sprintf('%s:- Mail from [%s] to [%s]',self::LOGKEY,$msg->fftn,$msg->tftn));
|
||||
|
||||
// @todo Quick check that the packet should be processed by us.
|
||||
// @todo validate that the packet's zone is in the domain.
|
||||
|
||||
/*
|
||||
* // @todo generate exception when echomail for an area that doesnt exist
|
||||
* // @todo generate exception when echomail for an area sender cannot post to
|
||||
* // @todo generate exception when echomail for an area sender not subscribed to
|
||||
* // @todo generate exception when echomail comes from a system not defined here
|
||||
* // @todo generate exception when echomail comes from a system doesnt exist
|
||||
*
|
||||
* // @todo generate exception when netmail to system that doesnt exist (node/point)
|
||||
* // @todo generate exception when netmail from system that doesnt exist (node/point)
|
||||
* // @todo generate warning when netmail comes from a system not defined here
|
||||
*
|
||||
* // @todo generate exception when packet has wrong password
|
||||
*/
|
||||
|
||||
try {
|
||||
// Dispatch job.
|
||||
if ($queue)
|
||||
MessageProcess::dispatch($msg,$f->pktName(),$this->ao,$po->fftn_o,$rcvd_time);
|
||||
else
|
||||
MessageProcess::dispatchSync($msg,$f->pktName(),$this->ao,$po->fftn_o,$rcvd_time);
|
||||
// If packet is greater than a size, lets queue it
|
||||
if ($this->queue || ($this->receiving->size > config('fido.queue_size',0))) {
|
||||
Log::info(sprintf('%s:- Packet [%s] will be sent to the queue for processing because its [%d] size, or queue forced',self::LOGKEY,$this->receiving->full_name,$this->receiving->size));
|
||||
PacketProcess::dispatch($this->receiving->rel_name,$this->ao->zone->domain,FALSE,$rcvd_time);
|
||||
} else
|
||||
PacketProcess::dispatchSync($this->receiving->rel_name,$this->ao->zone->domain,TRUE,$rcvd_time);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s:! Got error dispatching message [%s] (%d:%s-%s).',self::LOGKEY,$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
|
||||
}
|
||||
|
||||
$count++;
|
||||
}
|
||||
|
||||
if ($count === $po->count())
|
||||
$processed = TRUE;
|
||||
}
|
||||
|
||||
if (! $processed) {
|
||||
Log::alert(sprintf('%s:- Not deleting packet [%s], it doesnt seem to be processed?',self::LOGKEY,$this->receiving->nameas));
|
||||
|
||||
// If we want to keep the packet, we could do that logic here
|
||||
} elseif (! config('app.packet_keep')) {
|
||||
Log::debug(sprintf('%s:- Deleting processed packet [%s]',self::LOGKEY,$this->receiving->full_name));
|
||||
unlink($this->receiving->full_name);
|
||||
}
|
||||
|
||||
} catch (InvalidPacketException $e) {
|
||||
Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an InvalidPacketException',self::LOGKEY,$this->receiving->nameas),['e'=>$e->getMessage()]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s:- Not deleting packet [%s], as it generated an uncaught exception',self::LOGKEY,$this->receiving->nameas),['e'=>$e->getMessage()]);
|
||||
Log::error(sprintf('%s:! Got error dispatching packet [%s] (%d:%s-%s).',self::LOGKEY,$this->receiving->rel_name,$e->getLine(),$e->getFile(),$e->getMessage()));
|
||||
}
|
||||
|
||||
break;
|
||||
@ -217,7 +142,7 @@ class Receive extends Base
|
||||
Log::info(sprintf('%s:- Processing TIC file [%s]',self::LOGKEY,$this->receiving->nameas));
|
||||
|
||||
// Queue the tic to be processed later, in case the referenced file hasnt been received yet
|
||||
TicProcess::dispatch($this->receiving->rel_name);
|
||||
TicProcess::dispatch($this->receiving->pref_name)->delay(60);
|
||||
|
||||
break;
|
||||
|
||||
@ -232,11 +157,12 @@ class Receive extends Base
|
||||
/**
|
||||
* Add a new file to receive
|
||||
*
|
||||
* @param array $file
|
||||
* @param Address $ao
|
||||
* @param array $file The name of the receiving file
|
||||
* @param Address $ao Sender sending a file to us
|
||||
* @param bool $queue Force sending received items to the queue
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function new(array $file,Address $ao): void
|
||||
public function new(array $file,Address $ao,$queue=FALSE): void
|
||||
{
|
||||
Log::debug(sprintf('%s:+ Receiving new file [%s]',self::LOGKEY,join('|',$file)));
|
||||
|
||||
@ -244,6 +170,7 @@ class Receive extends Base
|
||||
throw new \Exception('Can only have 1 file receiving at a time');
|
||||
|
||||
$this->ao = $ao;
|
||||
$this->queue = $queue;
|
||||
|
||||
$this->list->push(new Item($ao,Arr::get($file,'name'),(int)Arr::get($file,'mtime'),(int)Arr::get($file,'size')));
|
||||
$this->index = $this->list->count()-1;
|
||||
|
@ -6,6 +6,7 @@ use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Flysystem\UnreadableFileEncountered;
|
||||
|
||||
use App\Classes\File\Send\Dynamic;
|
||||
use App\Classes\Node;
|
||||
use App\Models\Address;
|
||||
|
||||
@ -122,13 +123,38 @@ class Send extends Base
|
||||
|
||||
if ($successful) {
|
||||
$end = time()-$this->start;
|
||||
Log::debug(sprintf('%s: - Closing [%s], sent in [%d] with [%s] items',self::LOGKEY,$this->sending->nameas,$end,$this->sending->dbids->count()));
|
||||
Log::info(sprintf('%s:- Closing [%s], sent in [%d] with [%s] items',self::LOGKEY,$this->sending->nameas,$end,$this->sending->dbids->count()));
|
||||
}
|
||||
|
||||
$this->sending->close($successful,$node);
|
||||
$this->index = NULL;
|
||||
}
|
||||
|
||||
public function dynamic(Address $ao): bool
|
||||
{
|
||||
$file = FALSE;
|
||||
|
||||
// If the node is marked as hold - dont send any files.
|
||||
if ($ao->system->hold) {
|
||||
Log::info(sprintf('%s:- System [%d] is marked as hold - not checking for files.',self::LOGKEY,$ao->system_id));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Files
|
||||
if (($x=$ao->dynamicWaiting())->count()) {
|
||||
Log::debug(sprintf('%s:- [%d] Dynamic Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));
|
||||
|
||||
// Add Files
|
||||
foreach ($x as $do)
|
||||
$this->list->push(new Dynamic($do,$ao,self::T_FILE));
|
||||
|
||||
$file = TRUE;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/*
|
||||
private function compress(string $comp_mode): void
|
||||
{
|
||||
@ -167,7 +193,7 @@ class Send extends Base
|
||||
|
||||
// If the node is marked as hold - dont send any files.
|
||||
if ($ao->system->hold) {
|
||||
Log::info(sprintf('%s: - System [%d] is marked as hold - not checking for files.',self::LOGKEY,$ao->system_id));
|
||||
Log::info(sprintf('%s:- System [%d] is marked as hold - not checking for files.',self::LOGKEY,$ao->system_id));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
@ -202,7 +228,7 @@ class Send extends Base
|
||||
if ((($this->index=$this->list->search(function($item) { return $item->complete === FALSE; })) !== FALSE)
|
||||
&& $this->sending->open())
|
||||
{
|
||||
Log::debug(sprintf('%s:- Sending item [%d] (%s)',self::LOGKEY,$this->index,$this->sending->nameas));
|
||||
Log::info(sprintf('%s:- Sending item [%d] (%s)',self::LOGKEY,$this->index,$this->sending->nameas));
|
||||
|
||||
$this->pos = 0;
|
||||
$this->start = time();
|
||||
@ -223,32 +249,31 @@ class Send extends Base
|
||||
* Add our mail to the send queue
|
||||
*
|
||||
* @param Address $ao
|
||||
* @param bool $update
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function mail(Address $ao,bool $update=TRUE): bool
|
||||
public function mail(Address $ao): bool
|
||||
{
|
||||
$mail = FALSE;
|
||||
|
||||
// If the node is marked as hold - dont send any mail.
|
||||
if ($ao->system->hold) {
|
||||
Log::info(sprintf('%s: - System [%d] is marked as hold - not checking for mail.',self::LOGKEY,$ao->system_id));
|
||||
Log::info(sprintf('%s:- System [%d] is marked as hold - not checking for mail.',self::LOGKEY,$ao->system_id));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Netmail
|
||||
if ($x=$ao->getNetmail($update)) {
|
||||
Log::debug(sprintf('%s: - Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
|
||||
if ($x=$ao->getNetmail()) {
|
||||
Log::debug(sprintf('%s:- Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
|
||||
|
||||
$this->list->push(new Mail($x,self::T_NETMAIL));
|
||||
$mail = TRUE;
|
||||
}
|
||||
|
||||
// Echomail
|
||||
if ($x=$ao->getEchomail($update)) {
|
||||
Log::debug(sprintf('%s: - Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
|
||||
if ($x=$ao->getEchomail()) {
|
||||
Log::debug(sprintf('%s:- Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
|
||||
|
||||
$this->list->push(new Mail($x,self::T_ECHOMAIL));
|
||||
$mail = TRUE;
|
||||
|
124
app/Classes/File/Send/Dynamic.php
Normal file
124
app/Classes/File/Send/Dynamic.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\File\Send;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\Classes\File\Send;
|
||||
use App\Classes\Node;
|
||||
use App\Models\Address;
|
||||
use App\Models\Dynamic as Model;
|
||||
use App\Classes\Dynamic as Item;
|
||||
|
||||
/**
|
||||
* Dynamic files that are sent to systems during a mailer session
|
||||
*/
|
||||
final class Dynamic extends Send
|
||||
{
|
||||
private const LOGKEY = 'FSD';
|
||||
|
||||
/** @var int Our internal position counter */
|
||||
private int $readpos = 0;
|
||||
private string $buffer;
|
||||
private Item $item;
|
||||
private Carbon $sent;
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(private Model $do,Address $ao,int $type)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->ftype = ((($type&0xff)<<8)|self::IS_FILE);
|
||||
$this->item = new $this->do->model($ao,$this->do->arguments);
|
||||
$this->sent = Carbon::now();
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
switch ($key) {
|
||||
case 'dbids':
|
||||
return collect([$this->do->id]);
|
||||
|
||||
case 'nameas':
|
||||
return $this->item->getName();
|
||||
|
||||
case 'mtime':
|
||||
return $this->sent->timestamp;
|
||||
|
||||
case 'size':
|
||||
return strlen($this->buffer);
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public function close(bool $successful,Node $node): void
|
||||
{
|
||||
if ($successful) {
|
||||
$this->complete = TRUE;
|
||||
|
||||
$next_at = $this->do->next_at
|
||||
->startOfDay()
|
||||
->addHours($this->do->start_time->hour)
|
||||
->addMinutes($this->do->start_time->minute);
|
||||
|
||||
switch ($this->do->frequency) {
|
||||
case 'ONCE':
|
||||
$this->do->active = FALSE;
|
||||
|
||||
break;
|
||||
|
||||
case 'DAILY':
|
||||
$this->do->next_at = $next_at
|
||||
->addDay();
|
||||
|
||||
break;
|
||||
|
||||
case 'WEEKLY':
|
||||
$this->do->next_at = $next_at
|
||||
->addWeek();
|
||||
|
||||
break;
|
||||
|
||||
case 'MONTHLY':
|
||||
$this->do->next_at = $next_at
|
||||
->addMonth();
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \Exception(sprintf('%s:! Unknown frequency [%s] for [%d]',self::LOGKEY,$this->do->frequency,$this->do->id));
|
||||
}
|
||||
|
||||
$this->do->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function feof(): bool
|
||||
{
|
||||
return ($this->readpos === $this->size);
|
||||
}
|
||||
|
||||
public function open(string $compress=''): bool
|
||||
{
|
||||
$this->buffer = (string)$this->item;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function read(int $length): string
|
||||
{
|
||||
$result = substr($this->buffer,$this->readpos,$length);
|
||||
$this->readpos += strlen($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function seek(int $pos): bool
|
||||
{
|
||||
$this->readpos = ($pos < $this->size) ? $pos : $this->size;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ final class Tic extends Send
|
||||
$this->ftype = ((($type&0xff)<<8)|self::IS_TIC);
|
||||
$this->readpos = 0;
|
||||
|
||||
$this->tic = FTNTic::generate($ao,$file);
|
||||
$this->tic = (string)(new FTNTic($file))->to($ao);
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
|
@ -92,7 +92,7 @@ class Node
|
||||
|
||||
// Return how long our session has been connected
|
||||
case 'session_time':
|
||||
return Carbon::now()->diffInSeconds($this->start_time);
|
||||
return sprintf("%d",$this->start_time->diffInSeconds(Carbon::now()));
|
||||
|
||||
case 'system':
|
||||
case 'sysop':
|
||||
|
@ -186,6 +186,8 @@ class Page
|
||||
$this->text .= $text;
|
||||
|
||||
$this->text_right = $right;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,7 +9,9 @@ use App\Classes\File\{Receive,Send};
|
||||
use App\Classes\Protocol\EMSI;
|
||||
use App\Classes\Sock\SocketClient;
|
||||
use App\Classes\Sock\SocketException;
|
||||
use App\Models\{Address,Setup,System,SystemLog};
|
||||
use App\Models\{Address,Mailer,Setup,System,SystemLog};
|
||||
|
||||
// @todo after receiving a mail packet/file, dont acknowledge it until we can validate that we can read it properly.
|
||||
|
||||
abstract class Protocol
|
||||
{
|
||||
@ -26,14 +28,6 @@ abstract class Protocol
|
||||
protected const RCDO = -3;
|
||||
protected const ERROR = -5;
|
||||
|
||||
// Our sessions Types
|
||||
public const SESSION_AUTO = 0;
|
||||
/** @deprecate Use mailers:class */
|
||||
public const SESSION_EMSI = 1;
|
||||
/** @deprecate Use mailers:class */
|
||||
public const SESSION_BINKP = 2;
|
||||
public const SESSION_ZMODEM = 3;
|
||||
|
||||
protected const MAX_PATH = 1024;
|
||||
|
||||
/* O_ options - [First 9 bits are protocol P_* (Interfaces/ZModem)] */
|
||||
@ -132,9 +126,11 @@ abstract class Protocol
|
||||
|
||||
private array $comms;
|
||||
|
||||
protected bool $force_queue = FALSE;
|
||||
|
||||
abstract protected function protocol_init(): int;
|
||||
|
||||
abstract protected function protocol_session(): int;
|
||||
abstract protected function protocol_session(bool $force_queue=FALSE): int;
|
||||
|
||||
public function __construct(Setup $o=NULL)
|
||||
{
|
||||
@ -174,6 +170,10 @@ abstract class Protocol
|
||||
$this->comms[$key] = $value;
|
||||
break;
|
||||
|
||||
case 'client':
|
||||
$this->{$key} = $value;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \Exception('Unknown key: '.$key);
|
||||
}
|
||||
@ -309,14 +309,14 @@ abstract class Protocol
|
||||
$addresses = collect();
|
||||
|
||||
foreach (($this->originate ? $this->node->aka_remote_authed : $this->node->aka_remote) as $ao)
|
||||
$addresses = $addresses->merge($this->setup->system->match($ao->zone,Address::NODE_ZC|Address::NODE_RC|Address::NODE_NC|Address::NODE_HC|Address::NODE_ACTIVE|Address::NODE_PVT|Address::NODE_POINT));
|
||||
$addresses = $addresses->merge(our_address($ao->zone->domain));
|
||||
|
||||
$addresses = $addresses->unique();
|
||||
|
||||
Log::debug(sprintf('%s:- Presenting limited AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
|
||||
|
||||
} else {
|
||||
$addresses = $this->setup->system->addresses;
|
||||
$addresses = $this->setup->system->akas;
|
||||
|
||||
Log::debug(sprintf('%s:- Presenting ALL our AKAs [%s]',self::LOGKEY,$addresses->pluck('ftn')->join(',')));
|
||||
}
|
||||
@ -327,13 +327,13 @@ abstract class Protocol
|
||||
/**
|
||||
* Initialise our Session
|
||||
*
|
||||
* @param int $type
|
||||
* @param Mailer $mo
|
||||
* @param SocketClient $client
|
||||
* @param Address|null $o
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function session(int $type,SocketClient $client,Address $o=NULL): int
|
||||
public function session(Mailer $mo,SocketClient $client,Address $o=NULL): int
|
||||
{
|
||||
if ($o->exists)
|
||||
Log::withContext(['ftn'=>$o->ftn]);
|
||||
@ -365,12 +365,11 @@ abstract class Protocol
|
||||
|
||||
// We are an IP node
|
||||
$this->optionSet(self::O_TCP);
|
||||
$this->setClient($client);
|
||||
$this->client = $client;
|
||||
|
||||
switch ($type) {
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case self::SESSION_AUTO:
|
||||
Log::debug(sprintf('%s:- Trying EMSI',self::LOGKEY));
|
||||
switch ($mo->name) {
|
||||
case 'EMSI':
|
||||
Log::debug(sprintf('%s:- Starting EMSI',self::LOGKEY));
|
||||
|
||||
$rc = $this->protocol_init();
|
||||
if ($rc < 0) {
|
||||
@ -379,27 +378,19 @@ abstract class Protocol
|
||||
return self::S_FAILURE;
|
||||
}
|
||||
|
||||
case self::SESSION_EMSI:
|
||||
Log::debug(sprintf('%s:- Starting EMSI',self::LOGKEY));
|
||||
$rc = $this->protocol_session();
|
||||
$rc = $this->protocol_session($this->originate);
|
||||
|
||||
break;
|
||||
|
||||
case self::SESSION_BINKP:
|
||||
case 'BINKP':
|
||||
Log::debug(sprintf('%s:- Starting BINKP',self::LOGKEY));
|
||||
$rc = $this->protocol_session();
|
||||
|
||||
$rc = $this->protocol_session($this->originate);
|
||||
|
||||
break;
|
||||
|
||||
case self::SESSION_ZMODEM:
|
||||
Log::debug(sprintf('%s:- Starting ZMODEM',self::LOGKEY));
|
||||
$this->client->speed = self::TCP_SPEED;
|
||||
$this->originate = FALSE;
|
||||
|
||||
return $this->protocol_session();
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s:! Unsupported session type [%d]',self::LOGKEY,$type));
|
||||
Log::error(sprintf('%s:! Unsupported session type [%d]',self::LOGKEY,$mo->id));
|
||||
|
||||
return self::S_FAILURE;
|
||||
}
|
||||
@ -429,15 +420,16 @@ abstract class Protocol
|
||||
));
|
||||
|
||||
// Add unknown FTNs to the DB
|
||||
if ($this->node->aka_remote_authed->count()) {
|
||||
$so = $this->node->aka_remote_authed->first()->system;
|
||||
} else {
|
||||
$so = System::where('name','Discovered System')->single();
|
||||
}
|
||||
$so = ($this->node->aka_remote_authed->count())
|
||||
? $this->node->aka_remote_authed->first()->system
|
||||
: System::createUnknownSystem();
|
||||
|
||||
if ($so && $so->exists) {
|
||||
foreach ($this->node->aka_other as $aka) {
|
||||
Address::findFTN($aka,TRUE,$so);
|
||||
foreach ($this->node->aka_other as $aka)
|
||||
if (! Address::findFTN($aka)) {
|
||||
$oo = Address::createFTN($aka,$so);
|
||||
$oo->validated = TRUE;
|
||||
$oo->save();
|
||||
}
|
||||
|
||||
// Log session in DB
|
||||
@ -446,12 +438,18 @@ abstract class Protocol
|
||||
$slo->items_sent_size = $this->send->total_sent_bytes;
|
||||
$slo->items_recv = $this->recv->total_recv;
|
||||
$slo->items_recv_size = $this->recv->total_recv_bytes;
|
||||
$slo->sessiontype = $type;
|
||||
$slo->mailer_id = $mo->id;
|
||||
$slo->sessiontime = $this->node->session_time;
|
||||
$slo->result = ($rc & self::S_MASK);
|
||||
$slo->originate = $this->originate;
|
||||
|
||||
$so->logs()->save($slo);
|
||||
|
||||
// If we are autohold, then remove that
|
||||
if ($so->autohold) {
|
||||
$so->autohold = FALSE;
|
||||
$so->save();
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Optional after session execution event
|
||||
@ -495,15 +493,4 @@ abstract class Protocol
|
||||
{
|
||||
$this->session |= $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set our client that we are communicating with
|
||||
*
|
||||
* @param SocketClient $client
|
||||
* @deprecated use __get()/__set()
|
||||
*/
|
||||
protected function setClient(SocketClient $client): void
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
}
|
@ -9,11 +9,12 @@ use Illuminate\Support\Facades\Log;
|
||||
use League\Flysystem\UnreadableFileEncountered;
|
||||
|
||||
use App\Classes\Crypt;
|
||||
use App\Classes\Node;
|
||||
use App\Classes\Protocol as BaseProtocol;
|
||||
use App\Classes\Sock\SocketClient;
|
||||
use App\Classes\Sock\SocketException;
|
||||
use App\Exceptions\FileGrewException;
|
||||
use App\Models\Address;
|
||||
use App\Exceptions\{FileGrewException,InvalidFTNException};
|
||||
use App\Models\{Address,Mailer};
|
||||
|
||||
final class Binkp extends BaseProtocol
|
||||
{
|
||||
@ -147,7 +148,6 @@ final class Binkp extends BaseProtocol
|
||||
* @param SocketClient $client
|
||||
* @return int|null
|
||||
* @throws SocketException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function onConnect(SocketClient $client): ?int
|
||||
{
|
||||
@ -155,8 +155,7 @@ final class Binkp extends BaseProtocol
|
||||
if (! parent::onConnect($client)) {
|
||||
Log::withContext(['pid'=>getmypid()]);
|
||||
|
||||
// @todo If we can use SESSION_EMSI set an object class value that in BINKP of SESSION_BINKP, and move this method to the parent class
|
||||
$this->session(self::SESSION_BINKP,$client,(new Address));
|
||||
$this->session(Mailer::where('name','BINKP')->singleOrFail(),$client,(new Address));
|
||||
$this->client->close();
|
||||
exit(0);
|
||||
}
|
||||
@ -431,17 +430,23 @@ final class Binkp extends BaseProtocol
|
||||
$rc = TRUE;
|
||||
|
||||
} else {
|
||||
$data = substr($this->rx_buf,1);
|
||||
// http://ftsc.org/docs/fts-1026.001 - frames may be NULL terminated
|
||||
$data = rtrim(substr($this->rx_buf,1),"\x00");
|
||||
|
||||
switch ($msg) {
|
||||
case self::BPM_ADR:
|
||||
Log::debug(sprintf('%s:- ADR:Address [%s]',self::LOGKEY,$data));
|
||||
$rc = $this->M_adr($data);
|
||||
// @note It seems taurus may pad data with nulls at the end (esp BPM_ADR), so we should trim that.
|
||||
$rc = $this->M_adr(trim($data));
|
||||
break;
|
||||
|
||||
case self::BPM_EOB:
|
||||
Log::debug(sprintf('%s:- EOB:We got an EOB message with [%d] chars in the buffer',self::LOGKEY,strlen($data)));
|
||||
$rc = $this->M_eob($data);
|
||||
|
||||
if (strlen($data))
|
||||
Log::critical(sprintf('%s:! EOB but we have data?',self::LOGKEY),['data'=>$data]);
|
||||
|
||||
$rc = $this->M_eob();
|
||||
break;
|
||||
|
||||
case self::BPM_NUL:
|
||||
@ -451,7 +456,7 @@ final class Binkp extends BaseProtocol
|
||||
|
||||
case self::BPM_PWD:
|
||||
Log::debug(sprintf('%s:- PWD:We got a password [%s]',self::LOGKEY,$data));
|
||||
$rc = $this->M_pwd($data);
|
||||
$rc = $this->M_pwd(ltrim($data));
|
||||
break;
|
||||
|
||||
case self::BPM_ERR:
|
||||
@ -476,7 +481,7 @@ final class Binkp extends BaseProtocol
|
||||
|
||||
case self::BPM_OK:
|
||||
Log::debug(sprintf('%s:- OK:Got an OK [%s]',self::LOGKEY,$data));
|
||||
$rc = $this->M_ok($data);
|
||||
$rc = $this->M_ok(ltrim($data));
|
||||
break;
|
||||
|
||||
case self::BPM_CHAT:
|
||||
@ -680,26 +685,44 @@ final class Binkp extends BaseProtocol
|
||||
*/
|
||||
private function M_adr(string $buf): bool
|
||||
{
|
||||
$buf = $this->skip_blanks($buf);
|
||||
$rc = 0;
|
||||
|
||||
while ($rem_aka=$this->strsep($buf,' ')) {
|
||||
try {
|
||||
if (! ($o=Address::findFTN($rem_aka,FALSE,NULL,TRUE))) {
|
||||
if (! ($o=Address::findFTN($rem_aka,TRUE))) {
|
||||
// @todo when we have multiple inactive records, this returns more than 1, so pluck the active record if there is one
|
||||
Log::alert(sprintf('%s:? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka));
|
||||
|
||||
$this->node->ftn_other = $rem_aka;
|
||||
continue;
|
||||
|
||||
// If we only present limited AKAs dont validate password against akas outside of the domains we present
|
||||
} elseif (is_null(our_address($o))) {
|
||||
Log::alert(sprintf('%s:/ AKA domain [%s] is not in our domain(s) [%s] - ignoring',self::LOGKEY,$o->zone->domain->name,our_address()->pluck('zone.domain.name')->unique()->join(',')));
|
||||
|
||||
$this->node->ftn_other = $rem_aka;
|
||||
continue;
|
||||
|
||||
} elseif (! $o->active) {
|
||||
Log::alert(sprintf('%s:/ AKA is not active [%s], ignoring',self::LOGKEY,$rem_aka));
|
||||
Log::alert(sprintf('%s:/ AKA is not active [%s] - ignoring',self::LOGKEY,$rem_aka));
|
||||
|
||||
$this->node->ftn_other = $rem_aka;
|
||||
continue;
|
||||
|
||||
} else {
|
||||
Log::info(sprintf('%s:- Got AKA [%s]',self::LOGKEY,$rem_aka));
|
||||
|
||||
// We'll update this address status
|
||||
$o->validated = TRUE;
|
||||
$o->role &= ~(Address::NODE_HOLD|Address::NODE_DOWN);
|
||||
$o->save();
|
||||
}
|
||||
|
||||
} catch (InvalidFTNException $e) {
|
||||
Log::error(sprintf('%s:! AKA is INVALID [%s] (%s) - ignoring',self::LOGKEY,$rem_aka,$e->getMessage()));
|
||||
|
||||
continue;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s:! AKA is INVALID [%s] (%d:%s-%s)',self::LOGKEY,$rem_aka,$e->getLine(),$e->getFile(),$e->getMessage()));
|
||||
|
||||
@ -744,18 +767,14 @@ final class Binkp extends BaseProtocol
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add our mail to the queue if we have authenticated
|
||||
if ($this->node->aka_authed)
|
||||
foreach ($this->node->aka_remote_authed as $ao) {
|
||||
if (! $ao->validated) {
|
||||
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->send->mail($ao);
|
||||
$this->send->files($ao);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://ftsc.org/docs/fts-1026.001
|
||||
* M_NUL "TRF netmail_bytes arcmail_bytes"
|
||||
* traffic prognosis (in bytes) for the netmail
|
||||
* (netmail_bytes) and arcmail + files (arcmail_bytes),
|
||||
* both are decimal ASCII strings
|
||||
*/
|
||||
// @todo This is affectively redundant, because we are not determining our mail until later
|
||||
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->files_size));
|
||||
|
||||
if ($this->md_challenge) {
|
||||
@ -805,7 +824,7 @@ final class Binkp extends BaseProtocol
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function M_eob(string $buf): bool
|
||||
private function M_eob(): bool
|
||||
{
|
||||
if ($this->recv->fd) {
|
||||
Log::info(sprintf('%s:= Closing receiving file.',self::LOGKEY));
|
||||
@ -817,21 +836,8 @@ final class Binkp extends BaseProtocol
|
||||
$this->sessionClear(self::SE_DELAYEOB);
|
||||
|
||||
if (! $this->send->togo_count && $this->sessionGet(self::SE_NOFILES) && $this->capGet(self::F_MULTIBATCH,self::O_YES)) {
|
||||
// Add our mail to the queue if we have authenticated
|
||||
if ($this->node->aka_authed)
|
||||
foreach ($this->node->aka_remote_authed as $ao) {
|
||||
Log::debug(sprintf('%s:- Checking for any new mail and files to [%s]',self::LOGKEY,$ao->ftn));
|
||||
$this->getFiles($this->node);
|
||||
|
||||
if (! $ao->validated) {
|
||||
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->send->mail($ao);
|
||||
$this->send->files($ao);
|
||||
}
|
||||
|
||||
Log::debug(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->togo_count,$ao->ftn));
|
||||
if ($this->send->togo_count)
|
||||
$this->sessionClear(self::SE_NOFILES|self::SE_SENTEOB);
|
||||
}
|
||||
@ -892,7 +898,7 @@ final class Binkp extends BaseProtocol
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$this->recv->new($file['file'],$this->node->address);
|
||||
$this->recv->new($file['file'],$this->node->address,$this->force_queue);
|
||||
|
||||
try {
|
||||
switch ($this->recv->open($file['offs']<0,$file['flags'])) {
|
||||
@ -1012,6 +1018,9 @@ final class Binkp extends BaseProtocol
|
||||
|
||||
$this->send->close(TRUE,$this->node);
|
||||
}
|
||||
|
||||
} else {
|
||||
Log::error(sprintf('%s:! M_got[skip] not for our file? [%s]',self::LOGKEY,$buf));
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -1029,16 +1038,16 @@ final class Binkp extends BaseProtocol
|
||||
Log::info(sprintf('%s:+ M_NUL [%s]',self::LOGKEY,$buf));
|
||||
|
||||
if (! strncmp($buf,'SYS ',4)) {
|
||||
$this->node->system = $this->skip_blanks(substr($buf,4));
|
||||
$this->node->system = ltrim(substr($buf,4));
|
||||
|
||||
} elseif (! strncmp($buf, 'ZYZ ',4)) {
|
||||
$this->node->sysop = $this->skip_blanks(substr($buf,4));
|
||||
$this->node->sysop = ltrim(substr($buf,4));
|
||||
|
||||
} elseif (! strncmp($buf,'LOC ',4)) {
|
||||
$this->node->location = $this->skip_blanks(substr($buf,4));
|
||||
$this->node->location = ltrim(substr($buf,4));
|
||||
|
||||
} elseif (! strncmp($buf,'NDL ',4)) {
|
||||
$data = $this->skip_blanks(substr($buf,4));
|
||||
$data = ltrim(substr($buf,4));
|
||||
$comma = strpos($data,',');
|
||||
$spd = substr($data,0,$comma);
|
||||
|
||||
@ -1049,7 +1058,7 @@ final class Binkp extends BaseProtocol
|
||||
$this->client->speed = $spd;
|
||||
|
||||
} else {
|
||||
$comma = $this->skip_blanks(substr($buf,4));
|
||||
$comma = ltrim(substr($buf,4));
|
||||
$c = 0;
|
||||
while (($x=substr($comma,$c,1)) && is_numeric($x))
|
||||
$c++;
|
||||
@ -1071,19 +1080,25 @@ final class Binkp extends BaseProtocol
|
||||
}
|
||||
|
||||
} elseif (! strncmp($buf,'TIME ',5)) {
|
||||
$this->node->node_time = $this->skip_blanks(substr($buf,5));
|
||||
$this->node->node_time = ltrim(substr($buf,5));
|
||||
|
||||
} elseif (! strncmp($buf,'VER ',4)) {
|
||||
$data = $this->skip_blanks(substr($buf,4));
|
||||
$data = ltrim(substr($buf,4));
|
||||
$matches = [];
|
||||
preg_match('#^(.+)\s+binkp/([0-9]+)\.([0-9]+)$#',$data,$matches);
|
||||
preg_match('#^(.+)\s+\(?binkp/([0-9]+)\.([0-9]+)\)?$#',$data,$matches);
|
||||
|
||||
if (count($matches) === 4) {
|
||||
$this->node->software = $matches[1];
|
||||
$this->node->ver_major = $matches[2];
|
||||
$this->node->ver_minor = $matches[3];
|
||||
} else {
|
||||
$this->node->software = 'Unknown';
|
||||
$this->node->ver_major = 0;
|
||||
$this->node->ver_minor = 0;
|
||||
}
|
||||
|
||||
} elseif (! strncmp($buf,'TRF ',4)) {
|
||||
$data = $this->skip_blanks(substr($buf,4));
|
||||
$data = ltrim(substr($buf,4));
|
||||
$matches = [];
|
||||
preg_match('/^([0-9]+)\s+([0-9]+)$/',$data,$matches);
|
||||
|
||||
@ -1097,13 +1112,13 @@ final class Binkp extends BaseProtocol
|
||||
$this->sessionSet(self::SE_DELAYEOB);
|
||||
|
||||
} elseif (! strncmp($buf,'PHN ',4)) {
|
||||
$this->node->phone = $this->skip_blanks(substr($buf,4));
|
||||
$this->node->phone = ltrim(substr($buf,4));
|
||||
|
||||
} elseif (! strncmp($buf,'OPM ',4)) {
|
||||
$this->node->message = $this->skip_blanks(substr($buf,4));
|
||||
$this->node->message = ltrim(substr($buf,4));
|
||||
|
||||
} elseif (! strncmp($buf,'OPT ',4)) {
|
||||
$data = $this->skip_blanks(substr($buf,4));
|
||||
$data = ltrim(substr($buf,4));
|
||||
|
||||
while ($data && ($p = $this->strsep($data,' '))) {
|
||||
if (! strcmp($p,'MB')) {
|
||||
@ -1142,12 +1157,15 @@ final class Binkp extends BaseProtocol
|
||||
$this->capSet(self::F_COMP,self::O_THEY|self::O_EXT);
|
||||
|
||||
} elseif (! strncmp($p,'CRAM-MD5-',9) && $this->originate && $this->capGet(self::F_MD,self::O_WANT)) {
|
||||
if (($x=strlen(substr($p,9))) > 64 ) {
|
||||
Log::error(sprintf('%s:! The challenge string is TOO LONG [%d] (%s)',self::LOGKEY,$x,$p));
|
||||
if (strlen($hex=substr($p,9)) > 64 ) {
|
||||
Log::error(sprintf('%s:! The challenge string is TOO LONG [%d] (%s)',self::LOGKEY,strlen($hex),$p));
|
||||
|
||||
} elseif (strlen($hex)%2) {
|
||||
Log::error(sprintf('%s:! The challenge string is an odd size [%d] (%s)',self::LOGKEY,strlen($hex),$hex));
|
||||
|
||||
} else {
|
||||
Log::info(sprintf('%s:- Remote wants MD5 auth',self::LOGKEY));
|
||||
$this->md_challenge = hex2bin(substr($p,9));
|
||||
Log::info(sprintf('%s:- Remote wants MD5 auth with [%s]',self::LOGKEY,$hex));
|
||||
$this->md_challenge = hex2bin($hex);
|
||||
|
||||
if ($this->md_challenge)
|
||||
$this->capSet(self::F_MD,self::O_THEY);
|
||||
@ -1184,8 +1202,6 @@ final class Binkp extends BaseProtocol
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$buf = $this->skip_blanks($buf);
|
||||
|
||||
if ($this->optionGet(self::O_PWD) && $buf) {
|
||||
while (($t=$this->strsep($buf," \t")))
|
||||
if (strcmp($t,'non-secure') === 0) {
|
||||
@ -1208,11 +1224,10 @@ final class Binkp extends BaseProtocol
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @todo It appears when we poll a node, we dont ask for passwords, but we still send echomail and files.
|
||||
*/
|
||||
private function M_pwd(string $buf): bool
|
||||
{
|
||||
$buf = $this->skip_blanks($buf);
|
||||
$have_CRAM = !strncasecmp($buf,'CRAM-MD5-',9);
|
||||
$have_pwd = $this->optionGet(self::O_PWD);
|
||||
|
||||
@ -1294,24 +1309,13 @@ final class Binkp extends BaseProtocol
|
||||
if (strlen($opt))
|
||||
$this->msgs(self::BPM_NUL,sprintf('OPT%s',$opt));
|
||||
|
||||
// Add our mail to the queue if we have authenticated
|
||||
if ($this->node->aka_authed) {
|
||||
foreach ($this->node->aka_remote_authed as $ao) {
|
||||
if (! $ao->validated) {
|
||||
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->send->mail($ao);
|
||||
$this->send->files($ao);
|
||||
}
|
||||
|
||||
// @todo This is effectively redundant, because we are not getting files until later
|
||||
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->files_size));
|
||||
|
||||
if ($this->node->aka_authed) {
|
||||
$this->msgs(self::BPM_OK,sprintf('%ssecure',$have_pwd ? '' : 'non-'));
|
||||
|
||||
} else {
|
||||
// @todo Send any direct netmail to this node, if that node is unknown to us
|
||||
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->files_size));
|
||||
$this->msgs(self::OK,'non-secure');
|
||||
}
|
||||
|
||||
@ -1327,14 +1331,16 @@ final class Binkp extends BaseProtocol
|
||||
/**
|
||||
* Set up our BINKP session
|
||||
*
|
||||
* @param bool $force_queue
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function protocol_session(): int
|
||||
protected function protocol_session(bool $force_queue=FALSE): int
|
||||
{
|
||||
if ($this->binkp_init() !== self::OK)
|
||||
return self::S_FAILURE;
|
||||
|
||||
$this->force_queue = $force_queue;
|
||||
$this->binkp_hs();
|
||||
|
||||
while (TRUE) {
|
||||
@ -1344,9 +1350,12 @@ final class Binkp extends BaseProtocol
|
||||
&& (! $this->sessionGet(self::SE_NOFILES))
|
||||
&& (! $this->send->fd))
|
||||
{
|
||||
if (! $this->send->togo_count)
|
||||
$this->getFiles($this->node);
|
||||
|
||||
// Open our next file to send
|
||||
if ($this->send->togo_count && ! $this->send->fd) {
|
||||
Log::info(sprintf('%s:- Opening next file to send',self::LOGKEY));
|
||||
Log::info(sprintf('%s:- Opening next file to send - we have [%d] left',self::LOGKEY,$this->send->togo_count));
|
||||
$this->send->open();
|
||||
}
|
||||
|
||||
@ -1374,6 +1383,8 @@ final class Binkp extends BaseProtocol
|
||||
// We dont have anything to send
|
||||
} else {
|
||||
Log::info(sprintf('%s:- Nothing left to send in this batch',self::LOGKEY));
|
||||
// @todo We should look for more mail/files before thinking about sending an EOB
|
||||
// IE: When we are set to only send X messages, but we have > X to send, get the next batch.
|
||||
$this->sessionSet(self::SE_NOFILES);
|
||||
}
|
||||
}
|
||||
@ -1478,13 +1489,50 @@ final class Binkp extends BaseProtocol
|
||||
return $this->rc;
|
||||
}
|
||||
|
||||
public function getFiles(Node $node): void
|
||||
{
|
||||
// Add our mail to the queue if we have authenticated
|
||||
if ($node->aka_authed) {
|
||||
Log::info(sprintf('%s:- We have authed these AKAs [%s]',self::LOGKEY,$node->aka_remote_authed->pluck('ftn')->join(',')));
|
||||
|
||||
foreach ($node->aka_remote_authed as $ao) {
|
||||
Log::debug(sprintf('%s:- Checking for any new mail and files to [%s]',self::LOGKEY,$ao->ftn));
|
||||
|
||||
if (! $ao->validated) {
|
||||
Log::alert(sprintf('%s:! Address [%s] is not validated, so we wont bundle mail for it',self::LOGKEY,$ao->ftn));
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->send->mail($ao);
|
||||
$this->send->files($ao);
|
||||
$this->send->dynamic($ao);
|
||||
|
||||
/*
|
||||
* Add "dynamic files", eg: nodelist, nodelist segment, status reports.
|
||||
* Dynamic files are built on the fly
|
||||
* * query "dynamic" for items for the address
|
||||
* * column 'method' identifies the method that will be called, with the $ao as the argument
|
||||
* * a 'new Item' is added to the queue
|
||||
* * when it its ready to be sent, the __tostring() is called that renders it
|
||||
* * when sent, the dynamic table is updated with the sent_at
|
||||
*/
|
||||
}
|
||||
|
||||
Log::info(sprintf('%s:- We have [%d] items to send to [%s]',self::LOGKEY,$this->send->togo_count,$ao->system->name));
|
||||
|
||||
} else {
|
||||
// @todo We should only send netmail if unauthenticated - netmail that is direct to this node (no routing)
|
||||
Log::debug(sprintf('%s:- Not AUTHed so not looking for mail, but we know these akas [%s]',self::LOGKEY,$node->aka_remote->pluck('ftn')->join(',')));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip blanks at the beginning of a string
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
* @todo Can this be replaced with ltrim?
|
||||
* @deprecated - use ltrim instead
|
||||
*/
|
||||
private function skip_blanks(string $str): string
|
||||
{
|
||||
@ -1524,6 +1572,7 @@ final class Binkp extends BaseProtocol
|
||||
* @param string $str
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
* @deprecated No longer required since we are using ltrim
|
||||
*/
|
||||
private function isSpace(string $str):bool
|
||||
{
|
||||
|
@ -7,11 +7,11 @@ use Illuminate\Support\Str;
|
||||
|
||||
use App\Classes\Protocol as BaseProtocol;
|
||||
use App\Classes\Sock\SocketClient;
|
||||
use App\Http\Controllers\DomainController;
|
||||
use App\Models\{Address,Domain};
|
||||
use App\Models\{Address,Domain,Mailer};
|
||||
|
||||
/**
|
||||
* Respond to DNS queries and provide addresses to FTN nodes.
|
||||
* http://ftsc.org/docs/fts-5004.001
|
||||
*
|
||||
* This implementation doesnt support EDNS nor DNSSEC.
|
||||
*
|
||||
@ -59,8 +59,8 @@ final class DNS extends BaseProtocol
|
||||
public const DNS_TYPE_SOA = 6; // SOA Records
|
||||
public const DNS_TYPE_MX = 15; // MX Records
|
||||
public const DNS_TYPE_TXT = 16; // TXT Records
|
||||
|
||||
public const DNS_TYPE_AAAA = 28; // AAAA Records
|
||||
public const DNS_TYPE_SRV = 33; // SRV Records
|
||||
public const DNS_TYPE_OPT = 41; // OPT Records
|
||||
public const DNS_TYPE_DS = 43; // DS Records (Delegation signer RFC 4034)
|
||||
|
||||
@ -70,7 +70,7 @@ final class DNS extends BaseProtocol
|
||||
if (! parent::onConnect($client)) {
|
||||
Log::withContext(['pid'=>getmypid()]);
|
||||
|
||||
$this->setClient($client);
|
||||
$this->client = $client;
|
||||
$this->protocol_session();
|
||||
|
||||
Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
|
||||
@ -90,6 +90,7 @@ final class DNS extends BaseProtocol
|
||||
* Handle a DNS query
|
||||
*
|
||||
* https://www.ietf.org/rfc/rfc1035.txt
|
||||
* https://www.ietf.org/rfc/rfc2308.txt
|
||||
* https://github.com/guyinatuxedo/dns-fuzzer/blob/master/dns.md
|
||||
*
|
||||
* labels 63 octets or less
|
||||
@ -97,21 +98,29 @@ final class DNS extends BaseProtocol
|
||||
* TTL positive values of a signed 32 bit number.
|
||||
* UDP messages 512 octets or less
|
||||
*
|
||||
* @param bool $force_queue Not used here
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function protocol_session(): int
|
||||
public function protocol_session(bool $force_queue=FALSE): int
|
||||
{
|
||||
Log::debug(sprintf('%s:+ DNS Query',self::LOGKEY));
|
||||
|
||||
try {
|
||||
$this->query = new BaseProtocol\DNS\Query($this->client->read(0,512));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s:! Ignoring bad DNS query (%s)',self::LOGKEY,$e->getMessage()));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Log::info(sprintf('%s:= DNS Query from [%s] for [%s]',self::LOGKEY,$this->client->address_remote,$this->query->domain));
|
||||
|
||||
// If the wrong class
|
||||
if ($this->query->class !== self::DNS_QUERY_IN) {
|
||||
Log::error(sprintf('%s:! We only service Internet queries [%d]',self::LOGKEY,$this->query->class));
|
||||
return $this->reply(self::DNS_NOTIMPLEMENTED);
|
||||
return $this->reply(self::DNS_NOTIMPLEMENTED,[],$this->soa());
|
||||
}
|
||||
|
||||
$dos = Domain::select(['id','name','dnsdomain'])->active();
|
||||
@ -140,17 +149,9 @@ final class DNS extends BaseProtocol
|
||||
|
||||
return $this->reply(
|
||||
self::DNS_NOERROR,
|
||||
[serialize([
|
||||
$this->domain_split(gethostname()),
|
||||
$this->domain_split(Str::replace('@','.',config('app.mail.mail_from','nobody@'.gethostname()))),
|
||||
1,
|
||||
self::DEFAULT_TTL,
|
||||
self::DEFAULT_TTL,
|
||||
self::DEFAULT_TTL,
|
||||
self::DEFAULT_TTL
|
||||
]) => self::DNS_TYPE_SOA],
|
||||
$this->soa(),
|
||||
[],
|
||||
[serialize($this->domain_split(gethostname())) => self::DNS_TYPE_NS],
|
||||
[serialize($this->domain_split(config('fido.dns_ns'))) => self::DNS_TYPE_NS],
|
||||
);
|
||||
|
||||
case self::DNS_TYPE_NS:
|
||||
@ -158,23 +159,52 @@ final class DNS extends BaseProtocol
|
||||
|
||||
return $this->reply(
|
||||
self::DNS_NOERROR,
|
||||
[serialize($this->domain_split(gethostname())) => self::DNS_TYPE_NS]);
|
||||
[serialize($this->domain_split(config('fido.dns_ns'))) => self::DNS_TYPE_NS]);
|
||||
|
||||
// Respond to A/AAAA/CNAME queries, with value or NAMEERR
|
||||
case self::DNS_TYPE_CNAME:
|
||||
case self::DNS_TYPE_A:
|
||||
case self::DNS_TYPE_AAAA:
|
||||
case self::DNS_TYPE_SRV:
|
||||
case self::DNS_TYPE_TXT:
|
||||
Log::info(sprintf('%s:= Looking for record [%s] for [%s]',self::LOGKEY,$this->query->type,$this->query->domain));
|
||||
|
||||
$labels = clone($this->query->labels);
|
||||
$mailer = '';
|
||||
|
||||
// If this is a SRV record query
|
||||
if ($this->query->type === self::DNS_TYPE_SRV) {
|
||||
if ($labels->skip(1)->first() !== '_tcp')
|
||||
return $this->reply(self::DNS_NAMEERR);
|
||||
|
||||
switch ($labels->first()) {
|
||||
case '_binkp':
|
||||
$mailer = Mailer::where('name','BINKP')->singleOrFail();
|
||||
break;
|
||||
|
||||
case '_ifcico':
|
||||
$mailer = Mailer::where('name','EMSI')->singleOrFail();
|
||||
break;
|
||||
|
||||
default:
|
||||
return $this->reply(self::DNS_NAMEERR);
|
||||
}
|
||||
|
||||
$labels->shift(2);
|
||||
}
|
||||
|
||||
// First check that it is a query we can answer
|
||||
// First label should be p.. or f..
|
||||
if (! is_null($p=$this->parse('p',$labels->first())))
|
||||
$labels->shift();
|
||||
else
|
||||
$p = 0;
|
||||
|
||||
if (is_null($f=$this->parse('f',$labels->shift())))
|
||||
return $this->nameerr();
|
||||
// We'll assume f0
|
||||
if (! is_null($f=$this->parse('f',$labels->first())))
|
||||
$labels->shift();
|
||||
else
|
||||
$f = 0;
|
||||
|
||||
if (is_null($n=$this->parse('n',$labels->shift())))
|
||||
return $this->nameerr();
|
||||
@ -200,22 +230,51 @@ final class DNS extends BaseProtocol
|
||||
$ao = Address::findFTN(sprintf('%d:%d/%d.%d@%s',$z,$n,$f,$p,$d));
|
||||
|
||||
// Check we have the right record
|
||||
if ((! $ao) || (! $ao->system->address) || (($rootdn !== self::TLD) && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $rootdn)))) {
|
||||
if ((! $ao) || (($rootdn !== self::TLD) && ((! $ao->zone->domain->dnsdomain) || ($ao->zone->domain->dnsdomain !== $rootdn)))) {
|
||||
Log::alert(sprintf('%s:= No DNS record for [%d:%d/%d.%d@%s]',self::LOGKEY,$z,$n,$f,$p,$d));
|
||||
return $this->nameerr();
|
||||
}
|
||||
|
||||
switch ($this->query->type) {
|
||||
case self::DNS_TYPE_SRV:
|
||||
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
|
||||
|
||||
if (($ao->system->address) && ($xx=$ao->system->mailers->where('id',$mailer->id)->pop())) {
|
||||
return $this->reply(
|
||||
self::DNS_NOERROR,
|
||||
[serialize([
|
||||
0, // priority
|
||||
1, // weight
|
||||
$xx->pivot->port,
|
||||
$this->domain_split($ao->system->address),
|
||||
]) => self::DNS_TYPE_SRV]);
|
||||
|
||||
} else {
|
||||
return $this->nodata();
|
||||
}
|
||||
|
||||
case self::DNS_TYPE_TXT:
|
||||
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->name,$ao->ftn));
|
||||
|
||||
return $this->reply(
|
||||
self::DNS_NOERROR,
|
||||
[serialize($ao->system->name) => self::DNS_TYPE_TXT]);
|
||||
|
||||
default:
|
||||
Log::info(sprintf('%s:= Returning [%s] for DNS query [%s]',self::LOGKEY,$ao->system->address,$ao->ftn));
|
||||
|
||||
return (! $ao->system->address)
|
||||
? $this->nodata()
|
||||
: $this->reply(
|
||||
self::DNS_NOERROR,
|
||||
[serialize($this->domain_split($ao->system->address)) => self::DNS_TYPE_CNAME]);
|
||||
}
|
||||
|
||||
// Other attributes return NOTIMPL
|
||||
default:
|
||||
Log::error(sprintf('%s:! We dont support DNS query types [%d]',self::LOGKEY,$this->query->type));
|
||||
|
||||
return $this->reply(self::DNS_NOTIMPLEMENTED);
|
||||
return $this->reply(self::DNS_NOTIMPLEMENTED,[],$this->soa());
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,11 +289,36 @@ final class DNS extends BaseProtocol
|
||||
return pack('n',$offset | (3 << 14));
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a domain into a DNS domain string
|
||||
*
|
||||
* @param string $domain
|
||||
* @return string
|
||||
*/
|
||||
private function domain_split(string $domain): string
|
||||
{
|
||||
$a = '';
|
||||
|
||||
foreach (explode('.',$domain) as $item)
|
||||
$a .= pack('C',strlen($item)).$item;
|
||||
|
||||
$a .= "\x00";
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
private function nameerr(): int
|
||||
{
|
||||
Log::error(sprintf('%s:! DNS query for a resource we dont manage [%s]',self::LOGKEY,$this->query->domain));
|
||||
|
||||
return $this->reply(self::DNS_NAMEERR);
|
||||
return $this->reply(self::DNS_NAMEERR,[],$this->soa());
|
||||
}
|
||||
|
||||
private function nodata(): int
|
||||
{
|
||||
Log::error(sprintf('%s:! DNS query for a resource we dont manage [%s] in our zone(s)',self::LOGKEY,$this->query->domain));
|
||||
|
||||
return $this->reply(self::DNS_NOERROR,[],$this->soa());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -248,7 +332,7 @@ final class DNS extends BaseProtocol
|
||||
{
|
||||
$m = [];
|
||||
|
||||
return (preg_match('/^'.$prefix.'([0-9]+)+/',$label,$m) && ($m[1] <= DomainController::NUMBER_MAX))
|
||||
return (preg_match('/^'.$prefix.'([0-9]+)+/',$label,$m) && ($m[1] <= Address::ADDRESS_FIELD_MAX))
|
||||
? $m[1]
|
||||
: NULL;
|
||||
}
|
||||
@ -320,24 +404,6 @@ final class DNS extends BaseProtocol
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a domain into a DNS domain string
|
||||
*
|
||||
* @param string $domain
|
||||
* @return string
|
||||
*/
|
||||
private function domain_split(string $domain): string
|
||||
{
|
||||
$a = '';
|
||||
|
||||
foreach (explode('.',$domain) as $item)
|
||||
$a .= pack('C',strlen($item)).$item;
|
||||
|
||||
$a .= "\x00";
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a DNS Resource Record
|
||||
*
|
||||
@ -378,10 +444,34 @@ final class DNS extends BaseProtocol
|
||||
$a .= pack('NNNNN',$ars[2],$ars[3],$ars[4],$ars[5],$ars[6]);
|
||||
|
||||
break;
|
||||
|
||||
case self::DNS_TYPE_SRV:
|
||||
$a .= pack('nnn',$ars[0],$ars[1],$ars[2]);
|
||||
$a .= $ars[3];
|
||||
|
||||
break;
|
||||
|
||||
case self::DNS_TYPE_TXT:
|
||||
$a .= pack('C',strlen($ars)).$ars;
|
||||
break;
|
||||
}
|
||||
|
||||
$reply .= pack('n',strlen($a)).$a;
|
||||
|
||||
return $reply;
|
||||
}
|
||||
|
||||
private function soa(): array
|
||||
{
|
||||
return
|
||||
[serialize([
|
||||
$this->domain_split(config('fido.dns_ns')),
|
||||
$this->domain_split(Str::replace('@','.',config('app.mail.mail_from','nobody@'.gethostname()))),
|
||||
1, // Serial
|
||||
self::DEFAULT_TTL, // Refresh
|
||||
self::DEFAULT_TTL, // Retry
|
||||
self::DEFAULT_TTL*7,// Expire
|
||||
self::DEFAULT_TTL // Minimum cache
|
||||
]) => self::DNS_TYPE_SOA];
|
||||
}
|
||||
}
|
@ -32,7 +32,8 @@ final class Query
|
||||
'arcount' => [0x05,'n',1], // Resource Records in the addition records section
|
||||
];
|
||||
|
||||
public function __construct(string $buf) {
|
||||
public function __construct(string $buf)
|
||||
{
|
||||
$this->buf = $buf;
|
||||
$rx_ptr = 0;
|
||||
|
||||
@ -49,12 +50,20 @@ final class Query
|
||||
$this->labels = collect();
|
||||
|
||||
while (($len=ord(substr($this->buf,$rx_ptr++,1))) !== 0x00) {
|
||||
$this->labels->push(substr($this->buf,$rx_ptr,$len));
|
||||
$this->labels->push(strtolower(substr($this->buf,$rx_ptr,$len)));
|
||||
$rx_ptr += $len;
|
||||
}
|
||||
|
||||
// Get the query type/class
|
||||
try {
|
||||
$result = unpack('ntype/nclass',substr($this->buf,$rx_ptr,4));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s:! Unpack failed: Buffer: [%s] (%d), RXPTR [%d]',self::LOGKEY,hex_dump($this->buf),strlen($this->buf),$rx_ptr));
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$rx_ptr += 4;
|
||||
$this->type = $result['type'];
|
||||
$this->class = $result['class'];
|
||||
@ -65,17 +74,16 @@ final class Query
|
||||
if ($this->arcount) {
|
||||
// Additional records, EDNS: https://datatracker.ietf.org/doc/html/rfc6891
|
||||
if (($haystack = strstr(substr($this->buf,$rx_ptr+1+10),"\x00",true)) !== FALSE) {
|
||||
Log::error(sprintf('%s:! DNS additional record format error?',self::LOGKEY));
|
||||
// @todo catch this
|
||||
Log::error(sprintf('%s:! DNS additional record format error?',self::LOGKEY),['buf'=>hex_dump($this->buf)]);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->additional = new RR(substr($this->buf,$rx_ptr,(strlen($haystack) === 0) ? NULL : strlen($haystack)));
|
||||
$rx_ptr += $this->additional->length;
|
||||
}
|
||||
|
||||
if (strlen($this->buf) !== $rx_ptr) {
|
||||
dd(['query remaining'=>strlen($this->buf)-$rx_ptr,'hex'=>hex_dump(substr($this->buf,$rx_ptr))]);
|
||||
}
|
||||
if (strlen($this->buf) !== $rx_ptr)
|
||||
throw new \Exception(sprintf('! DNS Buffer still has [%d]: %s',strlen($this->buf)-$rx_ptr,hex_dump(substr($this->buf,$rx_ptr))));
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
@ -96,7 +104,8 @@ final class Query
|
||||
}
|
||||
}
|
||||
|
||||
public static function header_len() {
|
||||
public static function header_len()
|
||||
{
|
||||
return collect(self::header)->sum(function($item) { return $item[2]*2; });
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ namespace App\Classes\Protocol\DNS;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\Protocol\DNS;
|
||||
|
||||
@ -26,8 +27,15 @@ final class RR
|
||||
$domain = strstr($buf,"\x00",TRUE);
|
||||
$i += strlen($domain)+1;
|
||||
|
||||
try {
|
||||
$this->type = Arr::get(unpack('n',substr($buf,$i,2)),1);
|
||||
$this->class = Arr::get(unpack('n',substr($buf,$i+2,2)),1);
|
||||
|
||||
} catch (\ErrorException $e) {
|
||||
Log::error(sprintf('%s:! Error unpacking buffer [%s]',self::LOGKEY,$buf),['buf'=>hex_dump($buf)]);
|
||||
return;
|
||||
}
|
||||
|
||||
$i += 4;
|
||||
|
||||
switch ($this->type) {
|
||||
|
@ -3,13 +3,13 @@
|
||||
namespace App\Classes\Protocol;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\Protocol as BaseProtocol;
|
||||
use App\Classes\Sock\SocketClient;
|
||||
use App\Classes\Sock\SocketException;
|
||||
use App\Models\{Address,Setup};
|
||||
use App\Exceptions\InvalidFTNException;
|
||||
use App\Models\{Address,Mailer,Setup};
|
||||
use App\Interfaces\CRC as CRCInterface;
|
||||
use App\Interfaces\Zmodem as ZmodemInterface;
|
||||
use App\Traits\CRC as CRCTrait;
|
||||
@ -88,7 +88,6 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
* @param SocketClient $client
|
||||
* @return int|null
|
||||
* @throws SocketException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function onConnect(SocketClient $client): ?int
|
||||
{
|
||||
@ -96,8 +95,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
if (! parent::onConnect($client)) {
|
||||
Log::withContext(['pid'=>getmypid()]);
|
||||
|
||||
// @todo Can this be SESSION_EMSI? if so, set an object class value that in EMSI of SESSION_EMSI, and move this method to the parent class
|
||||
$this->session(self::SESSION_AUTO,$client,(new Address));
|
||||
$this->session(Mailer::where('name','EMSI')->singleOrFail(),$client,(new Address));
|
||||
$this->client->close();
|
||||
exit(0);
|
||||
}
|
||||
@ -108,7 +106,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
/**
|
||||
* Send our welcome banner
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function emsi_banner(): void
|
||||
{
|
||||
@ -123,7 +121,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
* Create the EMSI_DAT
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function emsi_makedat(): string
|
||||
{
|
||||
@ -273,7 +271,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
*
|
||||
* @param string $str
|
||||
* @return int
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function emsi_parsedat(string $str): int
|
||||
{
|
||||
@ -321,17 +319,38 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
Log::debug(sprintf('%s: - Parsing AKA [%s]',self::LOGKEY,$rem_aka));
|
||||
|
||||
try {
|
||||
if (! ($o = Address::findFTN($rem_aka))) {
|
||||
if (! ($o = Address::findFTN($rem_aka,TRUE))) {
|
||||
Log::debug(sprintf('%s: ? AKA is UNKNOWN [%s]',self::LOGKEY,$rem_aka));
|
||||
|
||||
$this->node->ftn_other = $rem_aka;
|
||||
continue;
|
||||
|
||||
// If we only present limited AKAs dont validate password against akas outside of the domains we present
|
||||
} elseif (is_null(our_address($o))) {
|
||||
Log::alert(sprintf('%s:/ AKA domain [%s] is not in our domain(s) [%s] - ignoring',self::LOGKEY,$o->zone->domain->name,our_address()->pluck('zone.domain.name')->unique()->join(',')));
|
||||
|
||||
$this->node->ftn_other = $rem_aka;
|
||||
continue;
|
||||
|
||||
} elseif (! $o->active) {
|
||||
Log::alert(sprintf('%s:/ AKA is not active [%s] - ignoring',self::LOGKEY,$rem_aka));
|
||||
|
||||
$this->node->ftn_other = $rem_aka;
|
||||
continue;
|
||||
|
||||
} else {
|
||||
Log::info(sprintf('%s:- Got AKA [%s]',self::LOGKEY,$rem_aka));
|
||||
}
|
||||
|
||||
} catch (Exception) {
|
||||
Log::error(sprintf('%s: ! AKA is INVALID [%s]',self::LOGKEY,$rem_aka));
|
||||
} catch (InvalidFTNException $e) {
|
||||
Log::error(sprintf('%s:! AKA is INVALID [%s] (%s), ignoring',self::LOGKEY,$rem_aka,$e->getMessage()));
|
||||
|
||||
continue;
|
||||
|
||||
} catch (\Exception) {
|
||||
Log::error(sprintf('%s: ! AKA is INVALID [%s]',self::LOGKEY,$rem_aka));
|
||||
|
||||
return self::S_FAILURE|self::S_ADDTRY;
|
||||
}
|
||||
|
||||
// Check if the remote has our AKA
|
||||
@ -504,7 +523,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
* STEP 2A, RECEIVE EMSI HANDSHAKE
|
||||
*
|
||||
* @throws SocketException
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function emsi_recv(int $mode): int
|
||||
{
|
||||
@ -683,7 +702,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
* STEP 2B, TRANSMIT EMSI HANDSHAKE
|
||||
*
|
||||
* @throws SocketException
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function emsi_send(): int
|
||||
{
|
||||
@ -822,7 +841,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
* STEP 1, EMSI INIT
|
||||
*
|
||||
* @throws SocketException
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function protocol_init(): int
|
||||
{
|
||||
@ -969,13 +988,14 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
* Setup our EMSI session
|
||||
*
|
||||
* @return int
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function protocol_session(): int
|
||||
protected function protocol_session(bool $force_queue=FALSE): int
|
||||
{
|
||||
// @todo introduce emsi_init() to perform the job of protocol_init. Only needs to be done when we originate a session
|
||||
|
||||
Log::debug(sprintf('%s:+ Starting EMSI Protocol SESSION',self::LOGKEY));
|
||||
$this->force_queue = $force_queue;
|
||||
|
||||
$was_req = 0;
|
||||
$got_req = 0;
|
||||
@ -1184,7 +1204,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
Log::debug(sprintf('%s:+ Start WAZOO Receive',self::LOGKEY));
|
||||
|
||||
// @todo If the node is not defined in the DB node->address is NULL. Need to figure out how to handle those nodes.
|
||||
$rc = (new Zmodem)->zmodem_receive($this->client,$zap,$this->recv,$this->node->address);
|
||||
$rc = (new Zmodem)->zmodem_receive($this->client,$zap,$this->recv,$this->node->address,$this->force_queue);
|
||||
|
||||
return ($rc === self::RCDO || $rc === self::ERROR);
|
||||
}
|
||||
@ -1194,7 +1214,7 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
||||
*
|
||||
* @param int $zap
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function wazoosend(int $zap): bool
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ use App\Classes\File\{Receive,Send};
|
||||
use App\Classes\Sock\{SocketClient,SocketException};
|
||||
use App\Interfaces\CRC as CRCInterface;
|
||||
use App\Interfaces\Zmodem as ZmodemInterface;
|
||||
use App\Models\Address;
|
||||
use App\Models\{Address,Mailer};
|
||||
use App\Traits\CRC as CRCTrait;
|
||||
|
||||
/**
|
||||
@ -213,7 +213,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
if (! parent::onConnect($client)) {
|
||||
Log::withContext(['pid'=>getmypid()]);
|
||||
|
||||
$this->session(self::SESSION_ZMODEM,$client);
|
||||
$this->session(Mailer::where('name','ZMODEM')->singleOrFail(),$client);
|
||||
$this->client->close();
|
||||
|
||||
Log::info(sprintf('%s:= onConnect - Connection closed [%s]',self::LOGKEY,$client->address_remote));
|
||||
@ -273,10 +273,11 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
/**
|
||||
* Setup our ZMODEM session
|
||||
*
|
||||
* @param bool $force_queue Not used here
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function protocol_session(): int
|
||||
public function protocol_session(bool $force_queue=FALSE): int
|
||||
{
|
||||
$proto = $this->originate ? $this->node->optionGet(self::P_MASK) : $this->optionGet(self::P_MASK);
|
||||
|
||||
@ -301,7 +302,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
* @param int $canzap
|
||||
* @return int
|
||||
*/
|
||||
public function zmodem_receive(SocketClient $client,int $canzap,Receive $recv,Address $ao): int
|
||||
public function zmodem_receive(SocketClient $client,int $canzap,Receive $recv,Address $ao,bool $force_queue=FALSE): int
|
||||
{
|
||||
Log::debug(sprintf('%s:+ Starting ZModem Receive [%d]',self::LOGKEY,$canzap));
|
||||
|
||||
@ -321,7 +322,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
return self::OK;
|
||||
|
||||
case self::ZFILE:
|
||||
Log::debug(sprintf('%s: = zmodem_receive ZFILE after INIT',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:= zmodem_receive ZFILE after INIT',self::LOGKEY));
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -343,46 +344,46 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
case self::ZFILE:
|
||||
if (! $this->recv->togo_count) {
|
||||
Log::error(sprintf('%s: ! zmodem_receive No files to get?',self::LOGKEY));
|
||||
Log::error(sprintf('%s:! zmodem_receive No files to get?',self::LOGKEY));
|
||||
|
||||
$frame = self::ZSKIP;
|
||||
|
||||
} else {
|
||||
switch ($this->recv->open()) {
|
||||
case self::FOP_SKIP:
|
||||
Log::info(sprintf('%s: = zmodem_receive Skip this file [%s]',self::LOGKEY,$this->recv->nameas));
|
||||
Log::info(sprintf('%s:= zmodem_receive Skip this file [%s]',self::LOGKEY,$this->recv->nameas));
|
||||
$frame = self::ZSKIP;
|
||||
|
||||
break;
|
||||
|
||||
case self::FOP_SUSPEND:
|
||||
Log::info(sprintf('%s: = zmodem_receive Suspend this file [%s]',self::LOGKEY,$this->recv->nameas));
|
||||
Log::info(sprintf('%s:= zmodem_receive Suspend this file [%s]',self::LOGKEY,$this->recv->nameas));
|
||||
$frame = self::ZFERR;
|
||||
|
||||
break;
|
||||
|
||||
case self::FOP_CONT:
|
||||
case self::FOP_OK:
|
||||
Log::info(sprintf('%s: = zmodem_receive Receving [%s] from [%d]',self::LOGKEY,$this->recv->nameas,$this->recv->pos));
|
||||
Log::info(sprintf('%s:= zmodem_receive Receiving [%s] from [%d]',self::LOGKEY,$this->recv->nameas,$this->recv->pos));
|
||||
$frame = self::ZRINIT;
|
||||
|
||||
switch (($rc=$this->ls_zrecvfile($recv->pos))) {
|
||||
case self::ZFERR:
|
||||
Log::debug(sprintf('%s: = zmodem_receive ZFERR',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:= zmodem_receive ZFERR',self::LOGKEY));
|
||||
$this->recv->close();
|
||||
$frame = self::ZFERR;
|
||||
|
||||
break;
|
||||
|
||||
case self::ZSKIP:
|
||||
Log::debug(sprintf('%s: = zmodem_receive ZSKIP',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:= zmodem_receive ZSKIP',self::LOGKEY));
|
||||
$this->recv->close();
|
||||
$frame = self::ZSKIP;
|
||||
|
||||
break;
|
||||
|
||||
case self::OK:
|
||||
Log::debug(sprintf('%s: = zmodem_receive OK',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:= zmodem_receive OK',self::LOGKEY));
|
||||
$this->recv->close();
|
||||
|
||||
break;
|
||||
@ -459,7 +460,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? zmodem_senddone Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? zmodem_senddone Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -489,7 +490,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
if (($rc=$this->ls_zinitsender(self::LSZ_WINDOW,'')) < 0)
|
||||
return $rc;
|
||||
|
||||
Log::debug(sprintf('%s: - ZMODEM Link Options %d/%d, %s%s%s%s',
|
||||
Log::debug(sprintf('%s:- ZMODEM Link Options %d/%d, %s%s%s%s',
|
||||
self::LOGKEY,
|
||||
$this->ls_MaxBlockSize,
|
||||
$this->ls_txWinSize,
|
||||
@ -536,7 +537,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
return $rc;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s: ! Error [%s]',self::LOGKEY,$e->getMessage()));
|
||||
Log::error(sprintf('%s:! Error [%s]',self::LOGKEY,$e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -878,12 +879,12 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
case self::XON|0x80:
|
||||
case self::XOFF|0x80:
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zdonereceiver XON/XOFF, skip it',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zdonereceiver XON/XOFF, skip it',self::LOGKEY));
|
||||
break;
|
||||
|
||||
case self::ZPAD:
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zdonereceiver ZPAD',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zdonereceiver ZPAD',self::LOGKEY));
|
||||
|
||||
if (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout)) < 0)
|
||||
return $rc;
|
||||
@ -948,7 +949,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
switch ($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout)) {
|
||||
/* Ok, We got RINIT! */
|
||||
case self::ZRINIT:
|
||||
Log::debug(sprintf('%s: - ls_zinitsender ZRINIT',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zinitsender ZRINIT',self::LOGKEY));
|
||||
|
||||
$this->rxOptions= (($this->ls_rxHdr[self::LSZ_F1]&0xff)<<8)|($this->ls_rxHdr[self::LSZ_F0]&0xff);
|
||||
|
||||
@ -979,7 +980,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
if ($window && (! $this->ls_txWinSize || ($this->ls_txWinSize > $window)))
|
||||
$this->ls_txWinSize = $window;
|
||||
|
||||
Log::debug(sprintf('%s: - ls_zinitsender ZRINIT OK - effproto [%08x], winsize [%d]',self::LOGKEY,$this->ls_Protocol,$this->ls_txWinSize));
|
||||
Log::debug(sprintf('%s:- ls_zinitsender ZRINIT OK - effproto [%08x], winsize [%d]',self::LOGKEY,$this->ls_Protocol,$this->ls_txWinSize));
|
||||
|
||||
/* Ok, now we could calculate real max frame size and initial block size */
|
||||
if ($this->ls_txWinSize && $this->ls_MaxBlockSize>$this->ls_txWinSize) {
|
||||
@ -1013,7 +1014,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
if ($this->ls_txCurBlockSize>$this->ls_MaxBlockSize)
|
||||
$this->ls_txCurBlockSize=$this->ls_MaxBlockSize;
|
||||
|
||||
Log::debug(sprintf('%s: - ls_zinitsender ZRINIT OK - block sizes Max [%d] Current [%d]',self::LOGKEY,$this->ls_MaxBlockSize,$this->ls_txCurBlockSize));
|
||||
Log::debug(sprintf('%s:- ls_zinitsender ZRINIT OK - block sizes Max [%d] Current [%d]',self::LOGKEY,$this->ls_MaxBlockSize,$this->ls_txCurBlockSize));
|
||||
|
||||
/* Send ZSINIT, if we need it */
|
||||
if ($attstr || (! ($this->rxOptions&self::LSZ_RXWNTESCCTL) && ($this->ls_Protocol&self::LSZ_OPTESCAPEALL)))
|
||||
@ -1024,7 +1025,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
/* Return number to peer, he is paranoid */
|
||||
case self::ZCHALLENGE:
|
||||
Log::debug(sprintf('%s: - ls_zinitsender CHALLENGE',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zinitsender CHALLENGE',self::LOGKEY));
|
||||
|
||||
if (($rc=$this->ls_zsendhhdr(ZACK,$this->ls_rxHdr)) < 0)
|
||||
return $rc;
|
||||
@ -1040,7 +1041,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
/* ZFIN from previous session? Or may be real one? */
|
||||
case self::ZFIN:
|
||||
Log::debug(sprintf('%s: - ls_zinitsender ZFIN [%d]',self::LOGKEY,$zfins));
|
||||
Log::debug(sprintf('%s:- ls_zinitsender ZFIN [%d]',self::LOGKEY,$zfins));
|
||||
|
||||
if (++$zfins === self::LSZ_TRUSTZFINS)
|
||||
return self::ERROR;
|
||||
@ -1049,11 +1050,11 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
/* Please, resend */
|
||||
case self::LSZ_BADCRC:
|
||||
Log::debug(sprintf('%s: - ls_zinitsender LSZ_BADCRC',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zinitsender LSZ_BADCRC',self::LOGKEY));
|
||||
|
||||
/* We don't support it! */
|
||||
case self::ZCOMMAND:
|
||||
Log::debug(sprintf('%s: - ls_zinitsender ZCOMMAND',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zinitsender ZCOMMAND',self::LOGKEY));
|
||||
$this->ls_zsendhhdr(ZNAK,$this->ls_storelong(0));
|
||||
|
||||
/* Abort this session -- we trust in ABORT! */
|
||||
@ -1062,7 +1063,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
return self::ERROR;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? ls_zinitsender Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? ls_zinitsender Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -1095,7 +1096,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
while ($rcvdata && (($c = $this->ls_readzdle($timeout)) >= 0)) {
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zrecvdata16 got [%x] (%c)',self::LOGKEY,$c,($c<31 ? 32 : $c)),['c'=>serialize($c)]);
|
||||
Log::debug(sprintf('%s:- ls_zrecvdata16 got [%x] (%c)',self::LOGKEY,$c,($c<31 ? 32 : $c)),['c'=>serialize($c)]);
|
||||
|
||||
if ($c < 256) {
|
||||
$data .= chr($c&0xff);
|
||||
@ -1141,7 +1142,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
$incrc = crc16($data.chr($frametype));
|
||||
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zrecvdata16 CRC%d got %08x - calculated %08x',self::LOGKEY,16,$incrc,$crc));
|
||||
Log::debug(sprintf('%s:- ls_zrecvdata16 CRC%d got %08x - calculated %08x',self::LOGKEY,16,$incrc,$crc));
|
||||
|
||||
if ($incrc != $crc)
|
||||
return self::LSZ_BADCRC;
|
||||
@ -1174,7 +1175,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
while ($rcvdata && (($c=$this->ls_readzdle($timeout)) >= 0)) {
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zrecvdata32 got [%x] (%c)',self::LOGKEY,$c,($c<31 ? 32 : $c)),['c'=>serialize($c)]);
|
||||
Log::debug(sprintf('%s:- ls_zrecvdata32 got [%x] (%c)',self::LOGKEY,$c,($c<31 ? 32 : $c)),['c'=>serialize($c)]);
|
||||
|
||||
if ($c < 256) {
|
||||
$data .= chr($c&0xff);
|
||||
@ -1226,7 +1227,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
$incrc = crc32($data.chr($frametype));
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zrecvdata32 CRC%d got %08x - calculated %08x',self::LOGKEY,32,$incrc,$crc));
|
||||
Log::debug(sprintf('%s:- ls_zrecvdata32 CRC%d got %08x - calculated %08x',self::LOGKEY,32,$incrc,$crc));
|
||||
|
||||
if ($incrc != $crc)
|
||||
return self::LSZ_BADCRC;
|
||||
@ -1266,7 +1267,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
$needzdata = 1;
|
||||
|
||||
case self::ZCRCG:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfile ZCRC%s, [%d] bytes at [%d]',self::LOGKEY,($rc==self::ZCRCE ? 'E' : 'G'),$len,$rxpos));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfile ZCRC%s, [%d] bytes at [%d]',self::LOGKEY,($rc==self::ZCRCE ? 'E' : 'G'),$len,$rxpos));
|
||||
|
||||
$rxpos += $len;
|
||||
|
||||
@ -1281,7 +1282,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
$needzdata = 1;
|
||||
|
||||
case self::ZCRCQ:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfile ZCRC%s, [%d] bytes at [%d]',self::LOGKEY,($rc==self::ZCRCW ? 'W' : 'Q'),$len,$rxpos));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfile ZCRC%s, [%d] bytes at [%d]',self::LOGKEY,($rc==self::ZCRCW ? 'W' : 'Q'),$len,$rxpos));
|
||||
|
||||
$rxpos += $len;
|
||||
|
||||
@ -1308,7 +1309,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? ls_zrecvfile Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? ls_zrecvfile Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -1325,13 +1326,13 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
/* We need new position -- ZDATA (and may be ZEOF) */
|
||||
} else {
|
||||
Log::debug(sprintf('%s: - ls_zrecvfile Want ZDATA/ZEOF at [%d]',self::LOGKEY,$rxpos));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfile Want ZDATA/ZEOF at [%d]',self::LOGKEY,$rxpos));
|
||||
|
||||
if (($rc=$this->ls_zrecvnewpos($rxpos,$newpos)) < 0)
|
||||
return $rc;
|
||||
|
||||
if ($newpos != $rxpos) {
|
||||
Log::error(sprintf('%s: - ls_zrecvfile Bad new position [%d] in [%d]',self::LOGKEY,$newpos,$rc));
|
||||
Log::error(sprintf('%s:- ls_zrecvfile Bad new position [%d] in [%d]',self::LOGKEY,$newpos,$rc));
|
||||
|
||||
if ($this->ls_rxAttnStr) {
|
||||
$this->client->buffer_add($this->ls_rxAttnStr);
|
||||
@ -1345,7 +1346,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
} else {
|
||||
if ($rc === self::ZEOF) {
|
||||
Log::debug(sprintf('%s: - ls_zrecvfile ZEOF',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfile ZEOF',self::LOGKEY));
|
||||
|
||||
if (($rc=$this->ls_zsendhhdr(self::ZRINIT,$this->ls_storelong(0))) < 0)
|
||||
return $rc;
|
||||
@ -1353,7 +1354,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
return self::OK;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('%s: - ls_zrecvfile ZDATA',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfile ZDATA',self::LOGKEY));
|
||||
$needzdata = 0;
|
||||
}
|
||||
}
|
||||
@ -1405,7 +1406,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
return $rc;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('%s: - ZRINIT',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ZRINIT',self::LOGKEY));
|
||||
|
||||
$txHdr = [];
|
||||
$txHdr[self::LSZ_P0] = ($win&0xff);
|
||||
@ -1423,22 +1424,22 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$this->ls_HeaderTimeout))) {
|
||||
/* Send ZRINIT again */
|
||||
case self::ZRQINIT:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfinfo ZRQINIT',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfinfo ZRQINIT',self::LOGKEY));
|
||||
/* We will trust in first ZFIN after ZRQINIT */
|
||||
$first = 1;
|
||||
|
||||
case self::ZNAK:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfinfo ZNAK',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfinfo ZNAK',self::LOGKEY));
|
||||
|
||||
case self::TIMEOUT:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfinfo TIMEOUT',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfinfo TIMEOUT',self::LOGKEY));
|
||||
$retransmit = 1;
|
||||
|
||||
break;
|
||||
|
||||
/* He want to set some options */
|
||||
case self::ZSINIT:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfinfo ZSINIT',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfinfo ZSINIT',self::LOGKEY));
|
||||
if (($rc=$this->ls_zrecvcrcw($this->rxbuf,$len)) < 0)
|
||||
return $rc;
|
||||
|
||||
@ -1466,7 +1467,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
/* Ok, File started! */
|
||||
case self::ZFILE:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfinfo ZFILE',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfinfo ZFILE',self::LOGKEY));
|
||||
|
||||
if (($rc=$this->ls_zrecvcrcw($this->rxbuf,$len)) < 0)
|
||||
return $rc;
|
||||
@ -1485,11 +1486,11 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
$filesleft, // @todo Should track this
|
||||
$bytesleft) < 2)
|
||||
{
|
||||
Log::error(sprintf('%s: ! ls_zrecvfinfo File info is corrupted [%s]',self::LOGKEY,$this->rxbuf));
|
||||
Log::error(sprintf('%s:! ls_zrecvfinfo File info is corrupted [%s]',self::LOGKEY,$this->rxbuf));
|
||||
$filesleft = -1;
|
||||
|
||||
} else {
|
||||
$this->recv->new($file,$ao);
|
||||
$this->recv->new($file,$ao,$this->force_queue);
|
||||
}
|
||||
|
||||
return self::ZFILE;
|
||||
@ -1503,7 +1504,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
/* ZFIN from previous session? Or may be real one? */
|
||||
case self::ZFIN:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfinfo ZFIN [%d], first [%d]',self::LOGKEY,$zfins,$first));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfinfo ZFIN [%d], first [%d]',self::LOGKEY,$zfins,$first));
|
||||
|
||||
if ($first || (++$zfins === self::LSZ_TRUSTZFINS))
|
||||
return self::ZFIN;
|
||||
@ -1516,7 +1517,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
return self::ZABORT;
|
||||
|
||||
case self::LSZ_BADCRC:
|
||||
Log::debug(sprintf('%s: - ls_zrecvfinfo BADCRC',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvfinfo BADCRC',self::LOGKEY));
|
||||
|
||||
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
||||
$retransmit = 1;
|
||||
@ -1524,7 +1525,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? ls_zrecvfinfo Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? ls_zrecvfinfo Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -1601,7 +1602,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
}
|
||||
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zrecvhdr [%x] (%c)',self::LOGKEY,$c,$c));
|
||||
Log::debug(sprintf('%s:- ls_zrecvhdr [%x] (%c)',self::LOGKEY,$c,$c));
|
||||
|
||||
/* Here is error */
|
||||
if ($c < 0)
|
||||
@ -1716,7 +1717,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
case self::rhBYTE:
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: / ls_zrecvhdr [%02x] (%d)',self::LOGKEY,$c,$got));
|
||||
Log::debug(sprintf('%s:/ ls_zrecvhdr [%02x] (%d)',self::LOGKEY,$c,$got));
|
||||
|
||||
$hdr[$got] = $c;
|
||||
|
||||
@ -1729,7 +1730,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
case self::rhCRC:
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: %% ls_zrecvhdr [%02x] (%d|%d)',self::LOGKEY,$c,$crcgot+1,$got));
|
||||
Log::debug(sprintf('%s:%% ls_zrecvhdr [%02x] (%d|%d)',self::LOGKEY,$c,$crcgot+1,$got));
|
||||
|
||||
if ($crcl === 2) {
|
||||
$crc <<= 8;
|
||||
@ -1832,7 +1833,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
$state = self::rhInit;
|
||||
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zrecvhdr rhXON',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvhdr rhXON',self::LOGKEY));
|
||||
|
||||
switch ($c) {
|
||||
case self::ZPAD:
|
||||
@ -1877,17 +1878,17 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
return self::OK;
|
||||
|
||||
case self::LSZ_BADCRC:
|
||||
Log::debug(sprintf('%s: - ls_zrecvcrcw got BADCRC',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvcrcw got BADCRC',self::LOGKEY));
|
||||
$this->ls_zsendhhdr(self::ZNAK,$this->ls_storelong(0));
|
||||
|
||||
return 1;
|
||||
|
||||
case self::TIMEOUT:
|
||||
Log::debug(sprintf('%s: - ls_zrecvcrcw got TIMEOUT',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zrecvcrcw got TIMEOUT',self::LOGKEY));
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? ls_zrecvcrcw Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? ls_zrecvcrcw Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -1938,7 +1939,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? ls_zrecvnewpos Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? ls_zrecvnewpos Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -2007,7 +2008,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
if ($this->ls_Protocol&self::LSZ_OPTCRC32) {
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zsenddata CRC32',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zsenddata CRC32',self::LOGKEY));
|
||||
|
||||
for ($n=0;$n<strlen($data);$n++)
|
||||
$this->ls_sendchar(ord($data[$n]));
|
||||
@ -2036,7 +2037,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
} else {
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zsenddata CRC16',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zsenddata CRC16',self::LOGKEY));
|
||||
|
||||
for ($n=0;$n<strlen($data);$n++) {
|
||||
$this->ls_sendchar(ord($data[$n]));
|
||||
@ -2144,7 +2145,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
switch (($rc=$this->ls_zsendfinfo($send,$sernum,$send->pos,$fileleft,$bytesleft))) {
|
||||
/* Ok, It's OK! */
|
||||
case self::ZRPOS:
|
||||
Log::debug(sprintf('%s: - ls_zsendfile ZRPOS to [%d]',self::LOGKEY,$send->pos));
|
||||
Log::debug(sprintf('%s:- ls_zsendfile ZRPOS to [%d]',self::LOGKEY,$send->pos));
|
||||
break;
|
||||
|
||||
/* Skip it */
|
||||
@ -2153,13 +2154,13 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
// @todo Should ZFERR be next to ZABORT?
|
||||
case self::ZFERR:
|
||||
// @todo Mark the file as skipped
|
||||
Log::debug(sprintf('%s: - ls_zsendfile ZSKIP/ZFERR',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zsendfile ZSKIP/ZFERR',self::LOGKEY));
|
||||
return $rc;
|
||||
|
||||
case self::ZABORT:
|
||||
/* Session is aborted */
|
||||
case self::ZFIN:
|
||||
Log::debug(sprintf('%s: - ls_zsendfile ZABORT/ZFIN',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zsendfile ZABORT/ZFIN',self::LOGKEY));
|
||||
$this->ls_zsendhhdr(self::ZFIN,$this->ls_storelong(0));
|
||||
|
||||
return self::ERROR;
|
||||
@ -2185,7 +2186,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
/* We need to send ZDATA if previous frame was ZCRCW
|
||||
Also, frame will be ZCRCW, if it is after RPOS */
|
||||
if ($frame === self::ZCRCW) {
|
||||
Log::debug(sprintf('%s: - ls_zsendfile send ZDATA at [%d]',self::LOGKEY,$send->pos));
|
||||
Log::debug(sprintf('%s:- ls_zsendfile send ZDATA at [%d]',self::LOGKEY,$send->pos));
|
||||
|
||||
if (($rc=$this->ls_zsendbhdr(self::ZDATA,$this->ls_storelong($send->pos))) < 0)
|
||||
return $rc;
|
||||
@ -2243,11 +2244,11 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
switch (($rc=$this->ls_zrecvhdr($this->ls_rxHdr,$needack ? $this->ls_HeaderTimeout : 0))) { // @todo set timeout to 5 for debugging wtih POP
|
||||
/* They don't need this file */
|
||||
case self::ZSKIP:
|
||||
Log::debug(sprintf('%s: - ls_zsendfile ZSKIP',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zsendfile ZSKIP',self::LOGKEY));
|
||||
|
||||
/* Problems occured -- suspend file */
|
||||
case self::ZFERR:
|
||||
Log::debug(sprintf('%s: - ls_zsendfile ZFERR',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zsendfile ZFERR',self::LOGKEY));
|
||||
return $rc;
|
||||
|
||||
/* Ok, position ACK */
|
||||
@ -2255,18 +2256,18 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
$this->ls_txLastACK = $this->ls_fetchlong($this->ls_rxHdr);
|
||||
|
||||
if (static::DEBUG)
|
||||
Log::debug(sprintf('%s: - ls_zsendfile ZACK',self::LOGKEY),['ls_rxHdr'=>$this->ls_rxHdr,'ls_txLastACK'=>$this->ls_txLastACK,'ls_rxHdr'=>$this->ls_rxHdr]);
|
||||
Log::debug(sprintf('%s:- ls_zsendfile ZACK',self::LOGKEY),['ls_rxHdr'=>$this->ls_rxHdr,'ls_txLastACK'=>$this->ls_txLastACK,'ls_rxHdr'=>$this->ls_rxHdr]);
|
||||
|
||||
break;
|
||||
|
||||
/* Repos */
|
||||
case self::ZRPOS:
|
||||
Log::debug(sprintf('%s: - ZRPOS',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ZRPOS',self::LOGKEY));
|
||||
|
||||
if (($rc=$this->ls_zrpos($send,$this->ls_fetchlong($this->ls_rxHdr))) < 0)
|
||||
return $rc;
|
||||
|
||||
Log::debug(sprintf('%s: - ZRPOS [%d]',self::LOGKEY,$rc));
|
||||
Log::debug(sprintf('%s:- ZRPOS [%d]',self::LOGKEY,$rc));
|
||||
|
||||
/* Force to retransmit ZDATA */
|
||||
$frame = self::ZCRCW;
|
||||
@ -2291,7 +2292,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? ls_zsendfile Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? ls_zsendfile Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -2350,7 +2351,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
|
||||
/* ACK for data -- it lost! */
|
||||
case self::ZACK:
|
||||
Log::debug(sprintf('%s: - ls_zsendfile ZACK after EOF',self::LOGKEY));
|
||||
Log::debug(sprintf('%s:- ls_zsendfile ZACK after EOF',self::LOGKEY));
|
||||
$this->ls_txLastACK = $this->ls_fetchlong($this->ls_rxHdr);
|
||||
|
||||
break;
|
||||
@ -2370,7 +2371,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? ls_zsendfile Something strange after ZEOF [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? ls_zsendfile Something strange after ZEOF [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -2506,7 +2507,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
@ -2628,7 +2629,7 @@ final class Zmodem extends Protocol implements CRCInterface,ZmodemInterface
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s: ? Something strange [%d]',self::LOGKEY,$rc));
|
||||
Log::error(sprintf('%s:? Something strange [%d]',self::LOGKEY,$rc));
|
||||
|
||||
if ($rc < 0)
|
||||
return $rc;
|
||||
|
@ -52,6 +52,99 @@ final class SocketClient {
|
||||
if ($this->type === SOCK_STREAM) {
|
||||
socket_getsockname($connection,$this->address_local,$this->port_local);
|
||||
socket_getpeername($connection,$this->address_remote,$this->port_remote);
|
||||
|
||||
// If HAPROXY is used, work get the clients address
|
||||
if (config('fido.haproxy')) {
|
||||
Log::debug(sprintf('%s:+ HAPROXY connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
|
||||
|
||||
if ($this->read(5,12) !== "\x0d\x0a\x0d\x0a\x00\x0d\x0aQUIT\x0a") {
|
||||
Log::error(sprintf('%s:! Failed to initialise HAPROXY connection',self::LOGKEY));
|
||||
throw new SocketException(SocketException::CANT_CONNECT,'Failed to initialise HAPROXY connection');
|
||||
}
|
||||
|
||||
// Version/Command
|
||||
$vc = $this->read_ch(5);
|
||||
|
||||
if (($x=($vc>>4)&0x7) !== 2) {
|
||||
Log::error(sprintf('%s:! HAPROXY version [%d] is not handled',self::LOGKEY,$x));
|
||||
|
||||
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY version');
|
||||
}
|
||||
|
||||
switch ($x=($vc&0x7)) {
|
||||
// HAPROXY internal
|
||||
case 0:
|
||||
Log::debug(sprintf('%s:! HAPROXY internal health-check',self::LOGKEY));
|
||||
throw new SocketException(SocketException::CANT_CONNECT,'Healthcheck');
|
||||
|
||||
// PROXY connection
|
||||
case 1:
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s:! HAPROXY command [%d] is not handled',self::LOGKEY,$x));
|
||||
|
||||
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY command');
|
||||
}
|
||||
|
||||
// Protocol/Address Family
|
||||
$pa = $this->read_ch(5);
|
||||
$p = NULL;
|
||||
|
||||
switch ($x=($pa>>4)&0x7) {
|
||||
case 1: // AF_INET
|
||||
$p = 4;
|
||||
break;
|
||||
|
||||
case 2: // AF_INET6
|
||||
$p = 6;
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s:! HAPROXY protocol [%d] is not handled',self::LOGKEY,$x));
|
||||
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY protocol');
|
||||
}
|
||||
|
||||
switch ($x=($pa&0x7)) {
|
||||
case 1: // STREAM
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('%s:! HAPROXY address family [%d] is not handled',self::LOGKEY,$x));
|
||||
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY address family');
|
||||
}
|
||||
|
||||
$len = Arr::get(unpack('n',$this->read(5,2)),1);
|
||||
|
||||
// IPv4
|
||||
if (($p === 4) && ($len === 12)) {
|
||||
$src = inet_ntop($this->read(5,4));
|
||||
$dst = inet_ntop($this->read(5,4));
|
||||
|
||||
} elseif (($p === 6) && ($len === 36)) {
|
||||
$src = inet_ntop($this->read(5,16));
|
||||
$dst = inet_ntop($this->read(5,16));
|
||||
|
||||
} else {
|
||||
Log::error(sprintf('%s:! HAPROXY address len [%d:%d] is not handled',self::LOGKEY,$p,$len));
|
||||
throw new SocketException(SocketException::CANT_CONNECT,'Unknown HAPROXY address length');
|
||||
}
|
||||
|
||||
$src_port = unpack('n',$this->read(5,2));
|
||||
$dst_port = unpack('n',$this->read(5,2));
|
||||
|
||||
$this->address_remote = $src;
|
||||
$this->port_remote = Arr::get($src_port,1);
|
||||
|
||||
Log::info(sprintf('%s:! HAPROXY src [%s:%d] dst [%s:%d]',
|
||||
self::LOGKEY,
|
||||
$this->address_remote,
|
||||
$this->port_remote,
|
||||
$dst,
|
||||
Arr::get($dst_port,1),
|
||||
));
|
||||
}
|
||||
|
||||
Log::info(sprintf('%s:+ Connection host [%s] on port [%d] (%s)',self::LOGKEY,$this->address_remote,$this->port_remote,$this->type));
|
||||
}
|
||||
}
|
||||
@ -108,6 +201,12 @@ final class SocketClient {
|
||||
|
||||
$sort = collect(['AAAA','A']);
|
||||
|
||||
if (filter_var($address,FILTER_VALIDATE_IP))
|
||||
$resolved = collect([[
|
||||
(($x=filter_var($address,FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) ? 'ipv6' : 'ip')=>$address,
|
||||
'type'=>$x ? 'AAAA' : 'A'
|
||||
]]);
|
||||
else
|
||||
// We only look at AAAA/A records
|
||||
$resolved = collect(dns_get_record($address,DNS_AAAA|DNS_A))
|
||||
->filter(function($item) use ($sort) { return $sort->search(Arr::get($item,'type')) !== FALSE; })
|
||||
@ -320,7 +419,7 @@ final class SocketClient {
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s: - socket_recv Exception [%s]',self::LOGKEY,$e->getMessage()));
|
||||
Log::error(sprintf('%s:! socket_recv Exception [%s]',self::LOGKEY,$e->getMessage()));
|
||||
|
||||
throw new SocketException($x=socket_last_error($this->connection),socket_strerror($x));
|
||||
}
|
||||
|
@ -128,8 +128,9 @@ final class SocketServer {
|
||||
try {
|
||||
$r = new SocketClient($accept);
|
||||
|
||||
} catch (\ErrorException $e) {
|
||||
} catch (\Exception $e) {
|
||||
Log::error(sprintf('%s:! Creating Socket client failed? [%s]',self::LOGKEY,$e->getMessage()));
|
||||
socket_close($accept);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
37
app/Console/Commands/AddressIdle.php
Normal file
37
app/Console/Commands/AddressIdle.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Jobs\AddressIdle as Job;
|
||||
use App\Models\{Address,Domain};
|
||||
|
||||
class AddressIdle extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'address:idle'
|
||||
.' {domain : Domain}'
|
||||
.' {--ftn= : Limit to specific address}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Find and mark nodes as hold/down/delist if idle';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$do = Domain::where('name',$this->argument('domain'))->singleOrFail();
|
||||
|
||||
return Job::dispatchSync($do,$this->option('ftn') ? Address::findFTN($this->option('ftn')) : NULL);
|
||||
}
|
||||
}
|
93
app/Console/Commands/Areafix/Rescan.php
Normal file
93
app/Console/Commands/Areafix/Rescan.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Areafix;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\{Address,Echoarea,Echomail};
|
||||
|
||||
class Rescan extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'areafix:rescan {ftn} {area} {days?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Resend some echomail to a node';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
if (($this->argument('days')) && (! is_numeric($this->argument('days'))))
|
||||
throw new \Exception('Days must be numeric: '.$this->argument('days'));
|
||||
|
||||
$ao = Address::findFtn($this->argument('ftn'));
|
||||
|
||||
if (! $ao)
|
||||
throw new \Exception('FTN not found: '.$this->argument('ftn'));
|
||||
|
||||
// Check that the area belongs to the domain for the FTN
|
||||
if (! $this->argument('area'))
|
||||
throw new \Exception('Areaname is required');
|
||||
|
||||
$eao = Echoarea::where('name',$this->argument('area'))->singleOrFail();
|
||||
if ($eao->domain_id !== $ao->zone->domain_id)
|
||||
throw new \Exception(sprintf('Echo area [%s] is not in domain [%s] for FTN [%s]',$eao->name,$ao->zone->domain->name,$ao->ftn));
|
||||
|
||||
// Check that the user is subscribed
|
||||
if (! $ao->echoareas->contains($eao->id))
|
||||
throw new \Exception(sprintf('FTN [%s] is not subscribed to [%s]',$ao->ftn,$eao->name));
|
||||
|
||||
// Check that an FTN can read the area
|
||||
if (! $eao->can_read($ao->security))
|
||||
throw new \Exception(sprintf('FTN [%s] doesnt have permission to receive [%s]',$ao->ftn,$eao->name));
|
||||
|
||||
foreach (Echomail::select('id')
|
||||
->where('echoarea_id',$eao->id)
|
||||
->when($this->argument('days'),function($query) {
|
||||
return $query->where('created_at','>=',Carbon::now()->subDays($this->argument('days'))->startOfDay());
|
||||
})
|
||||
->orderBy('datetime')
|
||||
->cursor() as $eo) {
|
||||
|
||||
// Echomail hasnt been exported before
|
||||
if (! $eo->seenby->count()) {
|
||||
$eo->seenby()->attach($ao->id,['export_at'=>Carbon::now()]);
|
||||
$this->info(sprintf('Exported [%d] to [%s]',$eo->id,$ao->ftn3d));
|
||||
|
||||
} else {
|
||||
$export = $eo->seenby->where('id',$ao->id)->pop();
|
||||
|
||||
// Echomail is pending export
|
||||
if ($export && $export->pivot->export_at && is_null($export->pivot->sent_at) && is_null($export->pivot->sent_pkt)) {
|
||||
$this->warn(sprintf('Not exporting [%d] already queued for [%s]',$eo->id,$ao->ftn3d));
|
||||
|
||||
// Echomail has been exported
|
||||
} elseif ($export) {
|
||||
$eo->seenby()->updateExistingPivot($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL,'sent_pkt'=>NULL]);
|
||||
$this->info(sprintf('Re-exported [%d] to [%s]',$eo->id,$ao->ftn3d));
|
||||
|
||||
// Echomail has not been exported
|
||||
} else {
|
||||
$eo->seenby()->attach($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL,'sent_pkt'=>NULL]);
|
||||
$this->info(sprintf('Exported [%d] to [%s]',$eo->id,$ao->ftn3d));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\BBS;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@ -13,7 +13,7 @@ class ANSIDecode extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ansi:decode'
|
||||
protected $signature = 'bbs:ansi:decode'
|
||||
.' {file : ANS file to decode}';
|
||||
|
||||
/**
|
||||
@ -26,10 +26,12 @@ class ANSIDecode extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
echo ANSI::ansi($this->argument('file'));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\BBS;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@ -13,7 +13,7 @@ class ANSIEncode extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ansi:encode'
|
||||
protected $signature = 'bbs:ansi:encode'
|
||||
.' {file : ANS file to encode}';
|
||||
|
||||
/**
|
||||
@ -26,9 +26,9 @@ class ANSIEncode extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
foreach (ANSI::binary($this->argument('file')) as $line) {
|
||||
foreach (str_split(bin2hex($line),2) as $y)
|
||||
@ -36,5 +36,7 @@ class ANSIEncode extends Command
|
||||
|
||||
echo "\r";
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ class FrameImport extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'frame:import {frame} {file} '.
|
||||
protected $signature = 'bbs:frame:import {frame} {file} '.
|
||||
'{--index=a : The frame index }'.
|
||||
'{--access=0 : Is frame accessible }'.
|
||||
'{--public=0 : Is frame limited to CUG }'.
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Inspired by Rob O'Donnell at irrelevant.com
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\BBS;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@ -16,7 +16,7 @@ use App\Classes\Sock\{SocketException,SocketServer};
|
||||
use App\Models\Mode;
|
||||
use App\Models\Setup;
|
||||
|
||||
class BBSStart extends Command
|
||||
class Start extends Command
|
||||
{
|
||||
private const LOGKEY = 'CBS';
|
||||
|
||||
@ -52,7 +52,7 @@ class BBSStart extends Command
|
||||
'address'=>$o->ansitex_bind,
|
||||
'port'=>$o->ansitex_port,
|
||||
'proto'=>SOCK_STREAM,
|
||||
'class'=>new Ansitex($o),
|
||||
'class'=>new Ansitex,
|
||||
]);
|
||||
|
||||
if (TRUE || $o->viewdata_active)
|
||||
@ -60,7 +60,7 @@ class BBSStart extends Command
|
||||
'address'=>$o->videotex_bind,
|
||||
'port'=>$o->videotex_port,
|
||||
'proto'=>SOCK_STREAM,
|
||||
'class'=>new Videotex($o),
|
||||
'class'=>new Videotex,
|
||||
]);
|
||||
|
||||
$children = collect();
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\Sock\SocketException;
|
||||
use App\Classes\Sock\SocketServer;
|
||||
use App\Classes\Protocol\Zmodem as ZmodemClass;
|
||||
|
||||
class CommZmodemReceive extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'comm:zmodem:receive';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'ZMODEM receive';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws SocketException
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Log::info('Listening for ZMODEM connections...');
|
||||
|
||||
$server = new SocketServer(60177,'0.0.0.0');
|
||||
$server->handler = [new ZmodemClass,'onConnect'];
|
||||
|
||||
try {
|
||||
$server->listen();
|
||||
|
||||
} catch (SocketException $e) {
|
||||
if ($e->getMessage() === 'Can\'t accept connections: "Success"')
|
||||
Log::debug('Server Terminated');
|
||||
else
|
||||
Log::emergency('Uncaught Message: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\Protocol;
|
||||
use App\Classes\Protocol\Zmodem as ZmodemClass;
|
||||
use App\Classes\Sock\SocketClient;
|
||||
|
||||
class CommZmodemSend extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'comm:zmodem:send {ip}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'ZMODEM send';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Log::info('Call ZMODEM send');
|
||||
|
||||
[$address,$service_port] = explode(':',$this->argument('ip'),2);
|
||||
$client = SocketClient::create($address,$service_port);
|
||||
|
||||
$o = new ZmodemClass;
|
||||
$o->session(Protocol::SESSION_ZMODEM,$client);
|
||||
|
||||
Log::info(sprintf('Connection ended: %s',$client->address_remote),['m'=>__METHOD__]);
|
||||
}
|
||||
}
|
34
app/Console/Commands/Debug/AddressCheck.php
Normal file
34
app/Console/Commands/Debug/AddressCheck.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\Address;
|
||||
|
||||
class AddressCheck extends Command
|
||||
{
|
||||
protected $signature = 'debug:address:check'
|
||||
.' {ftn : FTN}';
|
||||
|
||||
protected $description = 'Check the addresses we use for a node';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$o = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
if (! $o) {
|
||||
$this->error(sprintf('Address: %s doesnt exist?',$this->argument('ftn')));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->info(sprintf('Address: %s (%s)',$o->ftn,$o->role_name));
|
||||
$this->info(sprintf("Children: \n- %s",$o->children()->pluck('ftn4d')->join("\n- ")));
|
||||
$this->info(sprintf("Downstream: \n- %s",$o->downstream()->pluck('ftn4d')->join("\n- ")));
|
||||
$this->info(sprintf('Uplink: %s (Parent: %s)',$o->uplink()?->ftn,$o->parent()?->ftn));
|
||||
$this->info(sprintf('Our Address: %s',our_address($o)?->ftn));
|
||||
$this->info(sprintf('- Domain Addresses: %s',our_address($o->zone->domain)->pluck('ftn4d')->join(',')));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
50
app/Console/Commands/Debug/AddressCheckRole.php
Normal file
50
app/Console/Commands/Debug/AddressCheckRole.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use App\Models\Address;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class AddressCheckRole extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'debug:address:check:role {--f|fix : Fix the role}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check address roles and optionally fix';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
foreach (Address::withTrashed()->with(['zone.domain'])->cursor() as $o) {
|
||||
// Trim the role bit from role, since we now work out a role automatically.
|
||||
// @todo This doesnt work, because role_id returns back the overridden role, and thus would remove it
|
||||
if (($o->role & Address::NODE_ALL) === $o->role_id) {
|
||||
$o->role &= ~$o->role_id;
|
||||
|
||||
if ((! $o->role) || ($o->role === Address::NODE_UNKNOWN))
|
||||
$o->role = NULL;
|
||||
|
||||
if ($o->getDirty())
|
||||
if ($this->option('fix')) {
|
||||
$o->save();
|
||||
|
||||
} else {
|
||||
$this->warn(sprintf('Not changing [%s](%s) from [%d] to [%d]',$o->ftn,$o->role_name,$o->getOriginal('role'),$o->role));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
use App\Models\Address;
|
||||
use App\Models\{Address,System};
|
||||
|
||||
class AddressMerge extends Command
|
||||
{
|
||||
@ -15,7 +15,7 @@ class AddressMerge extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'address:merge'
|
||||
protected $signature = 'debug:address:merge'
|
||||
.' {src : Source Address}'
|
||||
.' {dst : Destination Address}'
|
||||
.' {--F|force : Force}'
|
||||
@ -34,79 +34,84 @@ class AddressMerge extends Command
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$src = Address::withTrashed()->findOrfail($this->argument('src'));
|
||||
$dst = Address::withTrashed()->findOrfail($this->argument('dst'));
|
||||
|
||||
if ((! $this->option('ignore')) && ($src->system_id !== $dst->system_id) && ($src->system->name !== 'Discovered System')) {
|
||||
if ((! $this->option('ignore')) && ($src->system_id !== $dst->system_id) && ($src->system->name !== System::default)) {
|
||||
$this->error(sprintf('FTN addresses are from different systems (%s/%s)',$src->system->name,$dst->system->name));
|
||||
exit(1);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if ((! $this->option('force')) && ($src->ftn !== $dst->ftn)) {
|
||||
$this->error(sprintf('FTN addresses are not the same (%s:%s)',$src->ftn,$dst->ftn));
|
||||
exit(1);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if ($src->active) {
|
||||
$this->error(sprintf('Source [%s] is still active',$src->ftn));
|
||||
exit(1);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
// Find all echomail seenbys
|
||||
$x = DB::update('update echomail_seenby set address_id=? where address_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE echomail_seenby SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
|
||||
$this->info(sprintf('Updated [%d] echomail seenby records',$x));
|
||||
|
||||
// Find all echomail paths
|
||||
$x = DB::update('update echomail_path set address_id=? where address_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE echomail_path SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
|
||||
$this->info(sprintf('Updated [%d] echomail path records',$x));
|
||||
|
||||
// Find all echomails
|
||||
$x = DB::update('update echomails set fftn_id=? where fftn_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE echomails SET fftn_id=? WHERE fftn_id=?',[$dst->id,$src->id]);
|
||||
$this->info(sprintf('Updated [%d] echomail source records',$x));
|
||||
|
||||
// Find all netmails
|
||||
$x = DB::update('update netmails set fftn_id=? where fftn_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE netmails SET fftn_id=? WHERE fftn_id=?',[$dst->id,$src->id]);
|
||||
$this->info(sprintf('Updated [%d] netmail source records',$x));
|
||||
|
||||
// Find all netmails
|
||||
$x = DB::update('update netmails set tftn_id=? where tftn_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE netmails SET tftn_id=? WHERE tftn_id=?',[$dst->id,$src->id]);
|
||||
$this->info(sprintf('Updated [%d] netmail destination records',$x));
|
||||
|
||||
// Find all nodelist
|
||||
$x = DB::update('update address_nodelist set address_id=? where address_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE address_nodelist SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
|
||||
$this->info(sprintf('Updated [%d] nodelist records',$x));
|
||||
|
||||
// Find all file seenbys
|
||||
$x = DB::update('update file_seenby set address_id=? where address_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE file_seenby SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
|
||||
$this->info(sprintf('Updated [%d] file seenby records',$x));
|
||||
|
||||
// Find all files
|
||||
$x = DB::update('update files set fftn_id=? where fftn_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE files SET fftn_id=? WHERE fftn_id=?',[$dst->id,$src->id]);
|
||||
$this->info(sprintf('Updated [%d] file source records',$x));
|
||||
|
||||
// Resubscribe echoareas
|
||||
try {
|
||||
$x = DB::update('update address_echoarea set address_id=? where address_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE address_echoarea SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
|
||||
|
||||
} catch (QueryException $e) {
|
||||
DB::rollback();
|
||||
$this->error(sprintf('You may need to remove %s:%s (%d) from echoareas',$src->ftn,$src->system->name,$src->id));
|
||||
exit(1);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info(sprintf('Updated [%d] echomail subscription records',$x));
|
||||
|
||||
// Resubscribe fileareas
|
||||
try {
|
||||
$x = DB::update('update address_filearea set address_id=? where address_id=?',[$dst->id,$src->id]);
|
||||
$x = DB::update('UPDATE address_filearea SET address_id=? WHERE address_id=?',[$dst->id,$src->id]);
|
||||
} catch (QueryException $e) {
|
||||
DB::rollback();
|
||||
$this->error(sprintf('You may need to remove %s:%s (%d) from fileareas',$src->ftn,$src->system->name,$src->id));
|
||||
exit(1);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info(sprintf('Updated [%d] filearea subscription records',$x));
|
||||
@ -117,7 +122,7 @@ class AddressMerge extends Command
|
||||
|
||||
} else {
|
||||
if ($src->forceDelete()) {
|
||||
$this->alert(sprintf('%s deleted.', $src->ftn));
|
||||
$this->alert(sprintf('%s deleted.',$src->ftn));
|
||||
DB::commit();
|
||||
|
||||
} else {
|
||||
@ -126,6 +131,6 @@ class AddressMerge extends Command
|
||||
}
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
36
app/Console/Commands/Debug/DynamicItem.php
Normal file
36
app/Console/Commands/Debug/DynamicItem.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Classes\File\Send;
|
||||
use App\Classes\File\Send\Dynamic;
|
||||
use App\Models\Address;
|
||||
use App\Models\Dynamic as DynamicModel;
|
||||
|
||||
class DynamicItem extends Command
|
||||
{
|
||||
protected $signature = 'debug:dynamic:item'
|
||||
.' {name : Dynamic Item}'
|
||||
.' {ftn : FTN Address}';
|
||||
|
||||
protected $description = 'Generate a dynamic item';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$do = DynamicModel::where('name',$this->argument('name'))->single();
|
||||
|
||||
if (! $do)
|
||||
throw new \Exception(sprintf('Dynamic Item [%s] doesnt exist?',$this->argument('name')));
|
||||
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
$d = new Dynamic($do,$ao,Send::T_FILE);
|
||||
|
||||
$d->open();
|
||||
echo $d->read($d->size);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@ -14,7 +14,7 @@ class EchomailDump extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'echomail:dump {id}';
|
||||
protected $signature = 'debug:echomail:dump {id}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -26,10 +26,12 @@ class EchomailDump extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
dump(Echomail::findOrFail($this->argument('id')));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
@ -15,7 +15,7 @@ class NetmailTest extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'netmail:test'
|
||||
protected $signature = 'debug:netmail:test'
|
||||
.' {ftn : FTN to send netmail to}';
|
||||
|
||||
/**
|
||||
@ -28,13 +28,15 @@ class NetmailTest extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle()
|
||||
public function handle():int
|
||||
{
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
Notification::route('netmail',$ao)->notify(new NetmailTestNotification());
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
62
app/Console/Commands/Debug/PacketAddress.php
Normal file
62
app/Console/Commands/Debug/PacketAddress.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\{Echomail,Netmail};
|
||||
use App\Models\Address;
|
||||
|
||||
class PacketAddress extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'debug:packet:address'
|
||||
.' {ftn : FTN Address}'
|
||||
.' {type : Message Type}'
|
||||
.' {dbid : Message ID}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate a mail packet for a system';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
switch ($this->argument('type')) {
|
||||
case 'echomail':
|
||||
$o = new Echomail;
|
||||
break;
|
||||
|
||||
case 'netmail':
|
||||
$o = new Netmail;
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->error('Unknown type: '.$this->argument('type'));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo hex_dump($ao
|
||||
->system
|
||||
->packet($ao)
|
||||
->mail($o->where('id',$this->argument('dbid'))->get())
|
||||
->generate()
|
||||
);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
67
app/Console/Commands/Debug/PacketDump.php
Normal file
67
app/Console/Commands/Debug/PacketDump.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\Address;
|
||||
|
||||
class PacketDump extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'debug:packet:dump'.
|
||||
' {type : Type of packet, netmail|echomail }'.
|
||||
' {ftn : FTN}'.
|
||||
' {file? : filename}'.
|
||||
' {--dump : Dump packet}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create an outgoing FTN packet';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
switch (strtolower($this->argument('type'))) {
|
||||
case 'netmail':
|
||||
$pkt = $ao->getNetmail();
|
||||
break;
|
||||
|
||||
case 'echomail':
|
||||
$pkt = $ao->getEchomail();
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->error('Unknown type: '.$this->argument('type'));
|
||||
throw new \Exception('Unknown type: '.$this->argument('type'));
|
||||
}
|
||||
|
||||
if ($this->option('dump')) {
|
||||
$this->info('Item Name:'.$pkt->name);
|
||||
$this->info('Item Type:'.get_class($pkt));
|
||||
$this->info('Dump:');
|
||||
echo hex_dump($pkt);
|
||||
|
||||
} else {
|
||||
$f = fopen($this->argument('file'),'w+');
|
||||
fputs($f,(string)$pkt);
|
||||
fclose($f);
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
@ -15,7 +15,7 @@ class SendTestEmail extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'email:test {id}';
|
||||
protected $signature = 'debug:email:test {id}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -27,13 +27,15 @@ class SendTestEmail extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$uo = User::find($this->argument('id'));
|
||||
|
||||
Mail::to($uo->email)
|
||||
->send(new MailTest($uo));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
47
app/Console/Commands/Debug/ZoneCheck.php
Normal file
47
app/Console/Commands/Debug/ZoneCheck.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Debug;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\Domain;
|
||||
|
||||
class ZoneCheck extends Command
|
||||
{
|
||||
protected $signature = 'debug:zone:check'
|
||||
.' {domain : Domain Name}'
|
||||
.' {--Z|zone= : Zone}';
|
||||
|
||||
protected $description = 'Check that the addresses in a zone are configured correctly';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$do = Domain::where('name',$this->argument('domain'))->singleOrFail();
|
||||
|
||||
foreach ($do->zones->sortby('zone_id') as $zo) {
|
||||
if ($this->option('zone') && ($this->option('zone') != $zo->zone_id))
|
||||
continue;
|
||||
|
||||
$this->warn('Zone: '.$zo->zone_id);
|
||||
$this->info(sprintf('- Our address(es): %s',our_address($do)->pluck('ftn4d')->join(',')));
|
||||
|
||||
$this->table(['id','ftn','role','parent','children','downlinks','uplink','send from','region_id','system','notes'],$zo->addresses()->FTNorder()->active()->with(['system'])->dontCache()->get()->transform(function($item) {
|
||||
return [
|
||||
'id'=>$item->id,
|
||||
'ftn'=>$item->ftn4d,
|
||||
'role'=>$item->role_name,
|
||||
'parent'=>$item->parent()?->ftn4d,
|
||||
'children'=>$item->children()->count(),
|
||||
'downlinks'=>$item->downlinks()->count(),
|
||||
'uplink'=>($x=$item->uplink())?->ftn4d,
|
||||
'send from'=>$x ? our_address($item->uplink())?->ftn4d : '',
|
||||
'region_id'=>$item->region_id,
|
||||
'system'=>$item->system->name,
|
||||
'notes'=>$item->isRoleOverride() ? 'Role Override' : '',
|
||||
];
|
||||
}));
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -30,9 +30,9 @@ class EchoareaImport extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$do = Domain::where('name',strtolower($this->argument('domain')))->singleOrFail();
|
||||
return Job::dispatchSync($this->argument('file'),$do,$this->option('prefix'),$this->option('unlink'));
|
||||
|
40
app/Console/Commands/FileareaImport.php
Normal file
40
app/Console/Commands/FileareaImport.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\Domain;
|
||||
use App\Jobs\FileareaImport as Job;
|
||||
|
||||
class FileareaImport extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'filearea:import'
|
||||
.' {file : NA File}'
|
||||
.' {domain : Domain}'
|
||||
.' {--P|prefix= : Add prefix to description}'
|
||||
.' {--U|unlink : Delete file after import}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Import Filearea';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$do = Domain::where('name',strtolower($this->argument('domain')))->singleOrFail();
|
||||
return Job::dispatchSync($this->argument('file'),$do,$this->option('prefix'),$this->option('unlink'));
|
||||
}
|
||||
}
|
90
app/Console/Commands/Filefix/Rescan.php
Normal file
90
app/Console/Commands/Filefix/Rescan.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Filefix;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\{Address,Filearea,File};
|
||||
|
||||
class Rescan extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'filefix:rescan {ftn} {area} {file?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Resend some files to a node';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$ao = Address::findFtn($this->argument('ftn'));
|
||||
|
||||
if (! $ao)
|
||||
throw new \Exception('FTN not found: '.$this->argument('ftn'));
|
||||
|
||||
// Check that the area belongs to the domain for the FTN
|
||||
if (! $this->argument('area'))
|
||||
throw new \Exception('Areaname is required');
|
||||
|
||||
$fao = Filearea::where('name',$this->argument('area'))->singleOrFail();
|
||||
if ($fao->domain_id !== $ao->zone->domain_id)
|
||||
throw new \Exception(sprintf('File area [%s] is not in domain [%s] for FTN [%s]',$fao->name,$ao->zone->domain->name,$ao->ftn));
|
||||
|
||||
// Check that the user is subscribed
|
||||
if (! $ao->fileareas->contains($fao->id))
|
||||
throw new \Exception(sprintf('FTN [%s] is not subscribed to [%s]',$ao->ftn,$fao->name));
|
||||
|
||||
// Check that an FTN can read the area
|
||||
if (! $fao->can_read($ao->security))
|
||||
throw new \Exception(sprintf('FTN [%s] doesnt have permission to receive [%s]',$ao->ftn,$fao->name));
|
||||
|
||||
foreach (File::select('id')
|
||||
->where('filearea_id',$fao->id)
|
||||
->when($this->argument('file'),function($query) {
|
||||
return $query->where('name','=',$this->argument('days'));
|
||||
})
|
||||
->orderBy('datetime')
|
||||
->cursor() as $fo) {
|
||||
|
||||
// File hasnt been exported before
|
||||
if (! $fo->seenby->count()) {
|
||||
$fo->seenby()->attach($ao->id,['export_at'=>Carbon::now()]);
|
||||
$this->info(sprintf('Exported [%d] to [%s]',$fo->id,$ao->ftn3d));
|
||||
|
||||
} else {
|
||||
$export = $fo->seenby->where('id',$ao->id)->pop();
|
||||
|
||||
// File is pending export
|
||||
if ($export && $export->pivot->export_at && is_null($export->pivot->sent_at) && is_null($export->pivot->sent_pkt)) {
|
||||
$this->warn(sprintf('Not exporting [%d] already queued for [%s]',$fo->id,$ao->ftn3d));
|
||||
|
||||
// File has been exported
|
||||
} elseif ($export) {
|
||||
$fo->seenby()->updateExistingPivot($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
|
||||
$this->info(sprintf('Re-exported [%d] to [%s]',$fo->id,$ao->ftn3d));
|
||||
|
||||
// File has not been exported
|
||||
} else {
|
||||
$fo->seenby()->attach($ao,['export_at'=>Carbon::now(),'sent_at'=>NULL]);
|
||||
$this->info(sprintf('Exported [%d] to [%s]',$fo->id,$ao->ftn3d));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ class FilesList extends Command
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$this->table([
|
||||
'files.id' => 'ID',
|
||||
@ -37,6 +37,6 @@ class FilesList extends Command
|
||||
->join('fileareas',['fileareas.id'=>'files.filearea_id'])
|
||||
->cursor());
|
||||
|
||||
return Command::SUCCESS;
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Database\Seeders\InitialSetupSeeder;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class InitialSetup extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'initial:setup';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Initial Setup of DB';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Artisan::call('db:seed',['class'=>InitialSetupSeeder::class]);
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ class JobList extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$lastq = NULL;
|
||||
|
||||
@ -35,16 +35,17 @@ class JobList extends Command
|
||||
$lastq = $o->queue;
|
||||
}
|
||||
|
||||
$this->info(sprintf('%s-%d: %s[%s] - %d tries (Created: %s,Timeout: %s,Next: %s)',
|
||||
$this->info(sprintf('%s-%d: %s[%s] - %d/%d tries [Next:%s]%s',
|
||||
$o->uuid,
|
||||
$o->id,
|
||||
$o->display_name,
|
||||
$o->command->subject,
|
||||
$o->attempts,
|
||||
$o->created_at,
|
||||
$o->retry_until ?: '-',
|
||||
$o->reserved_at ?: '-',
|
||||
$o->command->jobname,
|
||||
$o->attempts,$o->maxTries,
|
||||
$o->available_at ?: '-',
|
||||
$o->attempts ? sprintf(' (Created:%s)',$o->created_at) : ''
|
||||
));
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
79
app/Console/Commands/MailList.php
Normal file
79
app/Console/Commands/MailList.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\Address;
|
||||
|
||||
class MailList extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'mail:list {ftn : FTN address}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'List mail waiting for a node';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$ao = Address::findFTN($this->argument('ftn'),TRUE);
|
||||
|
||||
if (! $ao) {
|
||||
$this->error(sprintf('%s not found?',$this->argument('ftn')));
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('Netmail');
|
||||
$this->table([
|
||||
'id' => 'ID',
|
||||
'msgid' => 'MSGID',
|
||||
'from' => 'FROM',
|
||||
'to' => 'TO',
|
||||
'subject' => 'SUBJECT',
|
||||
],$ao->netmailWaiting()->map(function($item) {
|
||||
return [
|
||||
'id'=>$item->id,
|
||||
'msgid'=>$item->msgid,
|
||||
'from'=>$item->from,
|
||||
'to'=>$item->to,
|
||||
'subject'=>$item->subject,
|
||||
];
|
||||
}));
|
||||
|
||||
$this->info('Echomail');
|
||||
$this->table([
|
||||
'id' => 'ID',
|
||||
'msgid' => 'MSGID',
|
||||
'from' => 'FROM',
|
||||
'to' => 'TO',
|
||||
'subject' => 'SUBJECT',
|
||||
'area' => 'AREA',
|
||||
],$ao->echomailWaiting()->map(function($item) {
|
||||
return [
|
||||
'id'=>$item->id,
|
||||
'msgid'=>$item->msgid,
|
||||
'from'=>$item->from,
|
||||
'to'=>$item->to,
|
||||
'subject'=>$item->subject,
|
||||
'area'=>$item->echoarea->name,
|
||||
];
|
||||
}));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ class MailSend extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
switch ($this->option('type')) {
|
||||
case 'crash':
|
||||
@ -48,5 +48,7 @@ class MailSend extends Command
|
||||
default:
|
||||
$this->error('Specify -T crash, normal or all');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ class NodelistImport extends Command
|
||||
protected $signature = 'nodelist:import'
|
||||
.' {file : File ID | filename}'
|
||||
.' {domain? : Domain Name}'
|
||||
.' {--I|ignorecrc : Ignore the CRC}'
|
||||
.' {--D|delete : Delete old data for the date}'
|
||||
.' {--U|unlink : Delete file after import}'
|
||||
.' {--T|test : Dry run}';
|
||||
@ -31,16 +32,26 @@ class NodelistImport extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle():int
|
||||
{
|
||||
try {
|
||||
return Job::dispatchSync(
|
||||
is_numeric($x=$this->argument('file')) ? File::findOrFail($x) : $x,
|
||||
is_numeric($x=$this->argument('file'))
|
||||
? File::findOrFail($x)
|
||||
: sprintf('%s/%s',config('fido.dir'),$this->argument('file')),
|
||||
$this->argument('domain'),
|
||||
$this->option('delete'),
|
||||
$this->option('unlink'),
|
||||
$this->option('test')
|
||||
$this->option('test'),
|
||||
$this->option('ignorecrc'),
|
||||
);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,10 +3,11 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
use App\Classes\File;
|
||||
use App\Classes\FTN\Packet;
|
||||
use App\Models\System;
|
||||
use App\Models\{Address,Echomail};
|
||||
|
||||
class PacketInfo extends Command
|
||||
{
|
||||
@ -17,7 +18,7 @@ class PacketInfo extends Command
|
||||
*/
|
||||
protected $signature = 'packet:info'
|
||||
.' {file : Packet to process}'
|
||||
.' {system? : System the packet is from}';
|
||||
.' {ftn? : FTN the packet is from}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -29,41 +30,63 @@ class PacketInfo extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \App\Classes\FTN\InvalidPacketException
|
||||
* @return int
|
||||
* @throws \App\Exceptions\InvalidPacketException
|
||||
*/
|
||||
public function handle()
|
||||
public function handle():int
|
||||
{
|
||||
$f = new File($this->argument('file'));
|
||||
$s = $this->argument('system') ? System::where('name',$this->argument('system'))->singleOrFail() : NULL;
|
||||
$fs = Storage::disk(config('fido.local_disk'));
|
||||
$rel_name = sprintf('%s/%s',config('fido.dir'),$this->argument('file'));
|
||||
$a = NULL;
|
||||
|
||||
$f = new File($fs->path($rel_name));
|
||||
|
||||
$m = NULL;
|
||||
if ($this->argument('ftn')) {
|
||||
$a = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
} elseif (preg_match(sprintf('/^%s\.(.{3})$/',Packet::regex),$this->argument('file'),$m)) {
|
||||
$a = Address::findOrFail(hexdec($m[1]));
|
||||
}
|
||||
|
||||
foreach ($f as $packet) {
|
||||
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$s);
|
||||
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$a?->zone->domain);
|
||||
|
||||
$this->alert(sprintf('File Name: %s',$x));
|
||||
|
||||
$this->info(sprintf('Packet Type : %s (%s)',$pkt->type,get_class($pkt)));
|
||||
$this->info(sprintf('From : %s to %s',$pkt->fftn,$pkt->tftn));
|
||||
$this->info(sprintf('Dated : %s',$pkt->date));
|
||||
$this->info(sprintf('Password : %s (%s)',$pkt->password,$pkt->password ? 'SET' : 'NOT set'));
|
||||
$this->info(sprintf('Messages : %d',$pkt->messages->count()));
|
||||
$this->info(sprintf('Tosser : %d (%s) version %s',$pkt->software->code,$pkt->software->name,$pkt->software_ver));
|
||||
$this->info(sprintf('Capabilities: %x',$pkt->capability));
|
||||
$this->info(sprintf('From : %s to %s',$pkt->fftn->ftn,$pkt->tftn ? $pkt->tftn->ftn : $pkt->tftn_t));
|
||||
$this->info(sprintf('Dated : %s (%s) [%s]',$pkt->date,$pkt->date->timestamp,$pkt->date->tz->toOffsetName()));
|
||||
$this->info(sprintf('Password : %s (%s)',$pkt->password ?: '-',$pkt->password ? 'SET' : 'NOT set'));
|
||||
$this->info(sprintf('Messages : %d',$pkt->count()));
|
||||
$this->info(sprintf('Tosser : %d (%s) version %s (%x)',$pkt->software->code,$pkt->software->name,$pkt->software_ver,$pkt->product));
|
||||
$this->info(sprintf('Capabilities: %s',$pkt->capability));
|
||||
$this->info(sprintf('Has Errors : %s',$pkt->errors->count() ? 'YES' : 'No'));
|
||||
$this->info(sprintf('Messages : %d',$pkt->count()));
|
||||
|
||||
foreach ($pkt as $msg) {
|
||||
try {
|
||||
$this->warn(sprintf('- Date : %s',$msg->date));
|
||||
$this->warn(sprintf(' - Flags : %s',$msg->flags()->filter()->keys()->join(', ')));
|
||||
$this->warn(sprintf(' - From : %s (%s)',$msg->user_from,$msg->fftn));
|
||||
$this->warn(sprintf(' - To : %s (%s)',$msg->user_to,$msg->tftn));
|
||||
$this->warn(sprintf(' - Subject: %s',$msg->subject));
|
||||
$this->warn(sprintf(' - Area : %s',$msg->echoarea));
|
||||
echo "\n";
|
||||
|
||||
if ($msg->errors)
|
||||
foreach ($msg->errors->errors()->all() as $error)
|
||||
$this->line(' - '.$error);
|
||||
try {
|
||||
$this->warn(sprintf('- Date : %s (%s)',$msg->datetime,$msg->datetime->tz->toOffsetName()));
|
||||
$this->warn(sprintf(' - Errors : %s',$msg->errors->count() ? 'YES' : 'No'));
|
||||
$this->warn(sprintf(' - Flags : %s',$msg->flags()->keys()->join(', ')));
|
||||
$this->warn(sprintf(' - Cost : %d',$msg->cost));
|
||||
$this->warn(sprintf(' - From : %s (%s)',$msg->from,$msg->fftn->ftn));
|
||||
if ($msg instanceof Echomail)
|
||||
$this->warn(sprintf(' - To : %s',$msg->to));
|
||||
else
|
||||
$this->warn(sprintf(' - To : %s (%s)',$msg->to,$msg->tftn->ftn));
|
||||
$this->warn(sprintf(' - Subject: %s',$msg->subject));
|
||||
if ($msg instanceof Echomail)
|
||||
$this->warn(sprintf(' - Area : %s',$msg->echoarea->name));
|
||||
|
||||
if ($msg->errors->count()) {
|
||||
echo "\n";
|
||||
$this->error("Errors:");
|
||||
foreach ($msg->errors->all() as $error)
|
||||
$this->error(' - '.$error);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('! ERROR: '.$e->getMessage());
|
||||
@ -76,15 +99,17 @@ class PacketInfo extends Command
|
||||
foreach ($pkt->errors as $msg) {
|
||||
$this->error(sprintf('- Date: %s',$msg->date));
|
||||
$this->error(sprintf(' - FLAGS: %s',$msg->flags()->filter()->keys()->join(', ')));
|
||||
$this->error(sprintf(' - From: %s (%s)',$msg->user_from,$msg->fftn));
|
||||
$this->error(sprintf(' - To: %s (%s)',$msg->user_to,$msg->tftn));
|
||||
$this->error(sprintf(' - From: %s (%s)',$msg->from,$msg->fftn));
|
||||
$this->error(sprintf(' - To: %s (%s)',$msg->to,$msg->tftn));
|
||||
$this->error(sprintf(' - Subject: %s',$msg->subject));
|
||||
|
||||
foreach ($msg->errors->errors()->all() as $error)
|
||||
foreach ($msg->errors->all() as $error)
|
||||
$this->line(' - '.$error);
|
||||
}
|
||||
|
||||
$this->line("\n");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,39 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Classes\File;
|
||||
use App\Classes\FTN\Packet;
|
||||
use App\Jobs\MessageProcess as Job;
|
||||
use App\Jobs\PacketProcess as Job;
|
||||
use App\Models\Address;
|
||||
|
||||
/**
|
||||
* Things to test
|
||||
* + Packet
|
||||
* - Sender doesnt exist (try and send a bounce message in the same session)
|
||||
* - Sender defined in DB by not ours
|
||||
* - Sender has wrong password
|
||||
* - Packet too old
|
||||
*
|
||||
* + Echomail
|
||||
* - Area doesnt exist (to uplink)
|
||||
* - Sender not subscribed (to uplink)
|
||||
* - Sender cannot post (to uplink)
|
||||
* - Sender has wrong address for echorea domain (to uplink)
|
||||
* - Test message in echoarea
|
||||
* - Echomail from address doesnt match packet envelope (to uplink)
|
||||
* - Echomail too old (to uplink)
|
||||
* - Rescanned dont generate notifications
|
||||
* - Rescanned dont trigger bots
|
||||
* - Some Notifications to an uplink should go to the admin instead?
|
||||
*
|
||||
* + Netmail
|
||||
* - To hub, and user not defined (reject)
|
||||
* - To hub, but user redirect (redirected)
|
||||
* - To areafix (processed)
|
||||
* - To ping (respond)
|
||||
* - With trace turned on (respond)
|
||||
*/
|
||||
class PacketProcess extends Command
|
||||
{
|
||||
/**
|
||||
@ -20,7 +45,8 @@ class PacketProcess extends Command
|
||||
protected $signature = 'packet:process'
|
||||
.' {file : Packet to process}'
|
||||
.' {--N|nobot : Dont process bots}'
|
||||
.' {ftn : System the packet is from}';
|
||||
.' {ftn? : System the packet is from}'
|
||||
.' {--Q|dontqueue : Dont queue the message}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -32,24 +58,28 @@ class PacketProcess extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \App\Classes\FTN\InvalidPacketException
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$f = new File($this->argument('file'));
|
||||
$a = Address::findFTN($this->argument('ftn'));
|
||||
$rel_name = sprintf('%s/%s',config('fido.dir'),$this->argument('file'));
|
||||
|
||||
foreach ($f as $packet) {
|
||||
foreach (Packet::process($packet,$f->itemName(),$f->itemSize(),$a->system) as $msg) {
|
||||
// @todo Quick check that the packet should be processed by us.
|
||||
// @todo validate that the packet's zone is in the domain.
|
||||
$m = [];
|
||||
if ($this->argument('ftn')) {
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
$this->info(sprintf('Processing message from [%s] with msgid [%s] in (%s)',$msg->fboss,$msg->msgid,$f->pktName()));
|
||||
} elseif (preg_match(sprintf('/^%s\.(.{3})$/',Packet::regex),$this->argument('file'),$m)) {
|
||||
$ao = Address::findOrFail(hexdec($m[1]));
|
||||
|
||||
// Dispatch job.
|
||||
Job::dispatchSync($msg,$f->pktName(),$a,$a,Carbon::now(),$this->option('nobot'));
|
||||
}
|
||||
} else {
|
||||
$this->error('Unable to determine sender FTN address');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
Job::dispatchSync($rel_name,$ao->zone->domain,$this->option('dontqueue'));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Models\System;
|
||||
use App\Models\Address;
|
||||
|
||||
class PacketSystem extends Command
|
||||
{
|
||||
@ -14,7 +14,7 @@ class PacketSystem extends Command
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'packet:system'
|
||||
.' {sid : System ID}';
|
||||
.' {ftn : System address}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -26,21 +26,31 @@ class PacketSystem extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \App\Classes\FTN\InvalidPacketException
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$so = System::findOrFail($this->argument('sid'));
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
foreach ($so->addresses as $ao) {
|
||||
$pkt = $ao->getEchomail(FALSE);
|
||||
$this->info(sprintf('System address [%s] has [%d] messages.',$ao->ftn,$pkt?->count()));
|
||||
foreach ($ao->system->addresses->where('validated',TRUE) as $o) {
|
||||
$pkt = $o->getEchomail();
|
||||
$this->info(sprintf('System address [%s] has [%d] echomail messages.',$o->ftn,$pkt?->count()));
|
||||
|
||||
if ($pkt) {
|
||||
foreach ($pkt as $msg)
|
||||
$this->warn(sprintf('- %s',$msg->msgid));
|
||||
$this->warn(sprintf('- %s (%s)',$msg->msgid,$msg->id));
|
||||
}
|
||||
|
||||
$pkt = $o->getNetmail();
|
||||
$this->info(sprintf('System address [%s] has [%d] netmail messages.',$o->ftn,$pkt?->count()));
|
||||
|
||||
if ($pkt) {
|
||||
foreach ($pkt as $msg)
|
||||
$this->warn(sprintf('- %s (%s)',$msg->msgid,$msg->id));
|
||||
}
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -30,10 +30,10 @@ class ServerStart extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
* @return int
|
||||
* @throws SocketException
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
Log::info(sprintf('%s:+ Server Starting (%d)',self::LOGKEY,getmypid()));
|
||||
$o = Setup::findOrFail(config('app.id'));
|
||||
@ -71,7 +71,7 @@ class ServerStart extends Command
|
||||
if (! $start->count()) {
|
||||
Log::alert(sprintf('%s:! No servers configured to start',self::LOGKEY));
|
||||
|
||||
return;
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
pcntl_signal(SIGCHLD,SIG_IGN);
|
||||
@ -105,7 +105,7 @@ class ServerStart extends Command
|
||||
Log::info(sprintf('%s:= Finished: [%s]',self::LOGKEY,$item));
|
||||
|
||||
// Child finished we need to get out of this loop.
|
||||
exit;
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('%s:- Forked for [%s] (%d)',self::LOGKEY,$item,$pid));
|
||||
@ -125,5 +125,7 @@ class ServerStart extends Command
|
||||
|
||||
// Done
|
||||
Log::debug(sprintf('%s:= Finished.',self::LOGKEY));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
35
app/Console/Commands/SystemHeartbeat.php
Normal file
35
app/Console/Commands/SystemHeartbeat.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Jobs\SystemHeartbeat as Job;
|
||||
|
||||
class SystemHeartbeat extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'system:heartbeat'
|
||||
.' {--F|force : Force poll systems that are auto hold}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Poll systems that we havent seen for a while';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
Log::info('CSH:- Triggering heartbeat to systems');
|
||||
return Job::dispatchSync($this->option('force'));
|
||||
}
|
||||
}
|
@ -29,11 +29,9 @@ class TicProcess extends Command
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
// Dispatch job.
|
||||
Job::dispatchSync($this->argument('file'),$this->argument('domain'));
|
||||
|
||||
return Command::SUCCESS;
|
||||
return Job::dispatchSync($this->argument('file'),$this->argument('domain'));
|
||||
}
|
||||
}
|
@ -19,14 +19,16 @@ class UserCodeSend extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$ao = Address::findFTN($this->argument('ftn'));
|
||||
$uo = User::where('email',$this->argument('email'))->singleOrFail();
|
||||
|
||||
Notification::route('netmail',$ao->parent())->notify(new AddressLink($uo));
|
||||
Notification::route('netmail',$ao->uplink())->notify(new AddressLink($uo));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
@ -27,12 +27,14 @@ class UserMakeAdmin extends Command
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$o = User::where('email',$this->argument('email'))->firstOrfail();
|
||||
$o->admin = ! $o->admin;
|
||||
$o->save();
|
||||
|
||||
$this->info(sprintf('User [%s] %s an admin',$o->email,$o->admin ? 'IS' : 'is NOT'));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,11 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\MailSend;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
use App\Jobs\{AddressIdleDomain,MailSend,SystemHeartbeat};
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
@ -27,6 +28,8 @@ class Kernel extends ConsoleKernel
|
||||
{
|
||||
$schedule->job(new MailSend(TRUE))->everyMinute()->withoutOverlapping();
|
||||
$schedule->job(new MailSend(FALSE))->twiceDaily(1,13);
|
||||
$schedule->job(new SystemHeartbeat)->hourly();
|
||||
$schedule->job(new AddressIdleDomain)->weeklyOn(0,'01:00');
|
||||
}
|
||||
|
||||
/**
|
||||
|
9
app/Exceptions/InvalidCRCException.php
Normal file
9
app/Exceptions/InvalidCRCException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidCRCException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/InvalidFTNException.php
Normal file
9
app/Exceptions/InvalidFTNException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidFTNException extends Exception
|
||||
{
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\FTN;
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
9
app/Exceptions/InvalidPasswordException.php
Normal file
9
app/Exceptions/InvalidPasswordException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidPasswordException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/NoReadSecurityException.php
Normal file
9
app/Exceptions/NoReadSecurityException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NoReadSecurityException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/NoWriteSecurityException.php
Normal file
9
app/Exceptions/NoWriteSecurityException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NoWriteSecurityException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/NodeNotSubscribedException.php
Normal file
9
app/Exceptions/NodeNotSubscribedException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NodeNotSubscribedException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/TIC/NoFileAreaException.php
Normal file
9
app/Exceptions/TIC/NoFileAreaException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\TIC;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NoFileAreaException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/TIC/NotToMeException.php
Normal file
9
app/Exceptions/TIC/NotToMeException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\TIC;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NotToMeException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/TIC/SizeMismatchException.php
Normal file
9
app/Exceptions/TIC/SizeMismatchException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\TIC;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SizeMismatchException extends Exception
|
||||
{
|
||||
}
|
@ -2,13 +2,14 @@
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
/*
|
||||
@ -38,7 +39,8 @@ class LoginController extends Controller
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest')->except('logout');
|
||||
$this->middleware('guest')
|
||||
->except('logout');
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
@ -70,6 +72,7 @@ class LoginController extends Controller
|
||||
if (file_exists('login_note.txt'))
|
||||
$login_note = file_get_contents('login_note.txt');
|
||||
|
||||
return view('auth.login')->with('login_note',$login_note);
|
||||
return view('auth.login')
|
||||
->with('login_note',$login_note);
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,33 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
use App\Http\Requests\DomainRequest;
|
||||
use App\Models\{Address,Domain,Zone};
|
||||
|
||||
class DomainController extends Controller
|
||||
{
|
||||
// http://ftsc.org/docs/frl-1002.001
|
||||
public const NUMBER_MAX = 0x7fff;
|
||||
/**
|
||||
* Daily stats as shown on the about page
|
||||
*
|
||||
* @param Domain $o
|
||||
* @return Collection
|
||||
*/
|
||||
public function api_daily_stats(Request $request): Collection
|
||||
{
|
||||
$o = Domain::where('name',$request->name)->firstOrFail();
|
||||
|
||||
return $o->echoarea_total_daily()
|
||||
->sortBy('date')
|
||||
->groupBy('date')
|
||||
->transform(function($item,$key) { return [
|
||||
'x'=>\Carbon\Carbon::createFromFormat('Y-m-d',$key)->timestamp,
|
||||
'y'=>$item->sum('count')]; } )
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or edit a domain
|
||||
@ -19,12 +36,12 @@ class DomainController extends Controller
|
||||
public function add_edit(DomainRequest $request,Domain $o)
|
||||
{
|
||||
if ($request->post()) {
|
||||
foreach (['name','dnsdomain','active','public','homepage','notes','flatten'] as $key)
|
||||
foreach (['name','dnsdomain','active','public','homepage','notes','flatten','accept_app','nodestatus_id'] as $key)
|
||||
$o->{$key} = $request->post($key);
|
||||
|
||||
$o->save();
|
||||
|
||||
return redirect()->action([self::class,'home']);
|
||||
return redirect()->to('domain');
|
||||
}
|
||||
|
||||
return view('domain.addedit')
|
||||
@ -40,18 +57,16 @@ class DomainController extends Controller
|
||||
*/
|
||||
public function api_hosts(Zone $o,int $region): Collection
|
||||
{
|
||||
$oo = Address::where('role',Address::NODE_NC)
|
||||
->where('zone_id',$o->id)
|
||||
->when($region,function($query,$region) { return $query->where('region_id',$region); })
|
||||
->when((! $region),function($query) use ($region) { return $query->where('region_id',0); })
|
||||
->where('point_id',0)
|
||||
->FTNorder()
|
||||
->with(['system'])
|
||||
->get();
|
||||
|
||||
return $oo->map(function($item) {
|
||||
return ['id'=>$item->host_id,'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->name)];
|
||||
});
|
||||
return $o->hosts
|
||||
->filter(fn($item)=>$item->role_id === Address::NODE_NC)
|
||||
->filter(fn($item)=>$region ? ($item->region_id === $region) : TRUE)
|
||||
->map(function($item) {
|
||||
return [
|
||||
'id'=>$item->host_id,
|
||||
'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->name)
|
||||
];
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,15 +78,16 @@ class DomainController extends Controller
|
||||
*/
|
||||
public function api_hubs(Zone $o,int $host): Collection
|
||||
{
|
||||
$oo = Address::where('role',Address::NODE_HC)
|
||||
->where('zone_id',$o->id)
|
||||
->when($host,function($query,$host) { return $query->where('host_id',$host)->where('node_id','<>',0); })
|
||||
->with(['system'])
|
||||
->get();
|
||||
|
||||
return $oo->map(function($item) {
|
||||
return ['id'=>$item->id,'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->name)];
|
||||
});
|
||||
return $o->hubs
|
||||
->filter(fn($item)=>$item->role_id === Address::NODE_HC)
|
||||
->filter(fn($item)=>$host ? ($item->host_id === $host) : TRUE)
|
||||
->map(function($item) {
|
||||
return [
|
||||
'id'=>$item->id,
|
||||
'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->name)
|
||||
];
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,22 +98,25 @@ class DomainController extends Controller
|
||||
*/
|
||||
public function api_regions(Zone $o): Collection
|
||||
{
|
||||
$oo = Address::where('role',Address::NODE_RC)
|
||||
->where('zone_id',$o->id)
|
||||
->where('node_id',0)
|
||||
->where('point_id',0)
|
||||
->orderBy('region_id')
|
||||
->active()
|
||||
->with(['system'])
|
||||
->get();
|
||||
|
||||
return $oo->map(function($item) {
|
||||
return ['id'=>$item->region_id,'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->location)];
|
||||
});
|
||||
return $o->regions
|
||||
->filter(fn($item)=>$item->role_id === Address::NODE_RC)
|
||||
->map(function($item) {
|
||||
return [
|
||||
'id'=>$item->region_id,
|
||||
'value'=>sprintf('%s %s',$item->ftn_3d,$item->system->location)
|
||||
];
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
public function home()
|
||||
public function view(Domain $o)
|
||||
{
|
||||
return view('domain.home');
|
||||
if (! $o->public && ! Gate::check('admin',$o))
|
||||
abort(404);
|
||||
|
||||
$o->load(['zones.system','zones.domain','zones.addresses.nodes_hub','zones.addresses.echomail_from']);
|
||||
|
||||
return view('domain.view')
|
||||
->with('o',$o);
|
||||
}
|
||||
}
|
@ -35,15 +35,10 @@ class EchoareaController extends Controller
|
||||
|
||||
$o->save();
|
||||
|
||||
return redirect()->action([self::class,'home']);
|
||||
return redirect()->to('echoarea');
|
||||
}
|
||||
|
||||
return view('echoarea.addedit')
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
public function home()
|
||||
{
|
||||
return view('echoarea.home');
|
||||
}
|
||||
}
|
@ -40,15 +40,10 @@ class FileareaController extends Controller
|
||||
$o->domain->save();
|
||||
}
|
||||
|
||||
return redirect()->action([self::class,'home']);
|
||||
return redirect()->to('filearea');
|
||||
}
|
||||
|
||||
return view('filearea.addedit')
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
public function home()
|
||||
{
|
||||
return view('filearea.home');
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@ -12,7 +13,7 @@ use App\Classes\File;
|
||||
use App\Classes\FTN\Packet;
|
||||
use App\Http\Requests\SetupRequest;
|
||||
use App\Models\File as FileModel;
|
||||
use App\Models\{Address,Domain,Echomail,Netmail,Setup,System};
|
||||
use App\Models\{Address,Echomail,Netmail,Setup,System};
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
@ -38,25 +39,13 @@ class HomeController extends Controller
|
||||
})
|
||||
->get();
|
||||
|
||||
return view('file')
|
||||
return view('widgets.file')
|
||||
->with('f',$f);
|
||||
}
|
||||
|
||||
public function network(Domain $o)
|
||||
{
|
||||
if (! $o->public && ! Gate::check('admin',$o))
|
||||
abort(404);
|
||||
|
||||
$o->load(['zones.system','zones.domain']);
|
||||
|
||||
return view('domain.view')
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
public function packet_contents(System $o,string $packet)
|
||||
{
|
||||
$nm = Netmail::select('netmails.*')
|
||||
->distinct()
|
||||
$nm = Netmail::select(['netmails.id','fftn_id','tftn_id','netmails.datetime'])
|
||||
->leftJoin('netmail_path',['netmail_path.netmail_id'=>'netmails.id'])
|
||||
->where(function($query) use ($o,$packet) {
|
||||
return $query
|
||||
@ -65,37 +54,30 @@ class HomeController extends Controller
|
||||
})
|
||||
->get();
|
||||
|
||||
$em = Echomail::select('echomails.*')
|
||||
->distinct()
|
||||
$em = Echomail::select(['echomails.id','fftn_id','echoarea_id','msgid','datetime'])
|
||||
->leftJoin('echomail_seenby',['echomail_seenby.echomail_id'=>'echomails.id'])
|
||||
->leftJoin('echomail_path',['echomail_path.echomail_id'=>'echomails.id'])
|
||||
->where(function($query) use ($o,$packet) {
|
||||
return $query
|
||||
->where('sent_pkt',$packet)
|
||||
->whereIn('echomail_seenby.address_id',$o->addresses->pluck('id'));
|
||||
})
|
||||
->orWhere(function($query) use ($o,$packet) {
|
||||
->union(
|
||||
Echomail::select(['echomails.id','fftn_id','echoarea_id','msgid','datetime'])
|
||||
->leftJoin('echomail_path',['echomail_path.echomail_id'=>'echomails.id'])
|
||||
->where(function($query) use ($o,$packet) {
|
||||
return $query
|
||||
->where('recv_pkt',$packet)
|
||||
->whereIn('echomail_path.address_id',$o->addresses->pluck('id'));
|
||||
})
|
||||
->with('echoarea')
|
||||
)
|
||||
->with(['echoarea'])
|
||||
->get();
|
||||
|
||||
return view('packet')
|
||||
return view('widgets.packet')
|
||||
->with('nm',$nm)
|
||||
->with('em',$em);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a view that summarises the users permissions
|
||||
*/
|
||||
public function permissions()
|
||||
{
|
||||
return view('auth.permissions')
|
||||
->with('user',Auth::user());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a packet dump
|
||||
*
|
||||
@ -252,4 +234,60 @@ class HomeController extends Controller
|
||||
return view('setup')
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of unsent items
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application
|
||||
* @see \App\Jobs\AddressIdle:class
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
$date = Carbon::now()->yesterday()->endOfday();
|
||||
|
||||
$r = Address::select([
|
||||
'a.id',
|
||||
'a.system_id',
|
||||
'a.zone_id',
|
||||
'addresses.region_id',
|
||||
'a.host_id',
|
||||
'a.node_id',
|
||||
'a.point_id',
|
||||
'addresses.hub_id',
|
||||
'addresses.role',
|
||||
DB::raw('sum(a.uncollected_echomail) as uncollected_echomail'),
|
||||
DB::raw('sum(a.uncollected_netmail) as uncollected_netmail'),
|
||||
DB::raw('sum(a.uncollected_files) as uncollected_files')
|
||||
])
|
||||
->from(
|
||||
Address::UncollectedEchomailTotal()
|
||||
->where('echomails.created_at','<',$this->yesterdayEOD())
|
||||
->union(Address::UncollectedNetmailTotal()
|
||||
->where('netmails.created_at','<',$this->yesterdayEOD())
|
||||
)
|
||||
->union(Address::UncollectedFilesTotal()
|
||||
->where('files.created_at','<',$this->yesterdayEOD())
|
||||
),'a')
|
||||
->where('systems.active',TRUE)
|
||||
->where('addresses.active',TRUE)
|
||||
->where('zones.active',TRUE)
|
||||
->where('domains.active',TRUE)
|
||||
->when(! ($x=Auth::user()) || (! $x->isAdmin()),function($query) { return $query->where('domains.public',TRUE); })
|
||||
->join('addresses',['addresses.id'=>'a.id'])
|
||||
->join('systems',['systems.id'=>'a.system_id'])
|
||||
->join('zones',['zones.id'=>'addresses.zone_id'])
|
||||
->join('domains',['domains.id'=>'zones.domain_id'])
|
||||
->ftnOrder()
|
||||
->groupBy('a.system_id','a.id','a.zone_id','addresses.region_id','a.host_id','a.node_id','a.point_id','addresses.hub_id','addresses.role')
|
||||
->with(['system','zone.domain']);
|
||||
|
||||
return view('status')
|
||||
->with('date',$date)
|
||||
->with('uncollected',$r->get());
|
||||
}
|
||||
|
||||
private function yesterdayEOD(): Carbon
|
||||
{
|
||||
return Carbon::now()->yesterday()->endOfday();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
use App\Http\Requests\UserRequest;
|
||||
@ -23,33 +24,23 @@ class UserController extends Controller
|
||||
|
||||
if (! $o->exists)
|
||||
$o->password = base64_encode(random_bytes(20));
|
||||
elseif ($request->password)
|
||||
$o->password = Hash::make($request->password);
|
||||
|
||||
$o->save();
|
||||
|
||||
if ($o->wasRecentlyCreated)
|
||||
event(new Registered($o));
|
||||
|
||||
return redirect()->action([self::class,'home']);
|
||||
return redirect()
|
||||
->to('user/addedit/'.$o->id)
|
||||
->with('success','User Updated');
|
||||
}
|
||||
|
||||
return view('user.addedit')
|
||||
->with('o',$o);
|
||||
}
|
||||
|
||||
public function dashboard()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$user->load('systems.addresses.zone.domain.echoareas');
|
||||
|
||||
return view('dashboard')
|
||||
->with('user',$user);
|
||||
}
|
||||
|
||||
public function home()
|
||||
{
|
||||
return view('user.home');
|
||||
}
|
||||
|
||||
public function link(Request $request)
|
||||
{
|
||||
if ($request->post()) {
|
||||
@ -59,7 +50,7 @@ class UserController extends Controller
|
||||
]);
|
||||
|
||||
$ao = Address::findOrFail($request->address_id);
|
||||
if ($ao->check_activation(Auth::user(),$request->code)) {
|
||||
if ($ao->activation_check(Auth::user(),$request->code)) {
|
||||
$ao->validated = TRUE;
|
||||
$ao->save();
|
||||
|
||||
@ -79,9 +70,4 @@ class UserController extends Controller
|
||||
|
||||
return view('user.link');
|
||||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
return view('user/system/register');
|
||||
}
|
||||
}
|
@ -96,7 +96,7 @@ class ZoneController extends Controller
|
||||
$ao->active = TRUE;
|
||||
$o->addresses()->save($ao);
|
||||
|
||||
return redirect()->action([self::class,'home']);
|
||||
return redirect()->to('zone');
|
||||
}
|
||||
|
||||
return view('zone.addedit')
|
||||
@ -126,9 +126,4 @@ class ZoneController extends Controller
|
||||
'default' => (bool)$request->set,
|
||||
]);
|
||||
}
|
||||
|
||||
public function home()
|
||||
{
|
||||
return view('zone.home');
|
||||
}
|
||||
}
|
||||
|
201
app/Http/Requests/AddressAdd.php
Normal file
201
app/Http/Requests/AddressAdd.php
Normal file
@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
use App\Models\Address;
|
||||
use App\Rules\{FidoInteger,TwoByteInteger};
|
||||
|
||||
/**
|
||||
* Validation to add an address to a system
|
||||
* o = System::class
|
||||
*
|
||||
* @note This validation only expects to be used with POST
|
||||
* Variables:
|
||||
* + "action" => "node" // type of address being created
|
||||
* + "zone_id" => "2"
|
||||
* + "region_id" => "21"
|
||||
* + "host_id" => "3"
|
||||
* + "hub_id" => null
|
||||
* + "node_id" => "9999"
|
||||
* + "point_id" => "0"
|
||||
* + "region_id_new" => null // creating a new region id
|
||||
* + "host_id_new" => null // creating a new host id
|
||||
* + "node_id_new" => null // creating a new node id
|
||||
* + "hub" => "0"
|
||||
* + "security" => "9"
|
||||
*
|
||||
* Rules:
|
||||
* - ZC is z:0/0.0 - region,node,point must be zero
|
||||
* ZC is identified when region_id,host_id,node_id and point_id === 0
|
||||
* - RC is z:r/0.0 - region is non-zero, host_id = region_id, node,point must be zero.
|
||||
* RC is identified when region_id === host_id and, node_id and point_id === 0
|
||||
* - NC is z:h/0.0 (r=r, r!=h, h!=z) [parent where z:r/0 and h=r, n=0]
|
||||
* NC is identified when region_id !== host_id and, node_id and point_id === 0,
|
||||
* - HC is z:h/n.0 (r=r) [parent pointed by hub_id AND validate by z:h/0 is the hub_id]
|
||||
* HC is a normal node with, but has children pointing to it with hub_id
|
||||
* - NN is z:h/n.0 when point_id === 0
|
||||
* A normal node where node_id !== 0, it may or may not have a hub_id
|
||||
* - PT is z:h/n.p where point_id !== 0
|
||||
* PT is identified when point_id !== 0;
|
||||
*/
|
||||
class AddressAdd extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
session()->flash('accordion','address');
|
||||
|
||||
return request()->isMethod('post')
|
||||
&& Gate::allows('admin',$this->route('o'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(Request $request): array
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
switch ($request->action) {
|
||||
case 'region':
|
||||
$rules = [
|
||||
'region_id_new' => [
|
||||
'required',
|
||||
new TwoByteInteger,
|
||||
// Check that the region doesnt already exist
|
||||
function ($attribute,$value,$fail) use ($request) {
|
||||
$o = Address::where(fn($query)=>
|
||||
$query
|
||||
->where('region_id',$value)
|
||||
// Check that a host doesnt already exist
|
||||
->orWhere('host_id',$value)
|
||||
)
|
||||
->where('zone_id',$request->zone_id)
|
||||
->where('node_id',0)
|
||||
->where('point_id',0)
|
||||
->first();
|
||||
|
||||
if ($o && $o->count())
|
||||
$fail(sprintf('%s already exists [<a href="%s">here</a>]',$o->ftn,url('system/addedit',$o->system_id)));
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'host':
|
||||
$rules = [
|
||||
'region_id' => [
|
||||
'required',
|
||||
new FidoInteger, // @todo the RC should exist, ie: z:r/0.0 (h=0)
|
||||
],
|
||||
'host_id_new' => [
|
||||
'required',
|
||||
new TwoByteInteger,
|
||||
// Make sure that the host isnt already defined
|
||||
function ($attribute,$value,$fail) use ($request) {
|
||||
// Check that the region doesnt already exist
|
||||
$o = Address::where(fn($query)=>
|
||||
$query
|
||||
->where('region_id',$value)
|
||||
// Check that a host doesnt already exist
|
||||
->orWhere('host_id',$value)
|
||||
)
|
||||
->where('zone_id',$request->zone_id)
|
||||
->where('node_id',0)
|
||||
->where('point_id',0)
|
||||
->first();
|
||||
|
||||
if ($o && $o->count())
|
||||
$fail(sprintf('%s already exists [<a href="%s">here</a>]',$o->ftn,url('system/addedit',$o->system_id)));
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
case 'node':
|
||||
$rules = [
|
||||
'region_id' => [
|
||||
'required',
|
||||
new FidoInteger // @todo the RC should exist, ie: z:r/0.0 (h=0)
|
||||
],
|
||||
'host_id' => [
|
||||
'required',new FidoInteger // @todo the NC should exist, ie: z:r/0.0 (h=0)
|
||||
],
|
||||
'node_id' => [
|
||||
'required',
|
||||
new TwoByteInteger,
|
||||
function ($attribute,$value,$fail) use ($request) {
|
||||
if ($request->point_id === 0) {
|
||||
// Check that the host doesnt already exist
|
||||
$o = Address::where(function($query) use ($request,$value) {
|
||||
return $query
|
||||
->where('zone_id',$request->zone_id)
|
||||
->where('host_id',$request->host_id)
|
||||
->where('node_id',$value)
|
||||
->where('point_id',0)
|
||||
->where('id','<>',$request->submit);
|
||||
})
|
||||
->get();
|
||||
|
||||
if ($o && $o->count())
|
||||
$fail(sprintf('%s already exists [<a href="%s">here</a>]',$o->ftn,url('system/addedit',$o->system_id)));
|
||||
}
|
||||
},
|
||||
],
|
||||
'point_id' => [
|
||||
'required',
|
||||
new FidoInteger,
|
||||
function ($attribute,$value,$fail) use ($request) {
|
||||
// Check that the host doesnt already exist
|
||||
$o = Address::where(function($query) use ($request,$value) {
|
||||
return $query
|
||||
->where('zone_id',$request->zone_id)
|
||||
->where('host_id',$request->host_id)
|
||||
->where('node_id',$request->node_id)
|
||||
->where('point_id',$value)
|
||||
->where('id','<>',$request->submit);
|
||||
})
|
||||
->first();
|
||||
|
||||
if ($o && $o->count())
|
||||
$fail(sprintf('%s already exists [<a href="%s">here</a>]',$o->ftn,url('system/addedit',$o->system_id)));
|
||||
}
|
||||
],
|
||||
//'hub' => 'required|boolean',
|
||||
'hub_id' => 'nullable|exists:addresses,id',
|
||||
'submit' => 'nullable|exists:addresses,id',
|
||||
];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return array_merge($rules,
|
||||
[
|
||||
'action' => [
|
||||
'required',
|
||||
'in:region,host,node,update',
|
||||
],
|
||||
'zone_id' => [
|
||||
'required',
|
||||
'exists:zones,id',
|
||||
],
|
||||
'security' => [
|
||||
'nullable',
|
||||
'numeric',
|
||||
'min:0',
|
||||
'max:7',
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
use App\Models\Address;
|
||||
use App\Models\{Address,System};
|
||||
|
||||
class AddressMerge extends FormRequest
|
||||
{
|
||||
@ -14,7 +14,7 @@ class AddressMerge extends FormRequest
|
||||
|
||||
public function authorize()
|
||||
{
|
||||
return Gate::allows( 'admin');
|
||||
return Gate::allows('admin');
|
||||
}
|
||||
|
||||
public function rules(Request $request)
|
||||
@ -35,7 +35,7 @@ class AddressMerge extends FormRequest
|
||||
$dst = Address::withTrashed()->findOrFail($value);
|
||||
$src = Address::withTrashed()->findOrFail($request->src);
|
||||
|
||||
if ((! $dst->active) && ($dst->system_id !== $src->system_id) && ($src->system->name !== 'Discovered System'))
|
||||
if ((! $dst->active) && ($dst->system_id !== $src->system_id) && ($src->system->name !== System::default))
|
||||
$fail('Destination must be active, or be from the system system');
|
||||
},
|
||||
function ($attribute,$value,$fail) use ($request) {
|
||||
|
@ -12,7 +12,7 @@ class AreafixRequest extends FormRequest
|
||||
{
|
||||
public function authorize()
|
||||
{
|
||||
return Gate::allows( 'admin');
|
||||
return Gate::allows('admin');
|
||||
}
|
||||
|
||||
public function rules(Request $request)
|
||||
@ -23,11 +23,11 @@ class AreafixRequest extends FormRequest
|
||||
return [
|
||||
'to' => [
|
||||
'required',
|
||||
Rule::in(config('app.areafilefix')),
|
||||
Rule::in(config('fido.areafilefix')),
|
||||
],
|
||||
'fftn_id' => [
|
||||
'required',
|
||||
Rule::in(Setup::findOrFail(config('app.id'))->system->akas->pluck('id')),
|
||||
Rule::in(our_address()->pluck('id')),
|
||||
],
|
||||
'tftn_id' => [
|
||||
'required',
|
||||
|
@ -5,14 +5,15 @@ namespace App\Http\Requests;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
use App\Models\Domain;
|
||||
use App\Models\{Domain,Echoarea};
|
||||
|
||||
class DomainRequest extends FormRequest
|
||||
{
|
||||
public function authorize(Domain $o)
|
||||
{
|
||||
return Gate::allows( 'admin',$o);
|
||||
return Gate::allows('admin',$o);
|
||||
}
|
||||
|
||||
public function rules(Request $request)
|
||||
@ -27,7 +28,13 @@ class DomainRequest extends FormRequest
|
||||
'dnsdomain' => 'nullable|regex:/^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){1,127}(?![0-9]*$)[a-z0-9-]+\.?)$/i|unique:domains,dnsdomain,'.($o->exists ? $o->id : NULL),
|
||||
'active' => 'required|boolean',
|
||||
'public' => 'required|boolean',
|
||||
'accept_app' => 'required|boolean',
|
||||
'flatten' => 'nullable|boolean',
|
||||
'nodestatus_id' => [
|
||||
'nullable',
|
||||
Rule::exists(Echoarea::class,'id'),
|
||||
Rule::in($o->echoareas->pluck('id')),
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user