1*6236dae4SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*6236dae4SAndroid Build Coastguard Worker# -*- coding: utf-8 -*- 3*6236dae4SAndroid Build Coastguard Worker#*************************************************************************** 4*6236dae4SAndroid Build Coastguard Worker# _ _ ____ _ 5*6236dae4SAndroid Build Coastguard Worker# Project ___| | | | _ \| | 6*6236dae4SAndroid Build Coastguard Worker# / __| | | | |_) | | 7*6236dae4SAndroid Build Coastguard Worker# | (__| |_| | _ <| |___ 8*6236dae4SAndroid Build Coastguard Worker# \___|\___/|_| \_\_____| 9*6236dae4SAndroid Build Coastguard Worker# 10*6236dae4SAndroid Build Coastguard Worker# Copyright (C) Daniel Stenberg, <[email protected]>, et al. 11*6236dae4SAndroid Build Coastguard Worker# 12*6236dae4SAndroid Build Coastguard Worker# This software is licensed as described in the file COPYING, which 13*6236dae4SAndroid Build Coastguard Worker# you should have received as part of this distribution. The terms 14*6236dae4SAndroid Build Coastguard Worker# are also available at https://curl.se/docs/copyright.html. 15*6236dae4SAndroid Build Coastguard Worker# 16*6236dae4SAndroid Build Coastguard Worker# You may opt to use, copy, modify, merge, publish, distribute and/or sell 17*6236dae4SAndroid Build Coastguard Worker# copies of the Software, and permit persons to whom the Software is 18*6236dae4SAndroid Build Coastguard Worker# furnished to do so, under the terms of the COPYING file. 19*6236dae4SAndroid Build Coastguard Worker# 20*6236dae4SAndroid Build Coastguard Worker# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21*6236dae4SAndroid Build Coastguard Worker# KIND, either express or implied. 22*6236dae4SAndroid Build Coastguard Worker# 23*6236dae4SAndroid Build Coastguard Worker# SPDX-License-Identifier: curl 24*6236dae4SAndroid Build Coastguard Worker# 25*6236dae4SAndroid Build Coastguard Worker########################################################################### 26*6236dae4SAndroid Build Coastguard Worker# 27*6236dae4SAndroid Build Coastguard Workerimport logging 28*6236dae4SAndroid Build Coastguard Workerimport os 29*6236dae4SAndroid Build Coastguard Workerimport subprocess 30*6236dae4SAndroid Build Coastguard Workerimport time 31*6236dae4SAndroid Build Coastguard Worker 32*6236dae4SAndroid Build Coastguard Workerfrom datetime import datetime, timedelta 33*6236dae4SAndroid Build Coastguard Worker 34*6236dae4SAndroid Build Coastguard Workerfrom .curl import CurlClient 35*6236dae4SAndroid Build Coastguard Workerfrom .env import Env 36*6236dae4SAndroid Build Coastguard Worker 37*6236dae4SAndroid Build Coastguard Worker 38*6236dae4SAndroid Build Coastguard Workerlog = logging.getLogger(__name__) 39*6236dae4SAndroid Build Coastguard Worker 40*6236dae4SAndroid Build Coastguard Worker 41*6236dae4SAndroid Build Coastguard Workerclass VsFTPD: 42*6236dae4SAndroid Build Coastguard Worker 43*6236dae4SAndroid Build Coastguard Worker def __init__(self, env: Env, with_ssl=False): 44*6236dae4SAndroid Build Coastguard Worker self.env = env 45*6236dae4SAndroid Build Coastguard Worker self._cmd = env.vsftpd 46*6236dae4SAndroid Build Coastguard Worker self._scheme = 'ftp' 47*6236dae4SAndroid Build Coastguard Worker self._with_ssl = with_ssl 48*6236dae4SAndroid Build Coastguard Worker if self._with_ssl: 49*6236dae4SAndroid Build Coastguard Worker self._port = self.env.ftps_port 50*6236dae4SAndroid Build Coastguard Worker name = 'vsftpds' 51*6236dae4SAndroid Build Coastguard Worker else: 52*6236dae4SAndroid Build Coastguard Worker self._port = self.env.ftp_port 53*6236dae4SAndroid Build Coastguard Worker name = 'vsftpd' 54*6236dae4SAndroid Build Coastguard Worker self._vsftpd_dir = os.path.join(env.gen_dir, name) 55*6236dae4SAndroid Build Coastguard Worker self._run_dir = os.path.join(self._vsftpd_dir, 'run') 56*6236dae4SAndroid Build Coastguard Worker self._docs_dir = os.path.join(self._vsftpd_dir, 'docs') 57*6236dae4SAndroid Build Coastguard Worker self._tmp_dir = os.path.join(self._vsftpd_dir, 'tmp') 58*6236dae4SAndroid Build Coastguard Worker self._conf_file = os.path.join(self._vsftpd_dir, 'test.conf') 59*6236dae4SAndroid Build Coastguard Worker self._pid_file = os.path.join(self._vsftpd_dir, 'vsftpd.pid') 60*6236dae4SAndroid Build Coastguard Worker self._error_log = os.path.join(self._vsftpd_dir, 'vsftpd.log') 61*6236dae4SAndroid Build Coastguard Worker self._process = None 62*6236dae4SAndroid Build Coastguard Worker 63*6236dae4SAndroid Build Coastguard Worker self.clear_logs() 64*6236dae4SAndroid Build Coastguard Worker 65*6236dae4SAndroid Build Coastguard Worker @property 66*6236dae4SAndroid Build Coastguard Worker def domain(self): 67*6236dae4SAndroid Build Coastguard Worker return self.env.ftp_domain 68*6236dae4SAndroid Build Coastguard Worker 69*6236dae4SAndroid Build Coastguard Worker @property 70*6236dae4SAndroid Build Coastguard Worker def docs_dir(self): 71*6236dae4SAndroid Build Coastguard Worker return self._docs_dir 72*6236dae4SAndroid Build Coastguard Worker 73*6236dae4SAndroid Build Coastguard Worker @property 74*6236dae4SAndroid Build Coastguard Worker def port(self) -> int: 75*6236dae4SAndroid Build Coastguard Worker return self._port 76*6236dae4SAndroid Build Coastguard Worker 77*6236dae4SAndroid Build Coastguard Worker def clear_logs(self): 78*6236dae4SAndroid Build Coastguard Worker self._rmf(self._error_log) 79*6236dae4SAndroid Build Coastguard Worker 80*6236dae4SAndroid Build Coastguard Worker def exists(self): 81*6236dae4SAndroid Build Coastguard Worker return os.path.exists(self._cmd) 82*6236dae4SAndroid Build Coastguard Worker 83*6236dae4SAndroid Build Coastguard Worker def is_running(self): 84*6236dae4SAndroid Build Coastguard Worker if self._process: 85*6236dae4SAndroid Build Coastguard Worker self._process.poll() 86*6236dae4SAndroid Build Coastguard Worker return self._process.returncode is None 87*6236dae4SAndroid Build Coastguard Worker return False 88*6236dae4SAndroid Build Coastguard Worker 89*6236dae4SAndroid Build Coastguard Worker def start_if_needed(self): 90*6236dae4SAndroid Build Coastguard Worker if not self.is_running(): 91*6236dae4SAndroid Build Coastguard Worker return self.start() 92*6236dae4SAndroid Build Coastguard Worker return True 93*6236dae4SAndroid Build Coastguard Worker 94*6236dae4SAndroid Build Coastguard Worker def stop_if_running(self): 95*6236dae4SAndroid Build Coastguard Worker if self.is_running(): 96*6236dae4SAndroid Build Coastguard Worker return self.stop() 97*6236dae4SAndroid Build Coastguard Worker return True 98*6236dae4SAndroid Build Coastguard Worker 99*6236dae4SAndroid Build Coastguard Worker def stop(self, wait_dead=True): 100*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 101*6236dae4SAndroid Build Coastguard Worker if self._process: 102*6236dae4SAndroid Build Coastguard Worker self._process.terminate() 103*6236dae4SAndroid Build Coastguard Worker self._process.wait(timeout=2) 104*6236dae4SAndroid Build Coastguard Worker self._process = None 105*6236dae4SAndroid Build Coastguard Worker return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5)) 106*6236dae4SAndroid Build Coastguard Worker return True 107*6236dae4SAndroid Build Coastguard Worker 108*6236dae4SAndroid Build Coastguard Worker def restart(self): 109*6236dae4SAndroid Build Coastguard Worker self.stop() 110*6236dae4SAndroid Build Coastguard Worker return self.start() 111*6236dae4SAndroid Build Coastguard Worker 112*6236dae4SAndroid Build Coastguard Worker def start(self, wait_live=True): 113*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 114*6236dae4SAndroid Build Coastguard Worker if self._process: 115*6236dae4SAndroid Build Coastguard Worker self.stop() 116*6236dae4SAndroid Build Coastguard Worker self._write_config() 117*6236dae4SAndroid Build Coastguard Worker args = [ 118*6236dae4SAndroid Build Coastguard Worker self._cmd, 119*6236dae4SAndroid Build Coastguard Worker f'{self._conf_file}', 120*6236dae4SAndroid Build Coastguard Worker ] 121*6236dae4SAndroid Build Coastguard Worker procerr = open(self._error_log, 'a') 122*6236dae4SAndroid Build Coastguard Worker self._process = subprocess.Popen(args=args, stderr=procerr) 123*6236dae4SAndroid Build Coastguard Worker if self._process.returncode is not None: 124*6236dae4SAndroid Build Coastguard Worker return False 125*6236dae4SAndroid Build Coastguard Worker return not wait_live or self.wait_live(timeout=timedelta(seconds=5)) 126*6236dae4SAndroid Build Coastguard Worker 127*6236dae4SAndroid Build Coastguard Worker def wait_dead(self, timeout: timedelta): 128*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=self.env, run_dir=self._tmp_dir) 129*6236dae4SAndroid Build Coastguard Worker try_until = datetime.now() + timeout 130*6236dae4SAndroid Build Coastguard Worker while datetime.now() < try_until: 131*6236dae4SAndroid Build Coastguard Worker check_url = f'{self._scheme}://{self.domain}:{self.port}/' 132*6236dae4SAndroid Build Coastguard Worker r = curl.ftp_get(urls=[check_url], extra_args=['-v']) 133*6236dae4SAndroid Build Coastguard Worker if r.exit_code != 0: 134*6236dae4SAndroid Build Coastguard Worker return True 135*6236dae4SAndroid Build Coastguard Worker log.debug(f'waiting for vsftpd to stop responding: {r}') 136*6236dae4SAndroid Build Coastguard Worker time.sleep(.1) 137*6236dae4SAndroid Build Coastguard Worker log.debug(f"Server still responding after {timeout}") 138*6236dae4SAndroid Build Coastguard Worker return False 139*6236dae4SAndroid Build Coastguard Worker 140*6236dae4SAndroid Build Coastguard Worker def wait_live(self, timeout: timedelta): 141*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=self.env, run_dir=self._tmp_dir) 142*6236dae4SAndroid Build Coastguard Worker try_until = datetime.now() + timeout 143*6236dae4SAndroid Build Coastguard Worker while datetime.now() < try_until: 144*6236dae4SAndroid Build Coastguard Worker check_url = f'{self._scheme}://{self.domain}:{self.port}/' 145*6236dae4SAndroid Build Coastguard Worker r = curl.ftp_get(urls=[check_url], extra_args=[ 146*6236dae4SAndroid Build Coastguard Worker '--trace', 'curl-start.trace', '--trace-time' 147*6236dae4SAndroid Build Coastguard Worker ]) 148*6236dae4SAndroid Build Coastguard Worker if r.exit_code == 0: 149*6236dae4SAndroid Build Coastguard Worker return True 150*6236dae4SAndroid Build Coastguard Worker log.debug(f'waiting for vsftpd to become responsive: {r}') 151*6236dae4SAndroid Build Coastguard Worker time.sleep(.1) 152*6236dae4SAndroid Build Coastguard Worker log.error(f"Server still not responding after {timeout}") 153*6236dae4SAndroid Build Coastguard Worker return False 154*6236dae4SAndroid Build Coastguard Worker 155*6236dae4SAndroid Build Coastguard Worker def _rmf(self, path): 156*6236dae4SAndroid Build Coastguard Worker if os.path.exists(path): 157*6236dae4SAndroid Build Coastguard Worker return os.remove(path) 158*6236dae4SAndroid Build Coastguard Worker 159*6236dae4SAndroid Build Coastguard Worker def _mkpath(self, path): 160*6236dae4SAndroid Build Coastguard Worker if not os.path.exists(path): 161*6236dae4SAndroid Build Coastguard Worker return os.makedirs(path) 162*6236dae4SAndroid Build Coastguard Worker 163*6236dae4SAndroid Build Coastguard Worker def _write_config(self): 164*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._docs_dir) 165*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._tmp_dir) 166*6236dae4SAndroid Build Coastguard Worker conf = [ # base server config 167*6236dae4SAndroid Build Coastguard Worker 'listen=YES', 168*6236dae4SAndroid Build Coastguard Worker 'run_as_launching_user=YES', 169*6236dae4SAndroid Build Coastguard Worker '#listen_address=127.0.0.1', 170*6236dae4SAndroid Build Coastguard Worker f'listen_port={self.port}', 171*6236dae4SAndroid Build Coastguard Worker 'local_enable=NO', 172*6236dae4SAndroid Build Coastguard Worker 'anonymous_enable=YES', 173*6236dae4SAndroid Build Coastguard Worker f'anon_root={self._docs_dir}', 174*6236dae4SAndroid Build Coastguard Worker 'dirmessage_enable=YES', 175*6236dae4SAndroid Build Coastguard Worker 'write_enable=YES', 176*6236dae4SAndroid Build Coastguard Worker 'anon_upload_enable=YES', 177*6236dae4SAndroid Build Coastguard Worker 'log_ftp_protocol=YES', 178*6236dae4SAndroid Build Coastguard Worker 'xferlog_enable=YES', 179*6236dae4SAndroid Build Coastguard Worker 'xferlog_std_format=NO', 180*6236dae4SAndroid Build Coastguard Worker f'vsftpd_log_file={self._error_log}', 181*6236dae4SAndroid Build Coastguard Worker '\n', 182*6236dae4SAndroid Build Coastguard Worker ] 183*6236dae4SAndroid Build Coastguard Worker if self._with_ssl: 184*6236dae4SAndroid Build Coastguard Worker creds = self.env.get_credentials(self.domain) 185*6236dae4SAndroid Build Coastguard Worker assert creds # convince pytype this isn't None 186*6236dae4SAndroid Build Coastguard Worker conf.extend([ 187*6236dae4SAndroid Build Coastguard Worker 'ssl_enable=YES', 188*6236dae4SAndroid Build Coastguard Worker 'debug_ssl=YES', 189*6236dae4SAndroid Build Coastguard Worker 'allow_anon_ssl=YES', 190*6236dae4SAndroid Build Coastguard Worker f'rsa_cert_file={creds.cert_file}', 191*6236dae4SAndroid Build Coastguard Worker f'rsa_private_key_file={creds.pkey_file}', 192*6236dae4SAndroid Build Coastguard Worker # require_ssl_reuse=YES means ctrl and data connection need to use the same session 193*6236dae4SAndroid Build Coastguard Worker 'require_ssl_reuse=NO', 194*6236dae4SAndroid Build Coastguard Worker ]) 195*6236dae4SAndroid Build Coastguard Worker 196*6236dae4SAndroid Build Coastguard Worker with open(self._conf_file, 'w') as fd: 197*6236dae4SAndroid Build Coastguard Worker fd.write("\n".join(conf)) 198