commit 29da8777e6a6e08f90cada59ae301cfba4176a2e Author: Alexander Zhirov Date: Fri Apr 25 17:04:22 2025 +0300 init diff --git a/APKBUILD b/APKBUILD new file mode 100644 index 0000000..6f71b50 --- /dev/null +++ b/APKBUILD @@ -0,0 +1,52 @@ +# Maintainer: Alexander Zhirov +pkgname=tftp-hpa-trq +pkgver=5.2 +pkgrel=1 +pkgdesc="Official tftp server with TRQ" +url="https://git.zhirov.kz/alexander/tftp-hpa-trq" +arch="all" +license="BSD" +subpackages="$pkgname-doc" +source="https://www.kernel.org/pub/software/network/tftp/tftp-hpa/tftp-hpa-$pkgver.tar.xz + in.tftpd.initd + in.tftpd.confd + fix-common.patch + trq/trq.patch + trq/trq.c + trq/trq.h + " + +options="!check" + +prepare() { + # После распаковки архива необходимо изменить имя директории на корректное + mv "$srcdir/tftp-hpa-$pkgver" "$srcdir/$pkgname-$pkgver" + default_prepare +} + +build() { + cp $srcdir/trq.c $builddir/tftpd + cp $srcdir/trq.h $builddir/tftpd + + ./configure \ + --build=$CBUILD \ + --host=$CHOST \ + --prefix=/usr \ + --mandir=/usr/share/man + make +} + +package() { + make INSTALLROOT="$pkgdir" install + install -d "$pkgdir"/var/tftpboot + install -Dm755 "$srcdir"/in.tftpd.initd "$pkgdir"/etc/init.d/in.tftpd-trq + install -Dm644 "$srcdir"/in.tftpd.confd "$pkgdir"/etc/conf.d/in.tftpd-trq +} + +sha512sums="a5198e923a6e58281f749dc77b3f3ed8579e56b6f0fd6a17482cc88bdc8d34b6702c7c709717885b9b937ecae459d9a832328a49a2e3536dc7432cdb39d2a394 tftp-hpa-5.2.tar.xz +0d103efc8a6febc94c599d984ba22e3272c422619b548a24953b006c99f2739ad8149543a09b06a50a2367e877117b1ee32f8c79bb81d19a43bccdc7ed1f13a6 in.tftpd.initd +b13d18d2918ece59df61a8239692b91f8b491659b57b63293ca2b99347f0503a213f1f7dd80165478811b11fb1d5537aea6e8fd3de907d880224205c5c65b0bf in.tftpd.confd +40127e7ce276a2bc015255dbeef2665bfe8ff006b1e1d8fb79797e946d6030fd74606cffe0bea11eaad276a693f223faaaf1c1ccb5f276b40fdad40b366172b7 fix-common.patch +fbbf2c15dd1f3fef96672fe0a4b7b424e7ad84ba4eb456c07498d33742662c5859b800c8cdf61c36c5927c00500f560a4f1d48aefaa5d5f5ccbb622538e9140e trq.patch +314eea07339e7c8605a69db65638aacfb488077f39b127bdc81369202d0edae6545a456705cebe08fde3729293b7609892a09d91ec2f7dc93b63d693b10ff833 trq.c +29cc72f3197bcaea4a88dace5c91d53dad5d20a397752198e601e2ed83fdb66fca50a5b4b03c4d3033ebd7a0be1e90801a692c514733412d8d96cafde26e0837 trq.h" diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ab14b9 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# tftp-hpa-trq + +- [Описание](#описание) +- [Пример запроса на `php`](#пример-запроса-на-php) +- [Пример запроса на `python`](#пример-запроса-на-python) +- [Сборка пакета в Docker](#сборка-пакета-в-docker) + - [Генерация ключей](#генерация-ключей) +- [Безопасность](#безопасность) + +![protocol](img/protocol.png) + +## Описание + +К [стандартным](http://isp.vsi.ru/library/Networking/TCPIPIllustrated/tftp_tri.htm) `opcodes` `RRQ` и `WRQ` был добавлен `TRQ` (*Thinstation Request*) со значением `6`, позволяющий создать символическую ссылку на файл-загрузчик для PXE-сервера. + +Структура `filename` принимаемая TFTP-сервером состоит из частей, разделённая `;`: +```sh +01-;; +``` +где +- `01` - указывается как префикс MAC-адреса тонкого клиента +- `mac-address` - MAC-адрес тонкого клиента в нижнем регистре, разделённого `-`, например `ff-ff-ff-ff-ff-ff` +- `command` - команда (`cr`/`rm`) для создания/удаления символической ссылки на файл +- `source-file` - основной файл, на который необходимо создать символическую ссылку + +Символическая ссылка создаётся в директории `mac.cfg` с правами `drwxrwxrwx`, расположенной в корневой директории TFTP сервера. +Например, структура `filename` создания симолической ссылки на файл `thinstation`: + +```sh +01-ff-ff-ff-ff-ff-ff;cr;thinstation +``` + +Удаление символической ссылки: + +```sh +01-ff-ff-ff-ff-ff-ff;rm +``` + +Структура каталога `tftpboot`: + +```sh +. +└── tftpboot +    └── [drwxrwxrwx] mac.cfg + ├── 01-ff-ff-ff-ff-ff-ff -> thinstation +    └── thinstation +``` + +## Пример запроса на PHP + +Создание пакета на PHP для создания символической ссылки, отправляемого на TFTP-сервер: + +```php +$opcode = 6; // TRQ opcode +$request = '01-ff-ff-ff-ff-ff-ff;cr;thinstation'; +$mode = 'octet'; +$ip = '192.168.1.1'; +$port = 69; + +$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); +$package = chr(0) . chr($opcode) . $request . chr(0) . $mode . chr(0); +socket_sendto($sock, $package, strlen($package), MSG_EOF, $ip, $port); +socket_close($sock); +``` + +## Пример запроса на Python + +Создание пакета на Python для создания символической ссылки, отправляемого на TFTP-сервер: + +```python +import socket + +opcode = 6 # TRQ opcode +request = '01-ff-ff-ff-ff-ff-ff;cr;thinstation' +mode = 'octet' +ip = '192.168.0.11' +port = 69 + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +package = chr(0) + chr(opcode) + request + chr(0) + mode + chr(0) +sock.sendto(package.encode(), (ip, port)) +sock.close() +``` + +## Сборка пакета в Docker + +Склонировать репозиторий в удобное место: + +```sh +git clone https://git.zhirov.kz/alexander/tftp-hpa-trq.git +``` + +Войти в оболочку `alpine` для сборки пакета запустив `docker` командой: + +```sh +docker run --rm -it -v $PWD/tftp-hpa-trq:/home/packager/reposerve/main/tftp-hpa-trq alpine:latest /bin/ash +``` + +Установить инструменты разработчика: + +```sh +apk add sudo build-base alpine-sdk +``` + +Cоздать пользователя `packager` и добавьте его в список sudo + +```sh +adduser -D packager +addgroup packager abuild +echo 'packager ALL=(ALL) NOPASSWD:ALL' >/etc/sudoers.d/packager +``` + +Войти под созданным пользователем `packager`: + +```sh +sudo -u packager sh +``` + +Собрать пакет: + +```sh +cd /home/packager/reposerve/main/tftp-hpa-trq +REPODEST=~/packages/latest abuild -r +``` + +Для установки в других экземплярах `alpine` возможно понадобятся сгенерированные ранее ключи для сборки пакета (`/home/packager/.abuild/{*.rsa,*.rsa.pub}`). + +### Генерация ключей + +Сгенерировать ключи: + +```sh +abuild-keygen -a -i +``` + +Или же прокинуть в контейнер свои ключи в `/home/packager/.abuild`. + + +## Безопасность + +Отсутствует проверка на источник запроса, так как сформировать запрос на создание/удаление символической ссылки выполняется в произвольной форме. diff --git a/fix-common.patch b/fix-common.patch new file mode 100644 index 0000000..1f082e8 --- /dev/null +++ b/fix-common.patch @@ -0,0 +1,24 @@ +diff -urN tftp-hpa-5.2.orig/tftp/main.c tftp-hpa-5.2/tftp/main.c +--- tftp-hpa-5.2.orig/tftp/main.c 2020-11-14 22:21:15.851650899 -0700 ++++ tftp-hpa-5.2/tftp/main.c 2020-11-14 22:21:41.878327755 -0700 +@@ -95,7 +95,7 @@ + int margc; + char *margv[20]; + const char *prompt = "tftp> "; +-sigjmp_buf toplevel; ++static sigjmp_buf toplevel; + void intr(int); + struct servent *sp; + int portrange = 0; +diff -urN tftp-hpa-5.2.orig/tftp/tftp.c tftp-hpa-5.2/tftp/tftp.c +--- tftp-hpa-5.2.orig/tftp/tftp.c 2020-11-14 22:21:15.851650899 -0700 ++++ tftp-hpa-5.2/tftp/tftp.c 2020-11-14 22:21:51.304998113 -0700 +@@ -48,7 +48,7 @@ + #define PKTSIZE SEGSIZE+4 + char ackbuf[PKTSIZE]; + int timeout; +-sigjmp_buf toplevel; ++static sigjmp_buf toplevel; + sigjmp_buf timeoutbuf; + + static void nak(int, const char *); diff --git a/img/protocol.png b/img/protocol.png new file mode 100644 index 0000000..403d091 Binary files /dev/null and b/img/protocol.png differ diff --git a/in.tftpd.confd b/in.tftpd.confd new file mode 100644 index 0000000..8cc6bd7 --- /dev/null +++ b/in.tftpd.confd @@ -0,0 +1,15 @@ +# /etc/init.d/in.tftpd-trq + +# Path to server files from +# Depending on your application you may have to change this. +INTFTPD_PATH="/var/tftpboot/" +#INTFTPD_PATH="/var/tftp/" +#INTFTPD_PATH="/tftpboot/" +#INTFTPD_PATH="/tftproot/" + +# For more options, see in.tftpd-trq(8) +# -R 4096:32767 solves problems with ARC firmware, and obsoletes +# the /proc/sys/net/ipv4/ip_local_port_range hack. +# -s causes $INTFTPD_PATH to be the root of the TFTP tree. +# -l is passed by the init script in addition to these options. +INTFTPD_OPTS="-R 4096:32767 -s ${INTFTPD_PATH}" diff --git a/in.tftpd.initd b/in.tftpd.initd new file mode 100644 index 0000000..c27b11f --- /dev/null +++ b/in.tftpd.initd @@ -0,0 +1,21 @@ +#!/sbin/openrc-run +# Copyright 1999-2005 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-x86/net-ftp/tftp-hpa/files/in.tftpd.rc6,v 1.2 2005/07/30 06:29:14 vapier Exp $ + +depend() { + need net + after firewall +} + +start() { + ebegin "Starting tftpd-trq" + /usr/sbin/in.tftpd-trq -l ${INTFTPD_OPTS} + eend $? +} + +stop() { + ebegin "Stopping tftpd-trq" + start-stop-daemon --stop --exec /usr/sbin/in.tftpd-trq + eend $? +} diff --git a/trq/trq.c b/trq/trq.c new file mode 100644 index 0000000..27bec57 --- /dev/null +++ b/trq/trq.c @@ -0,0 +1,173 @@ +/* + * trq.c + * + * Created on: 17 авг. 2022 г. + * Author: Alexander Zhirov + * Mail: alexander@zhirov.website + * Telegram: alexanderzhirov + */ + +#include "trq.h" + +#include +#include +#include +#include +#include +#include + +void ts_free(ts_args *args) +{ + if (args) + { + if (args->command) + free(args->command); + if (args->file_src) + free(args->file_src); + if (args->file_dst) + free(args->file_dst); + if (args->path_src) + free(args->path_src); + if (args->command) + free(args->path_dst); + free(args); + } +} + +static char* ts_concat_path(const char *file) +{ + if (!file) + { + syslog(LOG_NOTICE, "TRQ Invalid value file (ts_concat_path)\n"); + exit(3); + } + const char *path_mac = "mac.cfg/"; + size_t len1 = strlen(path_mac); + size_t len2 = strlen(file); + char *result = (char*) malloc(sizeof(char) * (len1 + len2 + 1)); + if (!result) + { + syslog(LOG_NOTICE, "TRQ Disk full or allocation exceeded (ts_concat_path)\n"); + exit(3); + } + strncpy(result, path_mac, (len1 + 1)); + strncpy(result + len1, file, (len2 + 1)); + return result; +} + +static char* ts_copy_string(const char *string) +{ + if (!string) + return NULL; + int len = strlen(string); + char *tmp = (char*) calloc(1, sizeof(char) * (len + 1)); + if (!tmp) + { + syslog(LOG_NOTICE, "TRQ Disk full or allocation exceeded (ts_copy_string)\n"); + exit(3); + } + strncpy(tmp, string, (len + 1)); + return tmp; +} + +ts_args* ts_get_arguments(const char *filename, const char *path) +{ + ts_args *args = (ts_args*) calloc(1, sizeof(ts_args)); + if (!args) + { + syslog(LOG_NOTICE, "TRQ Disk full or allocation exceeded (ts_get_arguments)\n"); + exit(3); + } + char separator[2] = ";\0"; + char *part = NULL; + int len = strlen(filename); + int next = 1; /* reading the next iteration */ + char *tmp_filename = (char*) calloc(1, sizeof(char) * (len + 1)); + if (!tmp_filename) + { + syslog(LOG_NOTICE, "TRQ Disk full or allocation exceeded (ts_get_arguments)\n"); + exit(3); + } + strncpy(tmp_filename, filename, (len + 1)); + part = strtok(tmp_filename, separator); + while (part) + { + switch (args->size) + { + case 0: + regex_t regex; + regcomp(®ex, "^01-[0-9a-f]{2}(-[0-9a-f]{2}){5}$", REG_EXTENDED); + int reti = regexec(®ex, part, 0, NULL, 0); + if (reti) + { + syslog(LOG_NOTICE, "TRQ The MAC address was entered incorrectly (ts_get_arguments)\n"); + exit(100); + } + args->file_dst = ts_copy_string(part); + args->path_dst = ts_concat_path(part); + break; + case 1: + if (!strncmp("cr", part, 3)) {} + else if (!strncmp("rm", part, 3)) + next = 0; + else + { + syslog(LOG_NOTICE, "TRQ The command was specified incorrectly (ts_get_arguments)\n"); + exit(100); + } + args->command = ts_copy_string(part); + break; + case 2: + args->file_src = ts_copy_string(part); + args->path_src = ts_concat_path(part); + break; + default: + syslog(LOG_NOTICE, "TRQ Invalid request format (ts_get_arguments)\n"); + exit(100); + } + ++args->size; + if (next) + part = strtok(NULL, separator); + else + part = NULL; + } + free(tmp_filename); + if (!args->size) + { + free(args); + args = NULL; + } + return args; +} + +void ts_syslog(const ts_args *args) +{ + if (args) + { + if (args->size > 2) + syslog(LOG_NOTICE, "TRQ Create symlink %s on %s\n", args->file_dst, args->file_src); + else + syslog(LOG_NOTICE, "TRQ Remove symlink %s\n", args->file_dst); + } +} + +void ts_symlink(const ts_args *args) +{ + if (args) + { + if (args->size > 2) + { + if (access(args->path_src, F_OK) == 0 && !symlink(args->file_src, args->path_dst)) + syslog(LOG_NOTICE, "TRQ A symbolic link %s has been created\n", args->file_dst); + else + syslog(LOG_NOTICE, "TRQ Failed to create symbolic link %s. %s\n", args->file_dst, strerror(errno)); + } + else + { + if (access(args->path_dst, F_OK) == 0 && !unlink(args->path_dst)) + syslog(LOG_NOTICE, "TRQ A symbolic link %s has been removed\n", args->file_dst); + else + syslog(LOG_NOTICE, "TRQ Failed to remove symbolic link %s. %s\n", args->file_dst, strerror(errno)); + } + } +} diff --git a/trq/trq.h b/trq/trq.h new file mode 100644 index 0000000..d6828c3 --- /dev/null +++ b/trq/trq.h @@ -0,0 +1,30 @@ +/* + * trq.h + * + * Created on: 17 авг. 2022 г. + * Author: Alexander Zhirov + * Mail: alexander@zhirov.website + * Telegram: alexanderzhirov + */ + +#ifndef TRQ_H_ +#define TRQ_H_ + +#define TRQ 06 /* Thinstation */ + +typedef struct +{ + int size; + char *command; + char *file_src; + char *file_dst; + char *path_src; + char *path_dst; +} ts_args; + +ts_args* ts_get_arguments(const char*, const char*); +void ts_free(ts_args*); +void ts_syslog(const ts_args*); +void ts_symlink(const ts_args*); + +#endif diff --git a/trq/trq.patch b/trq/trq.patch new file mode 100644 index 0000000..7b64555 --- /dev/null +++ b/trq/trq.patch @@ -0,0 +1,126 @@ +diff --git a/Makefile b/Makefile +index 9ff12d8..3d5c537 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,7 +1,7 @@ + # You can do "make SUB=blah" to make only a few, or edit here, or both + # You can also run make directly in the subdirs you want. + +-SUB = lib common tftp tftpd ++SUB = lib common tftpd + + %.build: MCONFIG aconfig.h version.h + $(MAKE) -C $(patsubst %.build, %, $@) +@@ -17,7 +17,7 @@ SUB = lib common tftp tftpd + + all: MCONFIG $(patsubst %, %.build, $(SUB)) + +-tftp.build: lib.build common.build ++#tftp.build: lib.build common.build + tftpd.build: lib.build common.build + + install: MCONFIG $(patsubst %, %.install, $(SUB)) +@@ -68,7 +68,7 @@ configure: configure.in aclocal.m4 + autoconf + + version.h: version +- echo \#define VERSION \"tftp-hpa `cat version`\" > version.h ++ echo \#define VERSION \"tftp-hpa-trq `cat version`\" > version.h + + tftp.spec: tftp.spec.in version + sed -e "s/@@VERSION@@/`cat version`/g" < $< > $@ || rm -f $@ +diff --git a/tftpd/Makefile b/tftpd/Makefile +index a05335d..8e80113 100644 +--- a/tftpd/Makefile ++++ b/tftpd/Makefile +@@ -4,7 +4,7 @@ VERSION = $(shell cat ../version) + -include ../MCONFIG + include ../MRULES + +-OBJS = tftpd.$(O) recvfrom.$(O) misc.$(O) $(TFTPDOBJS) ++OBJS = tftpd.$(O) trq.$(O) recvfrom.$(O) misc.$(O) $(TFTPDOBJS) + + all: tftpd$(X) tftpd.8 + +@@ -18,9 +18,9 @@ tftpd.8: tftpd.8.in ../version + + install: all + mkdir -p $(INSTALLROOT)$(SBINDIR) $(INSTALLROOT)$(MANDIR)/man8 +- $(INSTALL_PROGRAM) tftpd$(X) $(INSTALLROOT)$(SBINDIR)/in.tftpd +- $(INSTALL_DATA) tftpd.8 $(INSTALLROOT)$(MANDIR)/man8/in.tftpd.8 +- cd $(INSTALLROOT)$(MANDIR)/man8 && $(LN_S) -f in.tftpd.8 tftpd.8 ++ $(INSTALL_PROGRAM) tftpd$(X) $(INSTALLROOT)$(SBINDIR)/in.tftpd-trq ++ $(INSTALL_DATA) tftpd.8 $(INSTALLROOT)$(MANDIR)/man8/in.tftpd-trq.8 ++ cd $(INSTALLROOT)$(MANDIR)/man8 && $(LN_S) -f in.tftpd-trq.8 tftpd-trq.8 + + clean: + rm -f *.o *.obj *.exe tftpd tftpsubs.c tftpsubs.h tftpd.8 +diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c +index 1873e70..c3df336 100644 +--- a/tftpd/tftpd.c ++++ b/tftpd/tftpd.c +@@ -52,6 +52,7 @@ + #include "common/tftpsubs.h" + #include "recvfrom.h" + #include "remap.h" ++#include "trq.h" + + #ifdef HAVE_SYS_FILIO_H + #include /* Necessary for FIONBIO on Solaris */ +@@ -1038,7 +1039,7 @@ int main(int argc, char **argv) + + tp = (struct tftphdr *)buf; + tp_opcode = ntohs(tp->th_opcode); +- if (tp_opcode == RRQ || tp_opcode == WRQ) ++ if (tp_opcode == RRQ || tp_opcode == WRQ || tp_opcode == TRQ) + tftp(tp, n); + exit(0); + } +@@ -1077,6 +1078,7 @@ int tftp(struct tftphdr *tp, int size) + char *filename, *mode = NULL; + const char *errmsgptr; + u_short tp_opcode = ntohs(tp->th_opcode); ++ ts_args *args = NULL; + + char *val = NULL, *opt = NULL; + char *ap = ackbuf + 2; +@@ -1118,6 +1120,10 @@ int tftp(struct tftphdr *tp, int size) + nak(EACCESS, errmsgptr); /* File denied by mapping rule */ + exit(0); + } ++ if (tp_opcode == TRQ) /* Thinstation request */ ++ { ++ args = ts_get_arguments(filename, dirs[ndirs - 1]); ++ } + if (verbosity >= 1) { + tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), + tmpbuf, INET6_ADDRSTRLEN); +@@ -1127,9 +1133,12 @@ int tftp(struct tftphdr *tp, int size) + } + if (filename == origfilename + || !strcmp(filename, origfilename)) +- syslog(LOG_NOTICE, "%s from %s filename %s\n", +- tp_opcode == WRQ ? "WRQ" : "RRQ", +- tmp_p, filename); ++ if (tp_opcode == TRQ) /* Thinstation request */ ++ ts_syslog(args); ++ else ++ syslog(LOG_NOTICE, "%s from %s filename %s\n", ++ tp_opcode == WRQ ? "WRQ" : "RRQ", ++ tmp_p, filename); + else + syslog(LOG_NOTICE, + "%s from %s filename %s remapped to %s\n", +@@ -1137,6 +1146,12 @@ int tftp(struct tftphdr *tp, int size) + tmp_p, origfilename, + filename); + } ++ if (tp_opcode == TRQ) ++ { ++ ts_symlink(args); ++ ts_free(args); ++ exit(0); ++ } + ecode = + (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr); + if (ecode) {