Wiznet makers

teddy

Published August 02, 2023 ©

97 UCC

8 WCC

73 VAR

0 Contests

0 Followers

0 Following

Original Link

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

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

COMPONENTS
PROJECT DESCRIPTION


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

 

Image06

 

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

 

Image07

 

We see that the client answered us with a password transfer.

We will also see what is happening in our traffic analyzer.

 

Image08

 

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

 

Image09

 

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 )

 

Image10

 

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

 

Image11

 

We see that the answer has come from us. Let's see how Wireshark decrypted it

 

Image12

 

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

 

Image13

 

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

 

Image14

 

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

 

Image15

 

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.

  

 

Documents
Comments Write