Implement networking syscalls, ftp and wget apps (AI)
This commit is contained in:
302
apps/ftp/ftp.c
Normal file
302
apps/ftp/ftp.c
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* @file ftp.c
|
||||
* @brief Simple FTP client for ClaudeOS.
|
||||
*
|
||||
* Connects to an FTP server and provides a minimal interactive
|
||||
* interface for sending FTP commands and viewing responses.
|
||||
*
|
||||
* Usage:
|
||||
* ftp <ip>[:<port>]
|
||||
*
|
||||
* Examples:
|
||||
* ftp 10.0.2.2
|
||||
* ftp 192.168.1.1:2121
|
||||
*
|
||||
* Once connected, type FTP commands directly:
|
||||
* USER anonymous
|
||||
* PASS user@
|
||||
* LIST
|
||||
* QUIT
|
||||
*
|
||||
* The client handles the control connection. PASV data connections
|
||||
* are not supported in this minimal version.
|
||||
*/
|
||||
|
||||
#include "syscalls.h"
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
|
||||
/**
|
||||
* Parse a decimal number from a string.
|
||||
*/
|
||||
static uint32_t parse_uint(const char **s) {
|
||||
uint32_t val = 0;
|
||||
while (**s >= '0' && **s <= '9') {
|
||||
val = val * 10 + (uint32_t)(**s - '0');
|
||||
(*s)++;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an IPv4 address.
|
||||
*/
|
||||
static uint32_t parse_ip(const char **s) {
|
||||
uint32_t ip = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint32_t octet = parse_uint(s);
|
||||
if (octet > 255) return 0;
|
||||
ip = (ip << 8) | octet;
|
||||
if (i < 3) {
|
||||
if (**s != '.') return 0;
|
||||
(*s)++;
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a decimal number.
|
||||
*/
|
||||
static void print_dec(uint32_t val) {
|
||||
char buf[12];
|
||||
int i = 0;
|
||||
if (val == 0) { putchar('0'); return; }
|
||||
while (val > 0) {
|
||||
buf[i++] = '0' + (char)(val % 10);
|
||||
val /= 10;
|
||||
}
|
||||
while (i > 0) putchar(buf[--i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format IP address.
|
||||
*/
|
||||
static void ip_to_str(uint32_t ip, char *buf) {
|
||||
int pos = 0;
|
||||
for (int i = 3; i >= 0; i--) {
|
||||
uint32_t octet = (ip >> (i * 8)) & 0xFF;
|
||||
char tmp[4]; int ti = 0;
|
||||
if (octet == 0) { tmp[ti++] = '0'; }
|
||||
else { while (octet > 0) { tmp[ti++] = '0' + (char)(octet % 10); octet /= 10; } }
|
||||
while (ti > 0) buf[pos++] = tmp[--ti];
|
||||
if (i > 0) buf[pos++] = '.';
|
||||
}
|
||||
buf[pos] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a line from stdin with echo.
|
||||
* Returns length (excluding newline).
|
||||
*/
|
||||
static int readline(char *buf, int maxlen) {
|
||||
int pos = 0;
|
||||
while (pos < maxlen - 1) {
|
||||
char c;
|
||||
int32_t n = read(0, &c, 1);
|
||||
if (n <= 0) { yield(); continue; }
|
||||
if (c == '\n' || c == '\r') { putchar('\n'); break; }
|
||||
if (c == '\b' || c == 127) {
|
||||
if (pos > 0) { pos--; puts("\b \b"); }
|
||||
} else if (c >= 32) {
|
||||
buf[pos++] = c;
|
||||
putchar(c);
|
||||
}
|
||||
}
|
||||
buf[pos] = '\0';
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and print FTP server response.
|
||||
* Reads until we get a line starting with a 3-digit code followed by space.
|
||||
* Returns the response code, or -1 on error.
|
||||
*/
|
||||
static int32_t recv_response(int32_t sockfd) {
|
||||
char buf[512];
|
||||
int total = 0;
|
||||
int timeout = 500;
|
||||
int code = -1;
|
||||
|
||||
while (timeout > 0) {
|
||||
int32_t n = net_recv(sockfd, buf + total,
|
||||
(uint32_t)(sizeof(buf) - 1 - (uint32_t)total));
|
||||
if (n > 0) {
|
||||
total += n;
|
||||
buf[total] = '\0';
|
||||
|
||||
/* Print received data */
|
||||
write(1, buf + total - n, (uint32_t)n);
|
||||
|
||||
/* Check if we have a complete response.
|
||||
* FTP response ends when we get "NNN " (3 digits + space) at start of line. */
|
||||
int got_complete = 0;
|
||||
for (int i = 0; i < total; i++) {
|
||||
/* Check for line start */
|
||||
if (i == 0 || (i > 0 && buf[i-1] == '\n')) {
|
||||
/* Check for "NNN " pattern */
|
||||
if (i + 3 < total &&
|
||||
buf[i] >= '0' && buf[i] <= '9' &&
|
||||
buf[i+1] >= '0' && buf[i+1] <= '9' &&
|
||||
buf[i+2] >= '0' && buf[i+2] <= '9' &&
|
||||
buf[i+3] == ' ') {
|
||||
code = (buf[i] - '0') * 100 +
|
||||
(buf[i+1] - '0') * 10 +
|
||||
(buf[i+2] - '0');
|
||||
got_complete = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (got_complete) break;
|
||||
timeout = 200;
|
||||
} else if (n < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
yield();
|
||||
timeout--;
|
||||
}
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an FTP command (appends \r\n).
|
||||
*/
|
||||
static int32_t send_cmd(int32_t sockfd, const char *cmd) {
|
||||
char buf[256];
|
||||
uint32_t len = 0;
|
||||
while (cmd[len] && len < sizeof(buf) - 3) {
|
||||
buf[len] = cmd[len];
|
||||
len++;
|
||||
}
|
||||
buf[len++] = '\r';
|
||||
buf[len++] = '\n';
|
||||
return net_send(sockfd, buf, len);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char arg[256];
|
||||
|
||||
if (getenv("ARG1", arg, sizeof(arg)) < 0 || arg[0] == '\0') {
|
||||
puts("Usage: ftp <ip>[:<port>]\n");
|
||||
puts(" e.g. ftp 10.0.2.2\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Parse IP and port */
|
||||
const char *p = arg;
|
||||
uint32_t ip = parse_ip(&p);
|
||||
if (ip == 0) {
|
||||
puts("ftp: invalid IP address\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t port = 21;
|
||||
if (*p == ':') {
|
||||
p++;
|
||||
port = parse_uint(&p);
|
||||
if (port == 0 || port > 65535) {
|
||||
puts("ftp: invalid port\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
char ip_str[64];
|
||||
ip_to_str(ip, ip_str);
|
||||
|
||||
puts("Connecting to ");
|
||||
puts(ip_str);
|
||||
putchar(':');
|
||||
print_dec(port);
|
||||
puts("...\n");
|
||||
|
||||
/* Create TCP socket */
|
||||
int32_t sockfd = socket(SOCK_TCP);
|
||||
if (sockfd < 0) {
|
||||
puts("ftp: failed to create socket\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Connect */
|
||||
if (connect(sockfd, ip, port) < 0) {
|
||||
puts("ftp: connect failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Wait for connection */
|
||||
int timeout = 500;
|
||||
while (timeout > 0) {
|
||||
int32_t state = sockstate(sockfd);
|
||||
if (state == TCP_STATE_ESTABLISHED) break;
|
||||
if (state == TCP_STATE_CLOSED) {
|
||||
puts("ftp: connection refused\n");
|
||||
return 1;
|
||||
}
|
||||
yield();
|
||||
timeout--;
|
||||
}
|
||||
if (timeout <= 0) {
|
||||
puts("ftp: connection timed out\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
puts("Connected to ");
|
||||
puts(ip_str);
|
||||
puts("\n");
|
||||
|
||||
/* Read server welcome banner */
|
||||
int32_t code = recv_response(sockfd);
|
||||
if (code < 0 || code >= 400) {
|
||||
puts("ftp: server rejected connection\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Interactive command loop */
|
||||
char cmd[256];
|
||||
for (;;) {
|
||||
puts("ftp> ");
|
||||
int len = readline(cmd, sizeof(cmd));
|
||||
if (len == 0) continue;
|
||||
|
||||
/* Check for local quit command */
|
||||
if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "exit") == 0 ||
|
||||
strcmp(cmd, "bye") == 0) {
|
||||
send_cmd(sockfd, "QUIT");
|
||||
recv_response(sockfd);
|
||||
puts("Goodbye.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check for help */
|
||||
if (strcmp(cmd, "help") == 0 || strcmp(cmd, "?") == 0) {
|
||||
puts("ClaudeOS FTP Client\n");
|
||||
puts("Send raw FTP commands:\n");
|
||||
puts(" USER <username> - specify username\n");
|
||||
puts(" PASS <password> - specify password\n");
|
||||
puts(" PWD - print working directory\n");
|
||||
puts(" CWD <path> - change directory\n");
|
||||
puts(" LIST - list files (control channel only)\n");
|
||||
puts(" SYST - show system type\n");
|
||||
puts(" STAT - show server status\n");
|
||||
puts(" QUIT - disconnect\n");
|
||||
puts(" quit/exit/bye - disconnect and exit\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Send the command to server */
|
||||
if (send_cmd(sockfd, cmd) < 0) {
|
||||
puts("ftp: send failed\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Receive response */
|
||||
code = recv_response(sockfd);
|
||||
if (code < 0) {
|
||||
puts("ftp: connection lost\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user