xref: /aosp_15_r20/external/curl/tests/http/testenv/vsftpd.py (revision 6236dae45794135f37c4eb022389c904c8b0090d)
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