Back

FTP, FTPS 與 SFTP

概述

FTP

FTP 是一個歷史悠久的通訊協定,RFC 就多次提出標準及各項補充:

  • RFC 114:初始標準
  • RFC 765:使其執行於 TCP/IP 上
  • RFC 959:目前大多數的 FTP 實作規範
  • RFC 1579:補充 FTP 被動模式,使其能夠穿越 NAT
  • RFC 2228:安全擴充
  • RFC 2428:對 IPv6 的支援與擴充被動模式

FTPS

因為 FTP 在規範中是明碼傳輸,這也導致其安全性與隱私性備受質疑。

RFC 4217 中定義了 FTP over TLS 的實作,使 FTP 傳輸的過程中能夠以 TLS 加密。

註:在 RFC 的草案中曾經嘗試定義 FTP over SSL,但因為 SSL 之後被 TLS 所取代,所以這項草案一直沒有通過,但大部份的 FTP Server 都支援兩者的實作

小知識:在 PHP 4.3 就定義了 ftp_ssl_connect() 這個內建函式,在 PHP 7 之後它依賴於 FTP 與 OpenSSL extension 的實作,所以無論是 SSL 或 TLS 都能夠使用。

SFTP

FTPS 時常會被與 SFTP 混淆,SFTP 是基於 SSH2 協定的補充。儘管目的仍是檔案交換,而且有著高度相似的指令與操作方式,但實際上底層是有相當的差異。

小知識:在 PHP 中,需要安裝 ssh2 extension 才能夠使用 ssh2_sftp() 建立 SFTP 的連接。

常見問題

主動與被動模式

在 FTP 與 FTPS 中有提供可選的主動模式(Active Mode)與被動模式(Passive Mode),其目的是為了實現 NAT 穿透或避免防火牆的阻擋。

在主動模式下,連線流程應該如下所示:

  1. Client 端向 Server 發起連線(命令連線)
  2. Server 端向 Client 端回應已收到連線
  3. Server 端主動向 Client 端發起另一條連線(資料連線)
  4. Client 端向 Server 端回應已收到連線

在被動模式下,連線流程被更改為:

  1. Client 端向 Server 發起連線(命令連線)
  2. Server 端向 Client 端回應已收到連線,並開啟一個 Port 等待資料連線
  3. Client 端向 Server 發起資料連線
  4. Server 端向 Client 端回應已收到連線

由於 Client 端很常是隱藏在 NAT 之後(例如路由器),導致主動模式的第三步是難以實現的,所以在被動模式下由 Server 端開啟一個 Port 讓 Client 端連線。

值得注意的是,SFTP 因為本身由 SSH Tunnel 實現 NAT 穿透,並沒有被動模式。

如何用 PHP 連線 FTP, FTPS 與 SFTP

FTP/FTPS

FTP 的連線需要 ftp extension;FTPS 的連線需要 ftp 與 openssl extensions。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

$url = 'ftp://username:[email protected]:21/file.txt';

$parsed = parse_url($url);
/*
 [
     "scheme" => "ftp",
     "host" => "ftp.example.com",
     "port" => 21,
     "user" => "username",
     "pass" => "password",
     "path" => "/file.txt",
 ]
 */

if ($parsed['scheme'] !== 'ftp' || $parsed['scheme'] !== 'ftps') {
    die("'{$parsed['scheme']}' isn't ftp or ftps protocol.")
}

$connection = $parsed['scheme'] === 'ftp'
    ? ftp_connect($parsed['host'])
    : ftp_ssl_connect($parsed['host']);
if (! $connection) {
    die("'$url' connect failed");
}

if (! ftp_login($connection, $parsed['username'], $parsed['password'])) {
    die("'$url' auth failed by '{$parsed['username']}' and '${$parsed['password']}'");
}

// 如果要求 Passive Mode 需要額外啟動
if ( ftp_pasv($connection, true)) {
    die("'$url' cannot enable passive mode");
}

ftp_get($connection, '/tmp/file.txt', $parsed['path']);
// ftp_fget($connection, $tmp = tmpfile(), $parsed['path']);

註:ftp_login 如果失敗,會丟出 PHP warning,務必記得關閉 PHP warning 的顯示。

SFTP

SSH2 extension

SFTP 的連線需要 SSH2 extension,通常會需要自行以 pecl install ssh2 安裝。

註:如果直接使用 pecl install ssh2 會無法於 PHP 7 以上的版本安裝,這是因為從 2016 年的 0.13 版之後就再也沒有發佈過 stable 版本的 ssh2 extension,需要指定版本才能夠為 PHP 7/8 安裝 ssh2 extension。

1
2
3
4
5
6
7
<?php

$url = 'sftp://username:[email protected]:22/file.txt';

stream_get_contents(
    fopen("ssh2.$url", 'r')
);
phpseclib

因為 SSH2 的安裝上比較複雜,可以用 phpseclib 作為純 PHP 的替代方案,可以在 這裡 找到相關文件

1
2
3
4
5
6
7
8
<?php

use phpseclib3\Net\SFTP;

$sftp = new SFTP('sftp.example.com');
$sftp->login('username', 'password');

$sftp->get('file.txt', '/tmp/local_file.txt');

結論

FTP 作為承載半世紀的通訊協定,為網際網路的發展寫下不可抹滅的功蹟。

儘管大多數的網頁瀏覽器(Chrome, Edge, Firefox)都已經停止原生支援 FTP 協定,且大部份 FTP 站點也都提供 HTTP 的瀏覽介面。然而仍有許多大型軟體(如 Firefox、Linux 發行版鏡像)提供 FTP 站點,也有不少虛擬主機商為了簡化操作而提供 FTP 上傳/下載功能。

FTP 未死,但勢必也會逐步退出舞台。

Built with Hugo
Theme Stack designed by Jimmy