STM Lesson 95. LAN. W5500. FTP Server. Part 2
STM Lesson 95. LAN. W5500. FTP Server. Part 2

B previous part classes we set up the project, slightly improved the code in terms of working with the HTTP server, and also created the very first functions for working with the FTP protocol.
Let's get back to the project in our function ftp_receive in file ftpd.c in the same condition, but only in his nasty body ( else ). This body will be executed provided that the package is received if we have already created a control connection. We add another condition to this body that will check that the package came with ( data is not empty ). Why do we need to respond to empty packages
ftpprop.
connect_stat
= FTP_CONNECT;
}
else
{
//проверим, что пакет не пустой
if
(len > 0)
{
}
}
Now go to the file w5500.c and add another pointer installation function there in the reading buffer after function GetReadPointer
//-----------------------------------------------
void
SetReadPointer(
uint8_t
sock_num,
uint16_t
point)
{
uint8_t
opcode;
opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;
w5500_writeReg(opcode, Sn_RX_RD0, point>>8);
w5500_writeReg(opcode, Sn_RX_RD1, (
uint8_t
)point);
}
//-----------------------------------------------
Add a prototype to this function in the title file.
Let's go back to the file ftpd.c and fill the body of the conditions we just created in the function ftp_receive
if
(len > 0)
{
//Отобразим размер принятых данных
sprintf
(str1,
"S%d len buf:0x%04Xrn"
,sn,len);
HAL_UART_Transmit(&huart2,(
uint8_t
*)str1,
strlen
(str1),0x1000);
//примем данные
point = GetReadPointer(sn);
//Отобразим адрес данных
sprintf
(str1,
"S%d point RX:0x%04Xrn"
,sn,point);
HAL_UART_Transmit(&huart2,(
uint8_t
*)str1,
strlen
(str1),0x1000);
w5500_readSockBuf(sn, point, sect, len);
//Передвинем указатель
SetReadPointer(sn, point+len);
RecvSocket(sn);
//завершим нулём
sect[len] = 0;
//отобразим их в терминальной программе
sprintf
(str1,
"S%d: %srn"
, sn, (
char
*)sect);
HAL_UART_Transmit(&huart2,(
uint8_t
*)(
char
*)sect,
strlen
((
char
*)sect),0x1000);
}
The procedure for receiving package data, written in this body, is standard, there is more information in terminal programs. But for now, let her be. We are going to ensure that we are normally in the process of creating code. In this section of the code, we place the accepted data in the sect array and then display it in the terminal program.
We collect the code, send the controller and again try to connect to the FTP server, looking at the information in the terminal program
All commands are accepted and nomally displayed in the terminal program.
Now we need to somehow respond to these teams.
We create the above function of parsing the package data bar, immediately creating a number of variables, pointers and arrays
//-----------------------------------------------
void
ftp_cmd_parse(
uint8_t
sn,
char
* buf)
{
char
**cmd_point, *ch, *arg, *ch_tmp;
char
buf_send[200];
uint16_t
len;
uint16_t
offset;
data_sect_ptr
*datasect = (
void
*)buf_send;
}
//-----------------------------------------------
Call this function in the body conditions at the end of the code we just added
HAL_UART_Transmit(&huart2,(
uint8_t
*)(
char
*)sect,
strlen
((
char
*)sect),0x1000);
ftp_cmd_parse(sn, (
char
*)sect);
}
}
}
else
if
(sn == FTP_SOCKET_DATA)
We will create a global array of commands presented in strict form and converted to lowercase, since FTP has no restrictions on this score and commands can come in different registers. By the number of string elements, the array must converge in the title file
extern
volatile
uint16_t
tcp_size_wnd;
//-----------------------------------------------
/* Command table */
static
char
*ftp_commands[] = {
"user"
,
"acct"
,
"pass"
,
"type"
,
"list"
,
"cwd"
,
"dele"
,
"name"
,
"quit"
,
"retr"
,
"stor"
,
"port"
,
"nlst"
,
"pwd"
,
"xpwd"
,
"mkd"
,
"xmkd"
,
"xrmd"
,
"rmd "
,
"stru"
,
"mode"
,
"syst"
,
"xmd5"
,
"xcwd"
,
"feat"
,
"pasv"
,
"size"
,
"mlsd"
,
"appe"
,
NULL
};
//-----------------------------------------------
Let's get back to function now ftp_cmd_parse and start composing her body, and it promises to be considerable.
Convert the incoming command to the lower case as well, converting the characters to the first space or to the end of the data
data_sect_ptr
*datasect = (
void
*)buf_send;
//преобразуем к нижнему регистру
for
(ch = buf; *ch !=
' '
&& *ch !=
''
; ch++) *ch = tolower(*ch);
Next, look for a team on our list.
for
(ch = buf; *ch !=
' '
&& *ch !=
''
; ch++) *ch = tolower(*ch);
// найдём команду в списке
for
(cmd_point = ftp_commands; *cmd_point != NULL; cmd_point++)
{
if
(
strncmp
(*cmd_point, buf,
strlen
(*cmd_point)) == 0)
break
;
}
If we don’t find it, we will send in response that the team is non-existent and leave the function
if
(
strncmp
(*cmd_point, buf,
strlen
(*cmd_point)) == 0)
break
;
}
if
(*cmd_point == NULL)
{
sprintf
((
char
*)datasect->
data
,
"500 Unknown command '%s'rn"
, buf);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send,
strlen
((
char
*)datasect->
data
));
return
;
}
Next, add a condition that finds out if the client is authorized on the server or not yet
return
;
}
if
(ftpprop.
login_stat
== FTPS_NOT_LOGIN)
{
}
Further in the body of this condition we add a condition of the switch type, which will recognize team options in which the reaction to three types of teams will exit the condition, and if other teams come as famous, since the unknown we have already filtered, we will send a message to the client that he is not yet atorized and will leave the function completely
if
(ftpprop.
login_stat
== FTPS_NOT_LOGIN)
{
switch
(cmd_point - ftp_commands)
{
case
USER_CMD
:
case
PASS_CMD
:
case
QUIT_CMD
:
break
;
default
:
len =
sprintf
((
char
*)datasect->
data
,
"530 Please log in with USER and PASSrn"
);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
return
;
}
}
Next, let's get out of the condition of non-authorization and set the pointer to the argument of the team
return
;
}
}
//установим указатель на аргумент команды клиента
arg = &buf[
strlen
(*cmd_point)];
while
(*arg ==
' '
) arg++;
Then we begin to explore which team came to us.
Add for this switch
while
(*arg ==
' '
) arg++;
switch
(cmd_point - ftp_commands)
{
}
And now let's start adding various team options to it.
At the moment, according to the traffic analyzer, we saw that the client sent us the USER command with the username argument for processing. Here we will process it, at the same time add the default branch
switch
(cmd_point - ftp_commands)
{
case
USER_CMD
:
break
;
default
:
// Invalid
break
;
}
Let's start with default, so as not to forget later.
default
:
// Invalid
len =
sprintf
((
char
*)datasect->
data
,
"500 Unknown command '%s'rn"
, arg);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
HAL_Delay(1000);
DisconnectSocket(sn);
//Разъединяемся
SocketClosedWait(sn);
sprintf
(str1,
"S%d closedrn"
,sn);
HAL_UART_Transmit(&huart2,(
uint8_t
*)str1,
strlen
(str1),0x1000);
OpenSocket(sn,Mode_TCP);
//Ждём инициализации сокета (статус SOCK_INIT)
SocketInitWait(sn);
//Продолжаем слушать сокет
ListenSocket(sn);
SocketListenWait(sn);
break
;
Here we behave in a standard way: we separate the socket, reopen it and start listening, while displaying some information about this in the terminal program. I turned on the delay to avoid very quick looping, I think it won’t hurt much.
Now the actual processing of the team USER.
case
USER_CMD
:
sprintf
(str1,
"USER_CMD : %s"
, arg);
HAL_UART_Transmit(&huart2,(
uint8_t
*)str1,
strlen
(str1),0x1000);
//отправим ответ на команду
len =
strlen
(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
strcpy
(ftpprop.
username
, arg);
len =
sprintf
((
char
*)datasect->
data
,
"331 Enter PASS commandrn"
);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
break
;
Here we show in the terminal program a team with an argument to see if we correctly extracted it, then measure its length, reset the carriage return and the transition to a new line, copy to the structure field with properties, in the future it will be the name of the client’s user, then we send the next message to the client with a password request. Here we applied a new type of string length measurement when using the sprintf function. This function returns it as an outgoing argument.
Let's go to the file now w5500.c and connect there a variable of our structure with properties
extern
http_sock_prop_ptr
httpsockprop[8];
extern
ftp_prop_ptr
ftpprop;
Also almost forgot. It is necessary to accept and process packages w5500_packetReceive handle the status of the disabled socket. This will be required if the socket is disconnected for some unknown reasons, for example, at the initiative of the client. When we turn it off intentionally, then we will again create a connection with it. And there are other moments.
Therefore, we will create a nasty case of an open socket in this function and write the corresponding code there
http_receive(sn);
}
}
else
{
//управляющий сокет FTP
if
(sn==FTP_SOCKET_CTRL)
{
if
(ftpprop.
connect_stat
) ftpprop.
connect_stat
=FTP_DISCONNECT;
if
(ftpprop.
login_stat
==FTPS_LOGIN) ftpprop.
login_stat
=FTPS_NOT_LOGIN;
SetReadPointer(FTP_SOCKET_CTRL, 0);
}
}
}
That is, in this case, we re-initialize the settings and set the buffer pointer to 0.
Let's now collect the code and sew the controller and follow the response to the USER command from the server, and also see what the client answers us.
Here is the result in the terminal program
We see that the client answered us with a password transfer.
We will also see what is happening in our traffic analyzer.
Here we also see that our client received our message and answered us with his password, to which the server replied that this was an unknown team for him. Of course, it is unknown to the server until we have processed it yet.
This is what we will do. Let's go back to the file ftpd.c and add the client authorization function over the command parsing function
//-----------------------------------------------
void
ftp_login(
uint8_t
sn,
char
* pass)
{
char
buf_send[100];
uint16_t
len;
data_sect_ptr
*datasect = (
void
*)buf_send;
len =
sprintf
((
char
*)datasect->
data
,
"230 Logged onrn"
);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
ftpprop.
login_stat
= FTPS_LOGIN;
}
//-----------------------------------------------
In this function, we set the data for transmission, transmit a message about the successful authorization of the client, and also set the structure field with the authorization property to authorized. As such, we will not have authorization in fact, we do not check the validity of the couple, we simply agree with everything that the client sends us.
Now back to our switch in function ftp_cmd_parse and process the team there PASS
break
;
case
PASS_CMD
:
len =
strlen
(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
ftp_login(sn, arg);
break
;
default
:
// Invalid
Everything is simple here. We also reset the line translation and carriage return and call the client authorization function.
We will collect the code, we will send the controller and see what the client will require from us this time.
The result in the terminal program
Customer sent us a team Syst, which is a request for identification of the operating system, for which we should tell him a little about our server.
We will also see a traffic analyzer in which, of course, we will see the client being sent that this team is unknown to us ( bye )
Moreover, the client turned out to be a very polite client and was not offended by our shameful response about the team’s ignorance and requested the next team. But, I think, there may be such cases when an offending client comes across, who will simply break the connection with us, so let's still answer something to him
break
;
case
SYST_CMD
:
len =
sprintf
((
char
*)datasect->
data
,
"215 UNIX emulated by W5500rnrn"
);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
break
;
default
:
// Invalid
Check the client’s reaction, for which we will collect the code and sew the controller.
The same reaction — the client sent a team Feat, which serves to coordinate functions, that is, the client asks now to tell more about the server in more detail what he can.
We will answer him with our capabilities
break
;
case
FEAT_CMD
:
len =
sprintf
((
char
*)datasect->
data
,
"211-Features:rn MDTMrn REST STREAMrn SIZErn MLST size*;type*;create*;modify*;rn MLSDrn UTF8rn CLNTrn MFMTrn211 ENDrn"
);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
break
;
default
:
// Invalid
These are our opportunities. There is also a requirement that after listing all the possibilities you need to send another word END, so that the client realizes that he had the last property of the server. What are these opportunities, I will not list here, as this can be found in special sources via FTP.
We will collect the project, sew the controller and check the result immediately in the Wireshark traffic analyzer, since there is still more information there, in the terminal program we do not see a report on the packages that came from us
We see that the answer has come from us. Let's see how Wireshark decrypted it
So, the traffic analyzer saw everything beautifully.
The next command is sent to us by the CWD client, sees the server’s response about the unknown, and then sends food and PWD.
The first CWD — command is a customer request to change the current directory. The name of the current directory the client transfers in the argument. The client asks to go to the root directory, so as an argument he transmits a slash ( to the slash ).
Slowly start answering him
break
;
case
CWD_CMD
:
len =
strlen
(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
//если не подкаталог
if
(arg[0]==
'/'
)
{
strcpy
(ftpprop.
work_dir
, arg);
}
break
;
default
:
// Invalid
Here we enter in the field with the name of the current directory ( folders, directories ) the transmitted argument. Only this if the client passed the root directory as an argument.
Next, we process the transfer by the client as an argument the transition to a level higher. This is such a case when the client asks from any catalog, which is currently to go out and go to the upper level. In this case, the client gives us two points in the argument
strcpy
(ftpprop.
work_dir
, arg);
}
//переход на верхний уровень
else
if
(
strncmp
(arg,
".."
, 2) == 0)
{
}
break
;
Now let's start writing the body of this transition to a level higher
else
if
(
strncmp
(arg,
".."
, 2) == 0)
{
//отрежем каталог самого нижнего уровня
//Найдём последний символ '/'
offset = 0;
while
(1)
{
ch_tmp =
strchr
(ftpprop.
work_dir
+offset,
'/'
);
if
(!ch_tmp)
{
ftpprop.
work_dir
[offset] =
''
;
break
;
}
ch = ch_tmp;
offset = ch_tmp - ftpprop.
work_dir
+ 1;
}
Here we use the name of the current directory of the structure stored in our field with the properties of the server. we will try to cut off the directory of the lowest level from it so that the directory of the higher level becomes the current directory. To do this, we are looking for the last slash character and, accordingly, insert 0 into this place, which will correspond to the end of the line. Then we remove the slash, if the name of the catalog ends, we do not need it. This does not apply to the root directory
offset = ch_tmp - ftpprop.
work_dir
+ 1;
}
//отрежем символ '/', если подкаталог
len =
strlen
(ftpprop.
work_dir
);
if
(len>1)
{
if
(ftpprop.
work_dir
[len-1]==
'/'
)
{
ftpprop.
work_dir
[len-1]=0;
}
}
}
break
;
Then we leave this condition ( not from the cycle, but from the condition completely and we will process all other conditions
ftpprop.
work_dir
[len-1]=0;
}
}
}
else
{
//дополним путь символом '/' в конце, если его нет
len =
strlen
(ftpprop.
work_dir
);
if
(ftpprop.
work_dir
[len-1]!=
'/'
)
strcat
(ftpprop.
work_dir
,
"/"
);
strcat
(ftpprop.
work_dir
, arg);
}
break
;
Here, on the contrary, we will supplement the name with a slash.
Then we leave this body and give the client a response to the message
strcat
(ftpprop.
work_dir
, arg);
}
len =
sprintf
((
char
*)datasect->
data
,
"250 CWD successful. "%s" is current directory.rn"
, ftpprop.
work_dir
);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
break
;
We will open the controller by collecting the code in advance and checking that the server has processed the client command
The team was accepted by the client, to which the client reacted, now sending not PWD, but another TYPE team. This is a customer request for the server to work with the client in ASCII. This is evidenced by argument A. If, for example, the argument would be B, then this would mean a client’s request to apply the binary standard.
Also in Total Commander, we already see that as if we already have a connection via FTP, but so far no list has been displayed
This is because we have not yet transferred the list of files and directories of the current directory, and no one has requested it yet. Moreover, this is already being done by connection for data transfer. But we will get to this.
In the meantime, we will process the client team
break
;
case
TYPE_CMD
:
len =
strlen
(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
switch
(arg[0])
{
case
'A'
:
case
'a'
:
/* Ascii */
ftpprop.
ftp_type
= ASCII_TYPE;
len =
sprintf
((
char
*)datasect->
data
,
"200 Type set to %srn"
, arg);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
break
;
case
'B'
:
case
'b'
:
/* Binary */
case
'I'
:
case
'i'
:
/* Image */
ftpprop.
ftp_type
= IMAGE_TYPE;
len =
sprintf
((
char
*)datasect->
data
,
"200 Type set to %srn"
, arg);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
break
;
default
:
/* Invalid */
len =
sprintf
((
char
*)datasect->
data
,
"501 Unknown type "%s"rn"
, arg);
tcp_send_ftp_one(sn, (
uint8_t
*)buf_send, len);
break
;
}
break
;
default
:
// Invalid
There is at least considerable code, but there’s nothing to explain in it. We simply respond to possible arguments and send appropriate messages.
We collect the code, send the controller and see what the client asks for next
We see that our client received the message and now he sends us a team PASV. This command indicates the client’s request to use a passive exchange mode, since we installed the corresponding checkbox in the FTP connection settings of the client. If the server agrees to such an exchange mode, then it is obliged to respond with the IP address and connection port for data transfer, since we already know that there will be a separate connection for data transfer. Since the command given for the server is still unknown, the server says that the client does not calm down and offers its connection options.
B next part classes we will write code to display information about the catalog to the client.