/* FTP Functions
$Revision: 1.5 $
$Date: 2003/03/10 22:31:26 $
vim: sw=4 ts=4
*/
#define _XOPEN_SOURCE 500
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <arpa/inet.h>
#include <fnmatch.h>
#include <string.h>
#include "ftp.h"
#include "quftp.h"
#include "text.h"
/*
* Global Variables
*/
/* STAT or LIST Formats
* %P UNIX style permissions
* %L Number of hard links
* %O Owner
* %G Group
* %S Size
* %N File Name
*
* %Y Year
* %M Month name
* %n Month number
* %D Day of month
* %h Hour
* %m Minute
* %s Second
*/
char *stat_formats[] = {
/* drwxr-xr-x 2 ftp ftp 1024 Jan 23 21:37 incoming */
"%P %L %O %G %S %M %D %h:%m %N",
/* d--x--x--x 2 ftp ftp 1024 Oct 17 1998 bin */
"%P %L %O %G %S %M %D %Y %N"
};
struct ftpconnection *ftp_new(void) {
struct ftpconnection *connection;
connection = malloc(sizeof(struct ftpconnection));
log(LOG_CALLS, "Creating new ftp structure at %p", connection);
memset(connection->hostname, 0, 64);
memset(connection->system, 0, 128);
memset(connection->username, 0, 32);
memset(connection->password, 0, 32);
memset(connection->remotedir, 0, 256);
memset(connection->localdir, 0, 256);
connection->port = 0;
connection->status = STATUS_NEW;
connection->controlfd = 0;
connection->datafd = 0;
connection->dataconfd = 0;
return connection;
}
int ftp_connect(struct ftpconnection *connection) {
struct hostent *hp;
int response, len = 128;
int readynow = 0;
char *ipaddr;
char resultstring[4096];
struct sockaddr_in controladdr;
log(LOG_CALLS, "%p", connection);
if (connection->status < STATUS_WAIT_CONNECT) {
return -1;
}
if (!connection->hostname || !*connection->hostname) {
log(LOG_CRITICAL, "Must initialize struct ftpconnection first\n");
return -1;
}
if (!(hp = (struct hostent *)gethostbyname(connection->hostname))) {
log(LOG_ALERT, "Can't resolve %s: %s\n", connection->hostname, HERROR);
return -1;
}
if ((connection->controlfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
exit(0);
}
log(LOG_MESSAGE, "Resolving %s", connection->hostname);
fflush(stdout);
memset(&controladdr, 0, sizeof(controladdr));
controladdr.sin_family = AF_INET;
controladdr.sin_port = htons(connection->port);
memcpy((char *)&controladdr.sin_addr, hp->h_addr, hp->h_length);
ipaddr = inet_ntoa(controladdr.sin_addr);
log(LOG_MESSAGE, "%s resolves to %s", connection->hostname, ipaddr);
if (connect(connection->controlfd, (SOCKADDR *)&controladdr, sizeof(controladdr)) < 0) {
log(LOG_ALERT, "Connection refused to %s\n", connection->hostname);
return -1;
}
if (getsockname(connection->controlfd, (SOCKADDR *)&controladdr,&len)< 0) {
perror("getsockname");
exit(0);
}
connection->localip = controladdr.sin_addr.s_addr;
connection->status = STATUS_WAIT_LOGIN;
while (!readynow) {
response = ftp_getrc(connection, resultstring, 4096, 0);
switch (response) {
case 120 : log(LOG_ALERT, "Service not yet ready.. waiting for 15 seconds");
sleep(15);
break;
case 220 : readynow = 1;
log(LOG_NOTICE, "Connected to %s on port %d",
connection->hostname, connection->port);
break;
default : log(LOG_ALERT, "%s", ftp_error_message(response));
return response;
break;
}
}
return 0;
}
int ftp_disconnect(struct ftpconnection *connection) {
if (connection->status >= STATUS_IDLE) {
int response;
response = ftp_sendline(connection, "QUIT\r\n");
response = ftp_getrc(connection, NULL, 0, 0);
if (response == 500)
log(LOG_MESSAGE, "Braindead server doesn't understand QUIT.. closing connection\n");
if (close(connection->controlfd) < 0) {
perror("close");
exit(0);
}
}
if (connection->hostname)
log(LOG_NOTICE, "Disconnected from %s", connection->hostname);
connection->controlfd = 0;
return 0;
}
int ftp_reset(struct ftpconnection *connection) {
if (connection->status >= STATUS_WAIT_LOGIN) {
log(LOG_WARNING, "Still connected. Call ftp_disconnect() first!\n");
return 1;
}
memset(connection, 0, sizeof(struct ftpconnection));
connection->status = STATUS_NEW;
return 0;
}
char *ftp_readline(struct ftpconnection *connection) {
int n = 0;
int length = 0;
char *temp, *buffer;
static char *read_line = 0;
if (read_line != 0) free(read_line);
read_line = (char *)NULL;
buffer = (char *)malloc(MAX_LINE_SIZE);
if (connection->controlfd <= 0) {
log(LOG_CRITICAL, "ftp_readline: Not connected\n");
return 0;
}
temp = buffer;
while ((n = read(connection->controlfd, temp, 1)) > 0) {
if (*temp == '\r') continue;
if (*temp == '\n') break;
if (*temp == '\0') break;
if (length == MAX_LINE_SIZE) break;
temp++; length++;
}
if (n < 0) {
perror("read");
exit(0);
}
if (n == 0)
{
log(LOG_WARNING, "FTP server disconnected");
return NULL;
}
read_line = (char *)malloc(length + 1);
strncpy(read_line, buffer, length);
read_line[length] = '\0';
log_hex(LOG_DATA, "Read", read_line, length);
return read_line;
}
int ftp_sendline(struct ftpconnection *connection, char *line) {
int n = 0;
int size;
if (!line || !*line) return 0;
size = strlen(line);
if (size == 0) return 0;
if (connection->status <= STATUS_WAIT_CONNECT) {
log(LOG_CRITICAL, "ftp_sendline: Not connected\n");
return 0;
}
log_hex(LOG_DATA, "Sent", line, size);
if ((n = write(connection->controlfd, line, size)) < 0) {
perror("ftp_sendline: write");
}
return n;
}
int ftp_getrc(struct ftpconnection *connection, char *data, int max_size, int print_line) {
char *line;
int response;
if (connection->status <= STATUS_WAIT_CONNECT) {
log(LOG_CRITICAL, "ftp_getrc: Not connected");
return 0;
}
while ((line = ftp_readline(connection))) {
if (!*line) {
log(LOG_CRITICAL, "Big bad error.. empty string returned by ftp_readline\n");
break;
}
if (print_line)
{
if (line[0] == ' ')
log(LOG_WARNING, "%s", (char *)(line + 1));
else
log(LOG_WARNING, "%s", (char *)(line + 4));
}
if (IS_NUMBER(line[0]) && IS_NUMBER(line[1]) && IS_NUMBER(line[2]) && line[3] == ' ')
break;
}
if (!line || !*line) return 0;
sscanf(line, "%d ", &response);
log(LOG_INFO, "ftp_getrc() = %d", response);
if (data)
strncpy(data, (line + 4), max_size);
if (response >= 100)
return response;
return 0;
}
int ftp_parse_pasv(char *line, char *address) {
int h1, h2, h3, h4, p1, p2, port;
if (line == NULL) return 0;
sscanf(line, "%i,%i,%i,%i,%i,%i", &h1, &h2, &h3, &h4, &p1, &p2);
port = (p1 << 8) + p2;
sprintf(address, "%d.%d.%d.%d", h1, h2, h3, h4);
return port;
}
int ftp_parse_permissions(char *perm) {
int permissions = 0;
if (perm[1] != 'r' && perm[1] != '-') return -1;
if (perm[0] == 'd') permissions |= 2048;
if (perm[1] == 'r') permissions |= 256;
if (perm[2] == 'w') permissions |= 128;
if (perm[3] == 'x') permissions |= 64;
if (perm[4] == 'r') permissions |= 32;
if (perm[5] == 'w') permissions |= 16;
if (perm[6] == 'x') permissions |= 8;
if (perm[7] == 'r') permissions |= 4;
if (perm[8] == 'w') permissions |= 2;
if (perm[9] == 'x') permissions |= 1;
return permissions;
}
int ftp_check_ready(struct ftpconnection *connection, int print) {
if (connection->status <= STATUS_WAIT_CONNECT) {
if (print) log(LOG_CRITICAL, "Not connected\n");
return 0;
}
if (connection->status == STATUS_WAIT_LOGIN) {
if (print) log(LOG_ALERT, "Not logged in\n");
return 0;
}
if (connection->status == STATUS_ERROR) {
if (print) log(LOG_ALERT, "Connection is in error state!\n");
return 0;
}
if (connection->status >= STATUS_WAITING) {
if (print) log(LOG_ALERT, "Server is busy. Please wait\n");
return 0;
}
return 1;
}
int ftp_stat(struct ftpconnection *connection, char *filename, struct filedetails *details) {
char *buffer = NULL;
int result = 0;
if (!details) return -1;
log(LOG_CALLS, "\"%s\"", filename);
if (!ftp_check_ready(connection, 1)) return -1;
if (details) memset(details, 0, sizeof(struct filedetails));
buffer = (char *)malloc(strlen(filename) + 15);
sprintf(buffer, "STAT %s\r\n", filename);
ftp_sendline(connection, buffer);
free(buffer);
buffer = ftp_readline(connection);
result = atoi(buffer);
switch (result) {
case 211 :
case 212 :
case 213 :
case 250 : break;
case 500 : log(LOG_ALERT, "Server doesn't understand STAT\n"); return result; break;
case 501 : log(LOG_ALERT, "Bad parameters to STAT\n"); return result; break;
case 502 : log(LOG_ALERT, "Server doesn't understand STAT\n"); return result; break;
case 421 : log(LOG_ALERT, "Service not available\n"); return result; break;
default : log(LOG_ALERT, "Unknown response to STAT: %d\n", result); return result; break;
}
if (buffer[3] == '-') {
/* Read a line at a time */
while ((buffer = ftp_readline(connection))) {
if (strstr(buffer, "total ") == buffer) continue;
break;
}
if (buffer[0] == '2' && buffer[1] == '1') return 1;
if (!buffer) {
log(LOG_CRITICAL, "Disconnected!\n");
ftp_disconnect(connection);
return 1;
}
if (strstr(buffer, "/bin/ls") != 0) {
ftp_readline(connection);
return 10;
}
if (ftp_split_ls(buffer, details) == 1) {;
ftp_strip_illegal(details->filename);
ftp_getrc(connection, NULL, 0, 0);
return 0;
} else {
ftp_getrc(connection, NULL, 0, 0);
return 1;
}
} else {
log(LOG_ALERT, "Bad response to STAT: %s\n", buffer);
}
return 0;
}
int ftp_split_ls(char *buffer, struct filedetails *details) {
int stat_index, complete_match = 0;
if (!buffer || !*buffer) return 0;
/* Try matching STAT output */
stat_index = 0;
while (stat_formats[stat_index]) {
char *format_p, *buffer_p;
complete_match = 1;
buffer_p = buffer;
format_p = stat_formats[stat_index];
while (*format_p) {
while (*buffer_p == ' ' || *buffer_p == '\t')
buffer_p++; /* Skip whitespace */
if (*format_p == '%') {
switch (*++format_p) {
case '%' : if (*buffer_p == '%') { /* %% means a literal % character */
buffer_p++;
} else {
complete_match = 0;
}
break;
case 'P' : { char *permissions_p; /* UNIX style permissions */
char *permissions = (char *)malloc(4096);
memset(permissions, 0, 4096);
permissions_p = permissions;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*permissions_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
details->permissions = ftp_parse_permissions(permissions);
if (details->permissions == 0) complete_match = 0;
free(permissions);
break; }
case 'L' : { char *links_p; /* UNIX style links */
char *links = (char *)malloc(4096);
memset(links, 0, 4096);
links_p = links;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*links_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
for (links_p = links; *links_p; links_p++)
if (*links_p < '0' || *links_p > '9') complete_match = 0;
details->links = atoi(links);
free(links);
break; }
case 'O' : { char *owner_p; /* UNIX style owner */
char *owner = (char *)malloc(4096);
memset(owner, 0, 4096);
owner_p = owner;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*owner_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
strncpy(details->owner, owner, 63);
free(owner);
break; }
case 'G' : { char *group_p; /* UNIX style group */
char *group = (char *)malloc(4096);
memset(group, 0, 4096);
group_p = group;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*group_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
strncpy(details->group, group, 63);
free(group);
break; }
case 'S' : { char *size_p; /* UNIX style size */
char *size = (char *)malloc(4096);
memset(size, 0, 4096);
size_p = size;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*size_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
for (size_p = size; *size_p; size_p++)
if (*size_p < '0' || *size_p > '9') complete_match = 0;
details->size = atol(size);
free(size);
break; }
case 'N' : { char *filename_p; /* UNIX style filename */
char *filename = (char *)malloc(4096);
memset(filename, 0, 4096);
filename_p = filename;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*filename_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
strncpy(details->filename, filename, 254);
free(filename);
break; }
case 'Y' : { char *year_p; /* UNIX style year */
char *year = (char *)malloc(4096);
memset(year, 0, 4096);
year_p = year;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*year_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
for (year_p = year; *year_p; year_p++)
if (*year_p < '0' || *year_p > '9') complete_match = 0;
free(year);
break; }
case 'M' : { char *month_p; /* UNIX style month name */
char *month = (char *)malloc(4096);
memset(month, 0, 4096);
month_p = month;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*month_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
free(month);
break; }
case 'n' : { char *month_p; /* UNIX style month number */
char *month = (char *)malloc(4096);
memset(month, 0, 4096);
month_p = month;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*month_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
for (month_p = month; *month_p; month_p++)
if (*month_p < '0' || *month_p > '9') complete_match = 0;
free(month);
break; }
case 'm' : { char *minute_p; /* UNIX style minute number */
char *minute = (char *)malloc(4096);
memset(minute, 0, 4096);
minute_p = minute;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*minute_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
for (minute_p = minute; *minute_p; minute_p++)
if (*minute_p < '0' || *minute_p > '9') complete_match = 0;
free(minute);
break; }
case 'D' : { char *mday_p; /* UNIX style mday number */
char *mday = (char *)malloc(4096);
memset(mday, 0, 4096);
mday_p = mday;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*mday_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
for (mday_p = mday; *mday_p; mday_p++)
if (*mday_p < '0' || *mday_p > '9') complete_match = 0;
free(mday);
break; }
case 's' : { char *second_p; /* UNIX style second number */
char *second = (char *)malloc(4096);
memset(second, 0, 4096);
second_p = second;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*second_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
for (second_p = second; *second_p; second_p++)
if (*second_p < '0' || *second_p > '9') complete_match = 0;
free(second);
break; }
case 'h' : { char *hour_p; /* UNIX style hour number */
char *hour = (char *)malloc(4096);
memset(hour, 0, 4096);
hour_p = hour;
format_p++;
while (*buffer_p && (*buffer_p != *format_p))
*hour_p++ = *buffer_p++; /* Loop and grab everything up until the next character in the format */
for (hour_p = hour; *hour_p; hour_p++)
if (*hour_p < '0' || *hour_p > '9') complete_match = 0;
free(hour);
break; }
default : log(LOG_CRITICAL, "Unknown character %c in STAT format description\n", *format_p);
break;
}
}
if (complete_match == 0) break;
if (!*format_p++) break;
}
if (complete_match == 1) break;
stat_index++;
}
return complete_match;
}
int ftp_delete(struct ftpconnection *connection, char *filename) {
int result;
char line[4096];
char resultstring[4096];
if (!filename) return 0;
if (!ftp_check_ready(connection, 1)) return 0;
sprintf(line, "DELE %s\r\n", filename);
ftp_sendline(connection, line);
result = ftp_getrc(connection, resultstring, 4096, 0);
switch (result) {
case 250 : log(LOG_INFO, "Deleted %s\n", filename); return 1; break;
case 450 : log(LOG_ALERT, "Can't delete %s: %s\n", filename, resultstring); break;
case 421 : log(LOG_ALERT, "%s\n", resultstring); break;
case 500 : log(LOG_ALERT, "This server does not support the DELE command\n"); break;
case 501 : log(LOG_ALERT, "Invalid parameters to DELE command\n"); break;
case 502 : log(LOG_ALERT, "This server does not support the DELE command\n"); break;
case 530 : log(LOG_ALERT, "Not logged in correctly\n"); break;
case 550 : log(LOG_ALERT, "Can't delete %s: %s\n", filename, resultstring); break;
case 553 : log(LOG_ALERT, "Not allowed to delete %s\n", filename); break;
default : log(LOG_ALERT, "Unrecognised response to DELE: %d %s\n", result, resultstring);
break;
}
return 0;
}
char *ftp_glob(struct ftpconnection *connection, char *wildcard, int status) {
static int index = 0;
static char pathname[4096], working[4096];
int response;
static char *return_list[500];
char *retstring;
if (!ftp_check_ready(connection, 1)) return NULL;
if (status == 0) {
memset(pathname, 0, 4096);
memset(working, 0, 4096);
memset(return_list, 0, sizeof(*return_list) * 500);
// Separate the path and filename
index = strlen(wildcard);
while (index) {
if (wildcard[index] == '/') {
strncpy(pathname, wildcard, index + 1);
strncpy(working, &wildcard[index + 1], 4096);
break;
}
index--;
}
if (strlen(working) == 0) strncpy(working, wildcard, 4096);
response = ftp_nlst(connection, pathname, return_list, 500);
if (response <= 0) return NULL;
index = 0;
}
while (return_list[index] != NULL) {
int eflags = FNM_PATHNAME | FNM_PERIOD | (1 << 3);
if (fnmatch(wildcard, return_list[index], eflags) != 0) {
index++;
continue;
}
retstring = (char *)strdup(return_list[index]);
return_list[index] = 0;
index++;
return retstring;
}
return NULL;
}
int ftp_exists(struct ftpconnection *connection, char *filename) {
/* Check if a file exists on the server */
/* Return codes
< 0 Don't know
0 Doesn't exist
> 0 Does exist
*/
char line[4096];
int result;
if (!filename) return 0;
if (!ftp_check_ready(connection, 1)) return 0;
log(LOG_CALLS, "(%p, %s)", connection, filename);
sprintf(line, "STAT %s\r\n", filename);
ftp_sendline(connection, line);
result = ftp_getrc(connection, NULL, 0, 0);
switch (result) {
case 213 : return 1;
case 211 : return 1;
case 212 : return 1;
case 421 : log(LOG_ALERT, "STAT error.. %d\n", result); return -1; break;
case 450 : return 0;
case 500 : break;
case 501 : break;
case 502 : break;
case 530 : log(LOG_ALERT, "Not logged in correctly\n"); return -1; break;
default : break;
}
return -1;
}
char *ftp_error_message(int error) {
switch(error) {
case 110 : return "Restart marker reply"; break;
case 120 : return "Service ready in nnn minutes."; break;
case 125 : return "Data connection already open; transfer starting."; break;
case 150 : return "File status okay; about to open data connection."; break;
case 200 : return "Command okay."; break;
case 202 : return "Command not implemented, superfluous at this site."; break;
case 211 : return "System status, or system help reply."; break;
case 212 : return "Directory status."; break;
case 213 : return "File status."; break;
case 214 : return "Help message."; break;
case 215 : return "NAME system type."; break;
case 220 : return "Service ready for new user."; break;
case 221 : return "Service closing control connection."; break;
case 225 : return "Data connection open; no transfer in progress."; break;
case 226 : return "Closing data connection successfully."; break;
case 227 : return "Entering Passive Mode (h1,h2,h3,h4,p1,p2)."; break;
case 230 : return "User logged in, proceed."; break;
case 250 : return "Requested file action okay, completed."; break;
case 257 : return "PATHNAME created."; break;
case 331 : return "User name okay, need password."; break;
case 332 : return "Need account for login."; break;
case 350 : return "Requested file action pending further information."; break;
case 421 : return "Service not available, closing control connection."; break;
case 425 : return "Can't open data connection."; break;
case 426 : return "Connection closed; transfer aborted."; break;
case 450 : return "Requested file action not taken - File unavailable."; break;
case 451 : return "Requested action aborted: local error in processing."; break;
case 452 : return "Requested action not taken - Insufficient storage space in system."; break;
case 500 : return "Syntax error, command unrecognized."; break;
case 501 : return "Syntax error in parameters or arguments."; break;
case 502 : return "Command not implemented."; break;
case 503 : return "Bad sequence of commands."; break;
case 504 : return "Command not implemented for that parameter."; break;
case 530 : return "Not logged in."; break;
case 532 : return "Need account for storing files."; break;
case 550 : return "Requested action not taken - File unavailable."; break;
case 551 : return "Requested action aborted: page type unknown."; break;
case 552 : return "Requested file action aborted - Exceeded storage allocation"; break;
case 553 : return "Requested action not taken - File name not allowed."; break;
default : return "Unknown error";
}
}
syntax highlighted by Code2HTML, v. 0.9.1