Последнее время часто сталкивался с необходимостью обмена файлами между Linux-машинами. В этом посте опишу 3 удобных способа, как можно быстро и легко развернуть тривиальный HTTP-сервер для трансфера файлов.

banner.png

Local – 10.10.10.1, remote – 10.10.10.2.

Python

Питон может выручить практически в любой ситуации, и наш случай не исключение.

Всем известны эти замечательные команды для запуска HTTP-серверов для второй версии питона:

local@server:~$ python -m SimpleHTTPServer [port]

И ее аналог для Python 3:

local@server:~$ python3 -m http.server [-h] [--cgi] [--bind ADDRESS] [port]

Таким способом можно только выдергивать файлы оттуда, где подняли сервер, т. к. единственные методы, который он понимает “из коробки”, это HEAD и GET. Однако никто не запрещает нам немного модифицировать дефолтное поведение, добавив, к примеру, обработку POST (выводим содержимое в консоль для примера) и PUT -запросов.

Простой скрипт:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Usage: python3 SimpleHTTPServer+.py [-h] [--bind ADDRESS] [port]

import http.server
import os

from argparse import ArgumentParser


class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
	def _set_headers(self):
		self.send_response(200)
		self.send_header('Content-type', 'text/html')
		self.end_headers()

	def do_POST(self):
		content_length = int(self.headers['Content-Length'])
		post_data = self.rfile.read(content_length)
		self._set_headers()
		self.wfile.write(b'<html><body><h1>POST!</h1></body></html>')

		print(post_data.decode('utf-8'))

	def do_PUT(self):
		path = self.translate_path(self.path)
		if path.endswith('/'):
			self.send_response(405, 'Method Not Allowed')
			self.wfile.write(b'PUT not allowed on a directory\n')
			return
		else:
			try:
				os.makedirs(os.path.dirname(path))
			except FileExistsError: pass
			length = int(self.headers['Content-Length'])
			with open(path, 'wb') as f:
				f.write(self.rfile.read(length))
			self.send_response(201, 'Created')
			self.end_headers()


def cli_options():
	parser = ArgumentParser()

	parser.add_argument(
		'--bind',
		'-b',
		default='',
		metavar='ADDRESS',
		help='Specify alternate bind address [default: all interfaces]'
	)

	parser.add_argument(
		'port',
		action='store',
		default=8000,
		type=int,
		nargs='?',
		help='Specify alternate port [default: 8000]'
	)

	return parser.parse_args()


if __name__ == '__main__':
	args = cli_options()
	http.server.test(HandlerClass=HTTPRequestHandler, port=args.port, bind=args.bind)

Позволяет успешно как выгружать файлы с:

local@server:~$ wget 10.10.10.2:8881/message
--2018-10-11 10:51:35--  http://10.10.10.2:8881/message
Connecting to 10.10.10.2:8881... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10 [application/octet-stream]
Saving to: ‘message’

message              100%[===================>]      10  --.-KB/s    in 0s

2018-10-11 10:51:35 (2.40 MB/s) - ‘message’ saved [10/10]
local@server:~$ cat message
Hi there!
remote@server:~$ python3 SimpleHTTPServer+.py 8881
Serving HTTP on 0.0.0.0 port 8881 (http://0.0.0.0:8881/) ...
10.10.10.1 - - [11/Oct/2018 11:04:37] "GET /message HTTP/1.1" 200 -

Так и загружать на Linux-машину:

local@server:~$ curl --upload-file message 10.10.10.2:8881
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    10    0     0  100    10      0      9  0:00:01  0:00:01 --:--:--     9


local@server:~$ curl -d @message -X POST 10.10.10.2:8881
<html><body><h1>POST!</h1></body></html>
remote@server:~$ python3 SimpleHTTPServer+.py 8881
Serving HTTP on 0.0.0.0 port 8881 (http://0.0.0.0:8881/) ...
10.10.10.1 - - [11/Oct/2018 10:52:10] "PUT /message HTTP/1.1" 201 -
10.10.10.1 - - [11/Oct/2018 10:52:18] "POST / HTTP/1.1" 200 -
Hi there!
remote@server:~$ cat message
Hi there!

Доступные методы: GET, POST, PUT.

PHP

Неудивительно, что двухстрочный скрипт на PHP может решить все наши проблемы — “препроцессор гипертекста” как-никак :sunglasses:

Итак, для тривиального PHP-сервера нам понадобится такой код:

<?php
$fname = basename($_REQUEST['filename']);
file_put_contents('uploads/' . $fname, file_get_contents('php://input'));
?>

На скриншоте ниже (кликабельно) можно видеть всю процедуру запуска сервера: предварительная настройка на панели слева, тесты — справа.

php.png

Несколько слов о том, что здесь происходит:

  1. Создаем необходимые директории и скрипт с содержимым выше.
  2. Создаем пользователя, от которого будет крутиться сервер. Новый пользователь нужен для того, чтобы недруги не смогли выполнить код, который сами загрузят. Поэтому командой umask 555 задаем настройку прав доступа, выдаваемых всем новым файлам, которые будет создавать наш юзер. 555 это 777 XOR 222, следовательно дефолтные биты будут выставлены, как если бы мы каждому новому файлу вручную задавали chmod 222 (разрешена только запись).
  3. Запускаем сервер и тестируем.
  4. ???????
  5. PROFIT

Доступные методы: GET, POST, PUT.

Nginx

Ну и куда же без the High-Performance Web Server and Reverse Proxy? Благо, на большинстве Linux-дистрибутивах Nginx предустановлен, поэтому настроить и развернуть его можно в считанные минуты.

Опять же на скриншоте ниже можно видеть всю процедуру запуска: предварительная настройка на панели сверху, тесты — снизу.

nginx.png

Что происходит здесь:

  1. Создаем необходимые директории и конфигурацию сервера по образцу из default‘а (содержимое конфига есть ниже).
  2. Делаем конфиг активным (симлинк в /etc/nginx/sites-enabled/)
  3. Перезапускаем службу nginx, проверяем ее активность и тестируем сервер.
  4. ???????
  5. PROFIT

Файл с конфигом:

root@kali:~# cat /etc/nginx/sites-available/file_upload
server {
	listen 8881 default_server;
	server_name snovvcrash.top;
	location / {
		root                  /var/www/uploads;
		dav_methods           PUT;
		create_full_put_path  on;
    		dav_access            group:rw all:r;
	}
}

Как напользовались, не забываем остановить сервер:

root@kali:~# systemctl stop nginx

Доступные методы: GET, PUT.