mirror of
https://github.com/joel16/VitaShell.git
synced 2025-03-04 01:57:07 +00:00
1084 lines
26 KiB
C
1084 lines
26 KiB
C
/*
|
|
* Copyright (c) 2015 Sergi Granell (xerpi)
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/syslimits.h>
|
|
|
|
#include <psp2/kernel/threadmgr.h>
|
|
|
|
#include <psp2/io/fcntl.h>
|
|
#include <psp2/io/dirent.h>
|
|
|
|
#include <psp2/net/net.h>
|
|
#include <psp2/net/netctl.h>
|
|
|
|
#include "main.h"
|
|
#include "file.h"
|
|
|
|
#define UNUSED(x) (void)(x)
|
|
|
|
#define NET_CTL_ERROR_NOT_TERMINATED 0x80412102
|
|
|
|
#define FTP_PORT 1337
|
|
#define NET_INIT_SIZE 1*1024*1024
|
|
#define FILE_BUF_SIZE 4*1024*1024
|
|
|
|
#define FTP_DEFAULT_PREFIX HOME_PATH
|
|
#define FTP_DEFAULT_PATH "/"
|
|
#define HOME_PREFIX_LENGTH 5
|
|
|
|
#define INFO(...)
|
|
#define DEBUG(...)
|
|
|
|
typedef enum {
|
|
FTP_DATA_CONNECTION_NONE,
|
|
FTP_DATA_CONNECTION_ACTIVE,
|
|
FTP_DATA_CONNECTION_PASSIVE,
|
|
} DataConnectionType;
|
|
|
|
typedef struct ClientInfo {
|
|
/* Client number */
|
|
int num;
|
|
/* Thread UID */
|
|
SceUID thid;
|
|
/* Control connection socket FD */
|
|
int ctrl_sockfd;
|
|
/* Data connection attributes */
|
|
int data_sockfd;
|
|
DataConnectionType data_con_type;
|
|
SceNetSockaddrIn data_sockaddr;
|
|
/* PASV mode client socket */
|
|
SceNetSockaddrIn pasv_sockaddr;
|
|
int pasv_sockfd;
|
|
/* Remote client net info */
|
|
SceNetSockaddrIn addr;
|
|
/* Receive buffer attributes */
|
|
int n_recv;
|
|
char recv_buffer[512];
|
|
/* Current working directory */
|
|
char cur_path[PATH_MAX];
|
|
/* Rename path */
|
|
char rename_path[PATH_MAX];
|
|
/* Client list */
|
|
struct ClientInfo *next;
|
|
struct ClientInfo *prev;
|
|
} ClientInfo;
|
|
|
|
typedef void (*cmd_dispatch_func)(ClientInfo *client);
|
|
|
|
typedef struct {
|
|
const char *cmd;
|
|
cmd_dispatch_func func;
|
|
} cmd_dispatch_entry;
|
|
|
|
static void *net_memory = NULL;
|
|
static int ftp_initialized = 0;
|
|
static SceNetInAddr vita_addr;
|
|
static SceUID server_thid;
|
|
static int server_sockfd;
|
|
static int number_clients = 0;
|
|
static ClientInfo *client_list = NULL;
|
|
static SceUID client_list_mtx;
|
|
|
|
static int netctl_init = -1;
|
|
static int net_init = -1;
|
|
|
|
#define client_send_ctrl_msg(cl, str) \
|
|
sceNetSend(cl->ctrl_sockfd, str, strlen(str), 0)
|
|
|
|
static inline void client_send_data_msg(ClientInfo *client, const char *str)
|
|
{
|
|
if (client->data_con_type == FTP_DATA_CONNECTION_ACTIVE) {
|
|
sceNetSend(client->data_sockfd, str, strlen(str), 0);
|
|
} else {
|
|
sceNetSend(client->pasv_sockfd, str, strlen(str), 0);
|
|
}
|
|
}
|
|
|
|
static inline int client_send_recv_raw(ClientInfo *client, void *buf, unsigned int len)
|
|
{
|
|
if (client->data_con_type == FTP_DATA_CONNECTION_ACTIVE) {
|
|
return sceNetRecv(client->data_sockfd, buf, len, 0);
|
|
} else {
|
|
return sceNetRecv(client->pasv_sockfd, buf, len, 0);
|
|
}
|
|
}
|
|
|
|
static inline void client_send_data_raw(ClientInfo *client, const void *buf, unsigned int len)
|
|
{
|
|
if (client->data_con_type == FTP_DATA_CONNECTION_ACTIVE) {
|
|
sceNetSend(client->data_sockfd, buf, len, 0);
|
|
} else {
|
|
sceNetSend(client->pasv_sockfd, buf, len, 0);
|
|
}
|
|
}
|
|
|
|
static void cmd_USER_func(ClientInfo *client)
|
|
{
|
|
client_send_ctrl_msg(client, "331 Username OK, need password b0ss.\n");
|
|
}
|
|
|
|
static void cmd_PASS_func(ClientInfo *client)
|
|
{
|
|
client_send_ctrl_msg(client, "230 User logged in!\n");
|
|
}
|
|
|
|
static void cmd_QUIT_func(ClientInfo *client)
|
|
{
|
|
client_send_ctrl_msg(client, "221 Goodbye senpai :'(\n");
|
|
}
|
|
|
|
static void cmd_SYST_func(ClientInfo *client)
|
|
{
|
|
client_send_ctrl_msg(client, "215 UNIX Type: L8\n");
|
|
}
|
|
|
|
static void cmd_PASV_func(ClientInfo *client)
|
|
{
|
|
int ret;
|
|
UNUSED(ret);
|
|
|
|
char cmd[512];
|
|
unsigned int namelen;
|
|
SceNetSockaddrIn picked;
|
|
|
|
/* Create data mode socket name */
|
|
char data_socket_name[64];
|
|
sprintf(data_socket_name, "FTPVita_client_%i_data_socket",
|
|
client->num);
|
|
|
|
/* Create the data socket */
|
|
client->data_sockfd = sceNetSocket(data_socket_name,
|
|
SCE_NET_AF_INET,
|
|
SCE_NET_SOCK_STREAM,
|
|
0);
|
|
|
|
DEBUG("PASV data socket fd: %d\n", client->data_sockfd);
|
|
|
|
/* Fill the data socket address */
|
|
client->data_sockaddr.sin_family = SCE_NET_AF_INET;
|
|
client->data_sockaddr.sin_addr.s_addr = sceNetHtonl(SCE_NET_INADDR_ANY);
|
|
/* Let the PSVita choose a port */
|
|
client->data_sockaddr.sin_port = sceNetHtons(0);
|
|
|
|
/* Bind the data socket address to the data socket */
|
|
ret = sceNetBind(client->data_sockfd,
|
|
(SceNetSockaddr *)&client->data_sockaddr,
|
|
sizeof(client->data_sockaddr));
|
|
DEBUG("sceNetBind(): 0x%08X\n", ret);
|
|
|
|
/* Start listening */
|
|
ret = sceNetListen(client->data_sockfd, 128);
|
|
DEBUG("sceNetListen(): 0x%08X\n", ret);
|
|
|
|
/* Get the port that the PSVita has chosen */
|
|
namelen = sizeof(picked);
|
|
sceNetGetsockname(client->data_sockfd, (SceNetSockaddr *)&picked,
|
|
&namelen);
|
|
|
|
DEBUG("PASV mode port: 0x%04X\n", picked.sin_port);
|
|
|
|
/* Build the command */
|
|
sprintf(cmd, "227 Entering Passive Mode (%hhu,%hhu,%hhu,%hhu,%hhu,%hhu)\n",
|
|
(vita_addr.s_addr >> 0) & 0xFF,
|
|
(vita_addr.s_addr >> 8) & 0xFF,
|
|
(vita_addr.s_addr >> 16) & 0xFF,
|
|
(vita_addr.s_addr >> 24) & 0xFF,
|
|
(picked.sin_port >> 0) & 0xFF,
|
|
(picked.sin_port >> 8) & 0xFF);
|
|
|
|
client_send_ctrl_msg(client, cmd);
|
|
|
|
/* Set the data connection type to passive! */
|
|
client->data_con_type = FTP_DATA_CONNECTION_PASSIVE;
|
|
}
|
|
|
|
static void cmd_PORT_func(ClientInfo *client)
|
|
{
|
|
unsigned char data_ip[4];
|
|
unsigned char porthi, portlo;
|
|
unsigned short data_port;
|
|
char ip_str[16];
|
|
SceNetInAddr data_addr;
|
|
|
|
sscanf(client->recv_buffer, "%*s %hhu,%hhu,%hhu,%hhu,%hhu,%hhu",
|
|
&data_ip[0], &data_ip[1], &data_ip[2], &data_ip[3],
|
|
&porthi, &portlo);
|
|
|
|
data_port = portlo + porthi*256;
|
|
|
|
/* Convert to an X.X.X.X IP string */
|
|
sprintf(ip_str, "%d.%d.%d.%d",
|
|
data_ip[0], data_ip[1], data_ip[2], data_ip[3]);
|
|
|
|
/* Convert the IP to a SceNetInAddr */
|
|
sceNetInetPton(SCE_NET_AF_INET, ip_str, &data_addr);
|
|
|
|
DEBUG("PORT connection to client's IP: %s Port: %d\n", ip_str, data_port);
|
|
|
|
/* Create data mode socket name */
|
|
char data_socket_name[64];
|
|
sprintf(data_socket_name, "FTPVita_client_%i_data_socket",
|
|
client->num);
|
|
|
|
/* Create data mode socket */
|
|
client->data_sockfd = sceNetSocket(data_socket_name,
|
|
SCE_NET_AF_INET,
|
|
SCE_NET_SOCK_STREAM,
|
|
0);
|
|
|
|
DEBUG("Client %i data socket fd: %d\n", client->num,
|
|
client->data_sockfd);
|
|
|
|
/* Prepare socket address for the data connection */
|
|
client->data_sockaddr.sin_family = SCE_NET_AF_INET;
|
|
client->data_sockaddr.sin_addr = data_addr;
|
|
client->data_sockaddr.sin_port = sceNetHtons(data_port);
|
|
|
|
/* Set the data connection type to active! */
|
|
client->data_con_type = FTP_DATA_CONNECTION_ACTIVE;
|
|
|
|
client_send_ctrl_msg(client, "200 PORT command successful!\n");
|
|
}
|
|
|
|
static void client_open_data_connection(ClientInfo *client)
|
|
{
|
|
int ret;
|
|
UNUSED(ret);
|
|
|
|
unsigned int addrlen;
|
|
|
|
if (client->data_con_type == FTP_DATA_CONNECTION_ACTIVE) {
|
|
/* Connect to the client using the data socket */
|
|
ret = sceNetConnect(client->data_sockfd,
|
|
(SceNetSockaddr *)&client->data_sockaddr,
|
|
sizeof(client->data_sockaddr));
|
|
|
|
DEBUG("sceNetConnect(): 0x%08X\n", ret);
|
|
} else {
|
|
/* Listen to the client using the data socket */
|
|
addrlen = sizeof(client->pasv_sockaddr);
|
|
client->pasv_sockfd = sceNetAccept(client->data_sockfd,
|
|
(SceNetSockaddr *)&client->pasv_sockaddr,
|
|
&addrlen);
|
|
DEBUG("PASV client fd: 0x%08X\n", client->pasv_sockfd);
|
|
}
|
|
}
|
|
|
|
static void client_close_data_connection(ClientInfo *client)
|
|
{
|
|
sceNetSocketClose(client->data_sockfd);
|
|
/* In passive mode we have to close the client pasv socket too */
|
|
if (client->data_con_type == FTP_DATA_CONNECTION_PASSIVE) {
|
|
sceNetSocketClose(client->pasv_sockfd);
|
|
}
|
|
client->data_con_type = FTP_DATA_CONNECTION_NONE;
|
|
}
|
|
|
|
static int gen_list_format(char *out, int n, int dir, unsigned int file_size,
|
|
int month_n, int day_n, int hour, int minute, const char *filename)
|
|
{
|
|
static const char num_to_month[][4] = {
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
|
|
return snprintf(out, n,
|
|
"%c%s 1 vita vita %d %s %-2d %02d:%02d %s\r\n",
|
|
dir ? 'd' : '-',
|
|
dir ? "rwxr-xr-x" : "rw-r--r--",
|
|
file_size,
|
|
num_to_month[(month_n-1)%12],
|
|
day_n,
|
|
hour,
|
|
minute,
|
|
filename);
|
|
}
|
|
|
|
static void send_LIST(ClientInfo *client, const char *path)
|
|
{
|
|
char buffer[512];
|
|
|
|
if (strcmp(path, HOME_PATH"/") == 0) {
|
|
client_send_ctrl_msg(client, "150 Opening ASCII mode data transfer for LIST.\n");
|
|
|
|
client_open_data_connection(client);
|
|
|
|
int i;
|
|
for (i = 0; i < getNumberMountPoints(); i++) {
|
|
char **mount_points = getMountPoints();
|
|
if (mount_points[i]) {
|
|
char *name = mount_points[i];
|
|
|
|
SceIoStat stat;
|
|
memset(&stat, 0, sizeof(SceIoStat));
|
|
sceIoGetstat(name, &stat);
|
|
|
|
gen_list_format(buffer, sizeof(buffer),
|
|
1,
|
|
stat.st_size,
|
|
stat.st_mtime.month,
|
|
stat.st_mtime.day,
|
|
stat.st_mtime.hour,
|
|
stat.st_mtime.minute,
|
|
name);
|
|
|
|
client_send_data_msg(client, buffer);
|
|
memset(buffer, 0, sizeof(buffer));
|
|
}
|
|
}
|
|
} else {
|
|
SceUID dir;
|
|
SceIoDirent dirent;
|
|
|
|
char vita_path[PATH_MAX];
|
|
strcpy(vita_path, path + HOME_PREFIX_LENGTH);
|
|
dir = sceIoDopen(vita_path);
|
|
if (dir < 0) {
|
|
client_send_ctrl_msg(client, "550 Invalid directory.\n");
|
|
return;
|
|
}
|
|
|
|
client_send_ctrl_msg(client, "150 Opening ASCII mode data transfer for LIST.\n");
|
|
|
|
client_open_data_connection(client);
|
|
|
|
memset(&dirent, 0, sizeof(dirent));
|
|
|
|
while (sceIoDread(dir, &dirent) > 0) {
|
|
gen_list_format(buffer, sizeof(buffer),
|
|
SCE_S_ISDIR(dirent.d_stat.st_mode),
|
|
dirent.d_stat.st_size,
|
|
dirent.d_stat.st_mtime.month,
|
|
dirent.d_stat.st_mtime.day,
|
|
dirent.d_stat.st_mtime.hour,
|
|
dirent.d_stat.st_mtime.minute,
|
|
dirent.d_name);
|
|
|
|
client_send_data_msg(client, buffer);
|
|
memset(&dirent, 0, sizeof(dirent));
|
|
memset(buffer, 0, sizeof(buffer));
|
|
}
|
|
|
|
sceIoDclose(dir);
|
|
}
|
|
|
|
DEBUG("Done sending LIST\n");
|
|
|
|
client_close_data_connection(client);
|
|
client_send_ctrl_msg(client, "226 Transfer complete.\n");
|
|
}
|
|
|
|
static void cmd_LIST_func(ClientInfo *client)
|
|
{
|
|
char list_path[PATH_MAX];
|
|
|
|
int n = sscanf(client->recv_buffer, "%*s %[^\r\n ]", list_path);
|
|
|
|
if (n > 0) { /* Client specified a path */
|
|
send_LIST(client, list_path);
|
|
} else { /* Use current path */
|
|
send_LIST(client, client->cur_path);
|
|
}
|
|
}
|
|
|
|
static void cmd_PWD_func(ClientInfo *client)
|
|
{
|
|
char msg[PATH_MAX];
|
|
/* We don't want to send the prefix */
|
|
const char *pwd_path = strchr(client->cur_path, '/');
|
|
|
|
sprintf(msg, "257 \"%s\" is the current directory.\n", pwd_path);
|
|
client_send_ctrl_msg(client, msg);
|
|
}
|
|
|
|
static void cmd_CWD_func(ClientInfo *client)
|
|
{
|
|
char cmd_path[PATH_MAX];
|
|
char path[PATH_MAX];
|
|
SceUID pd;
|
|
int n = sscanf(client->recv_buffer, "%*s %[^\r\n ]", cmd_path);
|
|
|
|
if (n < 1) {
|
|
client_send_ctrl_msg(client, "500 Syntax error, command unrecognized.\n");
|
|
} else {
|
|
if (cmd_path[0] != '/') { /* Change dir relative to current dir */
|
|
sprintf(path, "%s%s", client->cur_path, cmd_path);
|
|
} else {
|
|
sprintf(path, "%s%s", FTP_DEFAULT_PREFIX, cmd_path);
|
|
}
|
|
|
|
/* If there isn't "/" at the end, add it */
|
|
if (path[strlen(path) - 1] != '/') {
|
|
strcat(path, "/");
|
|
}
|
|
|
|
/* Check if the path exists */
|
|
char vita_path[PATH_MAX];
|
|
strcpy(vita_path, path + HOME_PREFIX_LENGTH);
|
|
pd = sceIoDopen(vita_path);
|
|
if (pd < 0) {
|
|
client_send_ctrl_msg(client, "550 Invalid directory.\n");
|
|
return;
|
|
}
|
|
sceIoDclose(pd);
|
|
|
|
strcpy(client->cur_path, path);
|
|
client_send_ctrl_msg(client, "250 Requested file action okay, completed.\n");
|
|
}
|
|
}
|
|
|
|
static void cmd_TYPE_func(ClientInfo *client)
|
|
{
|
|
char data_type;
|
|
char format_control[8];
|
|
int n_args = sscanf(client->recv_buffer, "%*s %c %s", &data_type, format_control);
|
|
|
|
if (n_args > 0) {
|
|
switch(data_type) {
|
|
case 'A':
|
|
case 'I':
|
|
client_send_ctrl_msg(client, "200 Okay\n");
|
|
break;
|
|
case 'E':
|
|
case 'L':
|
|
default:
|
|
client_send_ctrl_msg(client, "504 Error: bad parameters?\n");
|
|
break;
|
|
}
|
|
} else {
|
|
client_send_ctrl_msg(client, "504 Error: bad parameters?\n");
|
|
}
|
|
}
|
|
|
|
static void dir_up(char *path)
|
|
{
|
|
char *pch;
|
|
size_t len_in = strlen(path);
|
|
if (len_in == 1) {
|
|
strcpy(path, "/");
|
|
return;
|
|
}
|
|
if (path[len_in - 1] == '/') {
|
|
path[len_in - 1] = '\0';
|
|
}
|
|
pch = strrchr(path, '/');
|
|
if (pch) {
|
|
size_t s = len_in - (pch - path);
|
|
memset(pch + 1, '\0', s);
|
|
}
|
|
}
|
|
|
|
static void cmd_CDUP_func(ClientInfo *client)
|
|
{
|
|
char path[PATH_MAX];
|
|
/* Path without the prefix */
|
|
const char *normal_path = strchr(client->cur_path, '/');
|
|
strcpy(path, normal_path);
|
|
dir_up(path);
|
|
sprintf(client->cur_path, "%s%s", FTP_DEFAULT_PREFIX, path);
|
|
client_send_ctrl_msg(client, "200 Command okay.\n");
|
|
}
|
|
|
|
static void send_file(ClientInfo *client, const char *path)
|
|
{
|
|
unsigned char *buffer;
|
|
SceUID fd;
|
|
unsigned int bytes_read;
|
|
|
|
DEBUG("Opening: %s\n", path);
|
|
|
|
if ((fd = sceIoOpen(path, SCE_O_RDONLY, 0777)) >= 0) {
|
|
|
|
buffer = malloc(FILE_BUF_SIZE);
|
|
if (buffer == NULL) {
|
|
client_send_ctrl_msg(client, "550 Could not allocate memory.\n");
|
|
return;
|
|
}
|
|
|
|
client_open_data_connection(client);
|
|
client_send_ctrl_msg(client, "150 Opening Image mode data transfer.\n");
|
|
|
|
while ((bytes_read = sceIoRead (fd, buffer, FILE_BUF_SIZE)) > 0) {
|
|
client_send_data_raw(client, buffer, bytes_read);
|
|
}
|
|
|
|
sceIoClose(fd);
|
|
free(buffer);
|
|
client_send_ctrl_msg(client, "226 Transfer completed.\n");
|
|
client_close_data_connection(client);
|
|
|
|
} else {
|
|
client_send_ctrl_msg(client, "550 File not found.\n");
|
|
}
|
|
}
|
|
|
|
/* This function generates a PSVita valid path with the input path
|
|
* from RETR, STOR, DELE, RMD, MKD, RNFR and RNTO commands */
|
|
static void gen_filepath(ClientInfo *client, char *vita_path)
|
|
{
|
|
char path[PATH_MAX];
|
|
char cmd_path[PATH_MAX];
|
|
sscanf(client->recv_buffer, "%*[^ ] %[^\r\n ]", cmd_path);
|
|
|
|
if (cmd_path[0] != '/') { /* The file is relative to current dir */
|
|
/* Append the file to the current path */
|
|
sprintf(path, "%s%s", client->cur_path, cmd_path);
|
|
} else {
|
|
/* Add the prefix to the file */
|
|
sprintf(path, "%s%s", FTP_DEFAULT_PREFIX, cmd_path);
|
|
}
|
|
|
|
strcpy(vita_path, path + HOME_PREFIX_LENGTH);
|
|
}
|
|
|
|
static void cmd_RETR_func(ClientInfo *client)
|
|
{
|
|
char dest_path[PATH_MAX];
|
|
gen_filepath(client, dest_path);
|
|
send_file(client, dest_path);
|
|
}
|
|
|
|
static void receive_file(ClientInfo *client, const char *path)
|
|
{
|
|
unsigned char *buffer;
|
|
SceUID fd;
|
|
unsigned int bytes_recv;
|
|
|
|
DEBUG("Opening: %s\n", path);
|
|
|
|
if ((fd = sceIoOpen(path, SCE_O_CREAT | SCE_O_WRONLY | SCE_O_TRUNC, 0777)) >= 0) {
|
|
|
|
buffer = malloc(FILE_BUF_SIZE);
|
|
if (buffer == NULL) {
|
|
client_send_ctrl_msg(client, "550 Could not allocate memory.\n");
|
|
return;
|
|
}
|
|
|
|
client_open_data_connection(client);
|
|
client_send_ctrl_msg(client, "150 Opening Image mode data transfer.\n");
|
|
|
|
while ((bytes_recv = client_send_recv_raw(client, buffer, FILE_BUF_SIZE)) > 0) {
|
|
sceIoWrite(fd, buffer, bytes_recv);
|
|
}
|
|
|
|
sceIoClose(fd);
|
|
free(buffer);
|
|
client_send_ctrl_msg(client, "226 Transfer completed.\n");
|
|
client_close_data_connection(client);
|
|
|
|
} else {
|
|
client_send_ctrl_msg(client, "550 File not found.\n");
|
|
}
|
|
}
|
|
|
|
static void cmd_STOR_func(ClientInfo *client)
|
|
{
|
|
char dest_path[PATH_MAX];
|
|
gen_filepath(client, dest_path);
|
|
receive_file(client, dest_path);
|
|
}
|
|
|
|
static void delete_file(ClientInfo *client, const char *path)
|
|
{
|
|
DEBUG("Deleting: %s\n", path);
|
|
|
|
if (sceIoRemove(path) >= 0) {
|
|
client_send_ctrl_msg(client, "226 File deleted.\n");
|
|
} else {
|
|
client_send_ctrl_msg(client, "550 Could not delete the file.\n");
|
|
}
|
|
}
|
|
|
|
static void cmd_DELE_func(ClientInfo *client)
|
|
{
|
|
char dest_path[PATH_MAX];
|
|
gen_filepath(client, dest_path);
|
|
delete_file(client, dest_path);
|
|
}
|
|
|
|
static void delete_dir(ClientInfo *client, const char *path)
|
|
{
|
|
int ret;
|
|
DEBUG("Deleting: %s\n", path);
|
|
ret = sceIoRmdir(path);
|
|
if (ret >= 0) {
|
|
client_send_ctrl_msg(client, "226 Directory deleted.\n");
|
|
} else if (ret == 0x8001005A) { /* DIRECTORY_IS_NOT_EMPTY */
|
|
client_send_ctrl_msg(client, "550 Directory is not empty.\n");
|
|
} else {
|
|
client_send_ctrl_msg(client, "550 Could not delete the directory.\n");
|
|
}
|
|
}
|
|
|
|
static void cmd_RMD_func(ClientInfo *client)
|
|
{
|
|
char dest_path[PATH_MAX];
|
|
gen_filepath(client, dest_path);
|
|
delete_dir(client, dest_path);
|
|
}
|
|
|
|
static void create_dir(ClientInfo *client, const char *path)
|
|
{
|
|
DEBUG("Creating: %s\n", path);
|
|
|
|
if (sceIoMkdir(path, 0777) >= 0) {
|
|
client_send_ctrl_msg(client, "226 Directory created.\n");
|
|
} else {
|
|
client_send_ctrl_msg(client, "550 Could not create the directory.\n");
|
|
}
|
|
}
|
|
|
|
static void cmd_MKD_func(ClientInfo *client)
|
|
{
|
|
char dest_path[PATH_MAX];
|
|
gen_filepath(client, dest_path);
|
|
create_dir(client, dest_path);
|
|
}
|
|
|
|
static int file_exists(const char *path)
|
|
{
|
|
SceIoStat stat;
|
|
return (sceIoGetstat(path, &stat) >= 0);
|
|
}
|
|
|
|
static void cmd_RNFR_func(ClientInfo *client)
|
|
{
|
|
char from_path[PATH_MAX];
|
|
/* Get the origin filename */
|
|
gen_filepath(client, from_path);
|
|
|
|
/* Check if the file exists */
|
|
if (!file_exists(from_path)) {
|
|
client_send_ctrl_msg(client, "550 The file doesn't exist.\n");
|
|
return;
|
|
}
|
|
/* The file to be renamed is the received path */
|
|
strcpy(client->rename_path, from_path);
|
|
client_send_ctrl_msg(client, "250 I need the destination name b0ss.\n");
|
|
}
|
|
|
|
static void cmd_RNTO_func(ClientInfo *client)
|
|
{
|
|
char path_to[PATH_MAX];
|
|
/* Get the destination filename */
|
|
gen_filepath(client, path_to);
|
|
|
|
DEBUG("Renaming: %s to %s\n", client->rename_path, path_to);
|
|
|
|
if (sceIoRename(client->rename_path, path_to) < 0) {
|
|
client_send_ctrl_msg(client, "550 Error renaming the file.\n");
|
|
}
|
|
|
|
client_send_ctrl_msg(client, "226 Rename completed.\n");
|
|
}
|
|
|
|
static void cmd_SIZE_func(ClientInfo *client)
|
|
{
|
|
SceIoStat stat;
|
|
char path[PATH_MAX];
|
|
char cmd[64];
|
|
/* Get the filename to retrieve its size */
|
|
gen_filepath(client, path);
|
|
|
|
/* Check if the file exists */
|
|
if (sceIoGetstat(path, &stat) < 0) {
|
|
client_send_ctrl_msg(client, "550 The file doesn't exist.\n");
|
|
return;
|
|
}
|
|
/* Send the size of the file */
|
|
sprintf(cmd, "213: %lld\n", stat.st_size);
|
|
client_send_ctrl_msg(client, cmd);
|
|
}
|
|
|
|
#define add_entry(name) {#name, cmd_##name##_func}
|
|
static const cmd_dispatch_entry cmd_dispatch_table[] = {
|
|
add_entry(USER),
|
|
add_entry(PASS),
|
|
add_entry(QUIT),
|
|
add_entry(SYST),
|
|
add_entry(PASV),
|
|
add_entry(PORT),
|
|
add_entry(LIST),
|
|
add_entry(PWD),
|
|
add_entry(CWD),
|
|
add_entry(TYPE),
|
|
add_entry(CDUP),
|
|
add_entry(RETR),
|
|
add_entry(STOR),
|
|
add_entry(DELE),
|
|
add_entry(RMD),
|
|
add_entry(MKD),
|
|
add_entry(RNFR),
|
|
add_entry(RNTO),
|
|
add_entry(SIZE),
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static cmd_dispatch_func get_dispatch_func(const char *cmd)
|
|
{
|
|
int i;
|
|
for(i = 0; cmd_dispatch_table[i].cmd && cmd_dispatch_table[i].func; i++) {
|
|
if (strcmp(cmd, cmd_dispatch_table[i].cmd) == 0) {
|
|
return cmd_dispatch_table[i].func;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void client_list_add(ClientInfo *client)
|
|
{
|
|
/* Add the client at the front of the client list */
|
|
sceKernelLockMutex(client_list_mtx, 1, NULL);
|
|
|
|
if (client_list == NULL) { /* List is empty */
|
|
client_list = client;
|
|
client->prev = NULL;
|
|
client->next = NULL;
|
|
} else {
|
|
client->next = client_list;
|
|
client->next->prev = client;
|
|
client->prev = NULL;
|
|
client_list = client;
|
|
}
|
|
|
|
sceKernelUnlockMutex(client_list_mtx, 1);
|
|
}
|
|
|
|
static void client_list_delete(ClientInfo *client)
|
|
{
|
|
/* Remove the client from the client list */
|
|
sceKernelLockMutex(client_list_mtx, 1, NULL);
|
|
|
|
if (client->prev) {
|
|
client->prev->next = client->next;
|
|
}
|
|
if (client->next) {
|
|
client->next->prev = client->prev;
|
|
}
|
|
|
|
sceKernelUnlockMutex(client_list_mtx, 1);
|
|
}
|
|
|
|
static void client_list_thread_end()
|
|
{
|
|
ClientInfo *it, *next;
|
|
SceUID client_thid;
|
|
|
|
sceKernelLockMutex(client_list_mtx, 1, NULL);
|
|
|
|
it = client_list;
|
|
|
|
/* Iterate over the client list and close their sockets */
|
|
while (it) {
|
|
next = it->next;
|
|
client_thid = it->thid;
|
|
|
|
/* Shutdown the client's control socket */
|
|
sceNetShutdown(it->ctrl_sockfd, SCE_NET_SHUT_RDWR);
|
|
|
|
/* Wait until the client threads ends */
|
|
sceKernelWaitThreadEnd(client_thid, NULL, NULL);
|
|
|
|
it = next;
|
|
}
|
|
|
|
sceKernelUnlockMutex(client_list_mtx, 1);
|
|
}
|
|
|
|
static int client_thread(SceSize args, void *argp)
|
|
{
|
|
char cmd[16];
|
|
cmd_dispatch_func dispatch_func;
|
|
ClientInfo *client = *(ClientInfo **)argp;
|
|
|
|
DEBUG("Client thread %i started!\n", client->num);
|
|
|
|
client_send_ctrl_msg(client, "220 FTPVita Server ready.\n");
|
|
|
|
while (1) {
|
|
|
|
memset(client->recv_buffer, 0, sizeof(client->recv_buffer));
|
|
|
|
client->n_recv = sceNetRecv(client->ctrl_sockfd, client->recv_buffer, sizeof(client->recv_buffer), 0);
|
|
if (client->n_recv > 0) {
|
|
DEBUG("Received %i bytes from client number %i:\n",
|
|
client->n_recv, client->num);
|
|
|
|
INFO(" %i> %s", client->num, client->recv_buffer);
|
|
|
|
/* The command are the first chars until the first space */
|
|
sscanf(client->recv_buffer, "%s", cmd);
|
|
|
|
/* Wait 1 ms before sending any data */
|
|
sceKernelDelayThread(1*1000);
|
|
|
|
if ((dispatch_func = get_dispatch_func(cmd))) {
|
|
dispatch_func(client);
|
|
} else {
|
|
client_send_ctrl_msg(client, "502 Sorry, command not implemented. :(\n");
|
|
}
|
|
|
|
} else if (client->n_recv == 0) {
|
|
/* Value 0 means connection closed by the remote peer */
|
|
INFO("Connection closed by the client %i.\n", client->num);
|
|
/* Delete itself from the client list */
|
|
client_list_delete(client);
|
|
break;
|
|
} else if (client->n_recv == SCE_NET_ERROR_ECANCELED) {
|
|
/* SCE_NET_ERROR_ECANCELED means socket shutdown */
|
|
DEBUG("Client %i socket shutdown\n", client->num);
|
|
break;
|
|
} else {
|
|
/* Other errors */
|
|
INFO("Socket error client %i.\n", client->num);
|
|
client_list_delete(client);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Close the client's socket */
|
|
sceNetSocketClose(client->ctrl_sockfd);
|
|
|
|
/* If there's an open data connection, close it */
|
|
if (client->data_con_type != FTP_DATA_CONNECTION_NONE) {
|
|
sceNetSocketClose(client->data_sockfd);
|
|
if (client->data_con_type == FTP_DATA_CONNECTION_PASSIVE) {
|
|
sceNetSocketClose(client->pasv_sockfd);
|
|
}
|
|
}
|
|
|
|
DEBUG("Client thread %i exiting!\n", client->num);
|
|
|
|
free(client);
|
|
|
|
sceKernelExitDeleteThread(0);
|
|
return 0;
|
|
}
|
|
|
|
static int server_thread(SceSize args, void *argp)
|
|
{
|
|
int ret;
|
|
UNUSED(ret);
|
|
|
|
SceNetSockaddrIn serveraddr;
|
|
|
|
DEBUG("Server thread started!\n");
|
|
|
|
/* Create server socket */
|
|
server_sockfd = sceNetSocket("FTPVita_server_sock",
|
|
SCE_NET_AF_INET,
|
|
SCE_NET_SOCK_STREAM,
|
|
0);
|
|
|
|
DEBUG("Server socket fd: %d\n", server_sockfd);
|
|
|
|
/* Fill the server's address */
|
|
serveraddr.sin_family = SCE_NET_AF_INET;
|
|
serveraddr.sin_addr.s_addr = sceNetHtonl(SCE_NET_INADDR_ANY);
|
|
serveraddr.sin_port = sceNetHtons(FTP_PORT);
|
|
|
|
/* Bind the server's address to the socket */
|
|
ret = sceNetBind(server_sockfd, (SceNetSockaddr *)&serveraddr, sizeof(serveraddr));
|
|
DEBUG("sceNetBind(): 0x%08X\n", ret);
|
|
|
|
/* Start listening */
|
|
ret = sceNetListen(server_sockfd, 128);
|
|
DEBUG("sceNetListen(): 0x%08X\n", ret);
|
|
|
|
while (1) {
|
|
|
|
/* Accept clients */
|
|
SceNetSockaddrIn clientaddr;
|
|
int client_sockfd;
|
|
unsigned int addrlen = sizeof(clientaddr);
|
|
|
|
DEBUG("Waiting for incoming connections...\n");
|
|
|
|
client_sockfd = sceNetAccept(server_sockfd, (SceNetSockaddr *)&clientaddr, &addrlen);
|
|
if (client_sockfd >= 0) {
|
|
|
|
DEBUG("New connection, client fd: 0x%08X\n", client_sockfd);
|
|
|
|
/* Get the client's IP address */
|
|
char remote_ip[16];
|
|
sceNetInetNtop(SCE_NET_AF_INET,
|
|
&clientaddr.sin_addr.s_addr,
|
|
remote_ip,
|
|
sizeof(remote_ip));
|
|
|
|
INFO("Client %i connected, IP: %s port: %i\n",
|
|
number_clients, remote_ip, clientaddr.sin_port);
|
|
|
|
/* Create a new thread for the client */
|
|
char client_thread_name[64];
|
|
sprintf(client_thread_name, "FTPVita_client_%i_thread",
|
|
number_clients);
|
|
|
|
SceUID client_thid = sceKernelCreateThread(
|
|
client_thread_name, client_thread,
|
|
0x10000100, 0x10000, 0, 0, NULL);
|
|
|
|
DEBUG("Client %i thread UID: 0x%08X\n", number_clients, client_thid);
|
|
|
|
/* Allocate the ClientInfo struct for the new client */
|
|
ClientInfo *client = malloc(sizeof(*client));
|
|
client->num = number_clients;
|
|
client->thid = client_thid;
|
|
client->ctrl_sockfd = client_sockfd;
|
|
client->data_con_type = FTP_DATA_CONNECTION_NONE;
|
|
sprintf(client->cur_path, "%s%s", FTP_DEFAULT_PREFIX, FTP_DEFAULT_PATH);
|
|
memcpy(&client->addr, &clientaddr, sizeof(client->addr));
|
|
|
|
/* Add the new client to the client list */
|
|
client_list_add(client);
|
|
|
|
/* Start the client thread */
|
|
sceKernelStartThread(client_thid, sizeof(client), &client);
|
|
|
|
number_clients++;
|
|
} else {
|
|
/* if sceNetAccept returns < 0, it means that the listening
|
|
* socket has been closed, this means that we want to
|
|
* finish the server thread */
|
|
DEBUG("Server socket closed, 0x%08X\n", client_sockfd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DEBUG("Server thread exiting!\n");
|
|
|
|
sceKernelExitDeleteThread(0);
|
|
return 0;
|
|
}
|
|
|
|
int ftp_init(char *vita_ip, unsigned short int *vita_port)
|
|
{
|
|
int ret;
|
|
UNUSED(ret);
|
|
|
|
SceNetInitParam initparam;
|
|
SceNetCtlInfo info;
|
|
|
|
if (ftp_initialized) {
|
|
return -1;
|
|
}
|
|
|
|
/* Init Net */
|
|
ret = sceNetShowNetstat();
|
|
if (ret == 0) {
|
|
DEBUG("Net is already initialized.\n");
|
|
net_init = -1;
|
|
} else if (ret == SCE_NET_ERROR_ENOTINIT) {
|
|
net_memory = malloc(NET_INIT_SIZE);
|
|
|
|
initparam.memory = net_memory;
|
|
initparam.size = NET_INIT_SIZE;
|
|
initparam.flags = 0;
|
|
|
|
ret = net_init = sceNetInit(&initparam);
|
|
DEBUG("sceNetInit(): 0x%08X\n", net_init);
|
|
if (net_init < 0)
|
|
goto error_netinit;
|
|
} else {
|
|
INFO("Net error: 0x%08X\n", net_init);
|
|
goto error_netstat;
|
|
}
|
|
|
|
/* Init NetCtl */
|
|
ret = netctl_init = sceNetCtlInit();
|
|
DEBUG("sceNetCtlInit(): 0x%08X\n", netctl_init);
|
|
if (netctl_init < 0 && netctl_init != NET_CTL_ERROR_NOT_TERMINATED)
|
|
goto error_netctlinit;
|
|
|
|
/* Get IP address */
|
|
ret = sceNetCtlInetGetInfo(SCE_NETCTL_INFO_GET_IP_ADDRESS, &info);
|
|
DEBUG("sceNetCtlInetGetInfo(): 0x%08X\n", ret);
|
|
if (ret < 0)
|
|
goto error_netctlgetinfo;
|
|
|
|
/* Return data */
|
|
strcpy(vita_ip, info.ip_address);
|
|
*vita_port = FTP_PORT;
|
|
|
|
/* Save the IP of PSVita to a global variable */
|
|
sceNetInetPton(SCE_NET_AF_INET, info.ip_address, &vita_addr);
|
|
|
|
/* Create server thread */
|
|
server_thid = sceKernelCreateThread("FTPVita_server_thread",
|
|
server_thread, 0x10000100, 0x10000, 0, 0, NULL);
|
|
DEBUG("Server thread UID: 0x%08X\n", server_thid);
|
|
|
|
/* Create the client list mutex */
|
|
client_list_mtx = sceKernelCreateMutex("FTPVita_client_list_mutex", 0, 0, NULL);
|
|
DEBUG("Client list mutex UID: 0x%08X\n", client_list_mtx);
|
|
|
|
/* Start the server thread */
|
|
sceKernelStartThread(server_thid, 0, NULL);
|
|
|
|
ftp_initialized = 1;
|
|
|
|
return 0;
|
|
|
|
error_netctlgetinfo:
|
|
if (netctl_init == 0) {
|
|
sceNetCtlTerm();
|
|
netctl_init = -1;
|
|
}
|
|
error_netctlinit:
|
|
if (net_init == 0) {
|
|
sceNetTerm();
|
|
net_init = -1;
|
|
}
|
|
error_netinit:
|
|
if (net_memory) {
|
|
free(net_memory);
|
|
net_memory = NULL;
|
|
}
|
|
error_netstat:
|
|
return ret;
|
|
}
|
|
|
|
void ftp_fini()
|
|
{
|
|
if (ftp_initialized) {
|
|
/* In order to "stop" the blocking sceNetAccept,
|
|
* we have to close the server socket; this way
|
|
* the accept call will return an error */
|
|
sceNetSocketClose(server_sockfd);
|
|
|
|
/* Wait until the server threads ends */
|
|
sceKernelWaitThreadEnd(server_thid, NULL, NULL);
|
|
|
|
/* To close the clients we have to do the same:
|
|
* we have to iterate over all the clients
|
|
* and shutdown their sockets */
|
|
client_list_thread_end();
|
|
|
|
/* Delete the client list mutex */
|
|
sceKernelDeleteMutex(client_list_mtx);
|
|
|
|
client_list = NULL;
|
|
|
|
if (netctl_init == 0)
|
|
sceNetCtlTerm();
|
|
if (net_init == 0)
|
|
sceNetTerm();
|
|
if (net_memory)
|
|
free(net_memory);
|
|
|
|
netctl_init = -1;
|
|
net_init = -1;
|
|
net_memory = NULL;
|
|
ftp_initialized = 0;
|
|
}
|
|
}
|
|
|
|
int ftp_is_initialized()
|
|
{
|
|
return ftp_initialized;
|
|
}
|