mirror of
https://github.com/Motion-Project/motion.git
synced 2025-12-23 23:18:21 -05:00
886 lines
22 KiB
C
886 lines
22 KiB
C
/*
|
|
* Much of the FTP code was inspired by the nanoftp.c module from
|
|
* libxml2 (Copyright Daniel Veillard, 2003). The routines have been
|
|
* modified to fit the needs of the Motion project.
|
|
*
|
|
* Copyright 2005, William M. Brack
|
|
* This software is distributed under the GNU Public license Version 2.
|
|
* See also the file 'COPYING'.
|
|
*
|
|
*/
|
|
#include "motion.h" /* Needs to come first, because _GNU_SOURCE_ set there. */
|
|
|
|
#include <ctype.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include "netcam_ftp.h"
|
|
|
|
/**
|
|
* ftp_new_context
|
|
*
|
|
* Create a new FTP context structure.
|
|
*
|
|
* Parameters
|
|
*
|
|
* None
|
|
*
|
|
* Returns: Pointer to the newly-created structure, NULL if error.
|
|
*
|
|
*/
|
|
ftp_context_pointer ftp_new_context(void)
|
|
{
|
|
ftp_context_pointer ret;
|
|
|
|
/* Note that mymalloc will exit on any problem. */
|
|
ret = mymalloc(sizeof(ftp_context));
|
|
|
|
memset(ret, 0, sizeof(ftp_context));
|
|
ret->control_file_desc = -1; /* No control connection yet. */
|
|
ret->data_file_desc = -1; /* No data connection yet. */
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ftp_free_context
|
|
*
|
|
* Free the resources allocated for this context.
|
|
*
|
|
* Parameters
|
|
*
|
|
* ctxt Pointer to the ftp_context structure.
|
|
*
|
|
* Returns: Nothing
|
|
*
|
|
*/
|
|
void ftp_free_context(ftp_context_pointer ctxt)
|
|
{
|
|
if (ctxt == NULL)
|
|
return;
|
|
|
|
free(ctxt->path);
|
|
free(ctxt->user);
|
|
free(ctxt->passwd);
|
|
if (ctxt->control_file_desc >= 0)
|
|
close(ctxt->control_file_desc);
|
|
|
|
free(ctxt);
|
|
}
|
|
|
|
/**
|
|
* ftp_parse_response
|
|
*
|
|
* Parses the answer from the server, extracting the numeric code.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* buf the buffer containing the response.
|
|
* len the buffer length.
|
|
*
|
|
* Returns:
|
|
* 0 for errors
|
|
* +XXX for last line of response
|
|
* -XXX for response to be continued
|
|
*/
|
|
static int ftp_parse_response(char *buf, int len)
|
|
{
|
|
int val = 0;
|
|
|
|
if (len < 3)
|
|
return -1;
|
|
|
|
if ((*buf >= '0') && (*buf <= '9'))
|
|
val = val * 10 + (*buf - '0');
|
|
else
|
|
return 0;
|
|
|
|
buf++;
|
|
|
|
if ((*buf >= '0') && (*buf <= '9'))
|
|
val = val * 10 + (*buf - '0');
|
|
else
|
|
return 0;
|
|
|
|
buf++;
|
|
|
|
if ((*buf >= '0') && (*buf <= '9'))
|
|
val = val * 10 + (*buf - '0');
|
|
else
|
|
return 0;
|
|
|
|
buf++;
|
|
|
|
if (*buf == '-')
|
|
return -val;
|
|
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* ftp_get_more
|
|
*
|
|
* Read more information from the FTP control connection.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* ctxt pointer to an FTP context.
|
|
*
|
|
* Returns the number of bytes read, < 0 indicates an error
|
|
*/
|
|
static int ftp_get_more(ftp_context_pointer ctxt)
|
|
{
|
|
int len;
|
|
int size;
|
|
|
|
/* Validate that our context structure is valid. */
|
|
if ((ctxt == NULL) || (ctxt->control_file_desc < 0))
|
|
return -1;
|
|
|
|
if ((ctxt->control_buffer_index < 0) || (ctxt->control_buffer_index > FTP_BUF_SIZE))
|
|
return -1;
|
|
|
|
if ((ctxt->control_buffer_used < 0) || (ctxt->control_buffer_used > FTP_BUF_SIZE))
|
|
return -1;
|
|
|
|
if (ctxt->control_buffer_index > ctxt->control_buffer_used)
|
|
return -1;
|
|
|
|
/* First pack the control buffer. */
|
|
if (ctxt->control_buffer_index > 0) {
|
|
memmove(&ctxt->control_buffer[0],
|
|
&ctxt->control_buffer[ctxt->control_buffer_index],
|
|
ctxt->control_buffer_used - ctxt->control_buffer_index);
|
|
ctxt->control_buffer_used -= ctxt->control_buffer_index;
|
|
ctxt->control_buffer_index = 0;
|
|
}
|
|
|
|
size = FTP_BUF_SIZE - ctxt->control_buffer_used;
|
|
|
|
if (size == 0)
|
|
return 0;
|
|
|
|
/* Read the amount left on the control connection. */
|
|
if ((len = recv(ctxt->control_file_desc,
|
|
&ctxt->control_buffer[ctxt->control_buffer_index], size, 0)) < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: recv failed in ftp_get_more");
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
ctxt->control_buffer_used += len;
|
|
ctxt->control_buffer[ctxt->control_buffer_used] = 0;
|
|
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* ftp_get_response
|
|
*
|
|
* Read the response from the FTP server after a command.
|
|
*
|
|
* Parameters
|
|
*
|
|
* ctxt pointer to an FTP context
|
|
*
|
|
* Returns the code number
|
|
*/
|
|
static int ftp_get_response(ftp_context_pointer ctxt)
|
|
{
|
|
char *ptr, *end;
|
|
int len;
|
|
int res = -1, cur = -1;
|
|
|
|
if ((ctxt == NULL) || (ctxt->control_file_desc < 0))
|
|
return -1;
|
|
|
|
get_more:
|
|
/*
|
|
* Assumes everything up to control_buffer[control_buffer_index]
|
|
* has been read and analyzed.
|
|
*/
|
|
len = ftp_get_more(ctxt);
|
|
|
|
if (len < 0)
|
|
return -1;
|
|
|
|
|
|
if ((ctxt->control_buffer_used == 0) && (len == 0))
|
|
return -1;
|
|
|
|
|
|
ptr = &ctxt->control_buffer[ctxt->control_buffer_index];
|
|
end = &ctxt->control_buffer[ctxt->control_buffer_used];
|
|
|
|
while (ptr < end) {
|
|
cur = ftp_parse_response(ptr, end - ptr);
|
|
if (cur > 0) {
|
|
/*
|
|
* Successfully scanned the control code, skip
|
|
* till the end of the line, but keep the index to be
|
|
* able to analyze the result if needed.
|
|
*/
|
|
res = cur;
|
|
ptr += 3;
|
|
ctxt->control_buffer_answer = ptr - ctxt->control_buffer;
|
|
while ((ptr < end) && (*ptr != '\n'))
|
|
ptr++;
|
|
|
|
if (*ptr == '\n')
|
|
ptr++;
|
|
|
|
if (*ptr == '\r')
|
|
ptr++;
|
|
|
|
break;
|
|
}
|
|
|
|
while ((ptr < end) && (*ptr != '\n'))
|
|
ptr++;
|
|
|
|
if (ptr >= end) {
|
|
ctxt->control_buffer_index = ctxt->control_buffer_used;
|
|
goto get_more;
|
|
}
|
|
|
|
if (*ptr != '\r')
|
|
ptr++;
|
|
}
|
|
|
|
if (res < 0)
|
|
goto get_more;
|
|
|
|
ctxt->control_buffer_index = ptr - ctxt->control_buffer;
|
|
|
|
MOTION_LOG(DBG, TYPE_NETCAM, NO_ERRNO, "%s: Server Response: %s",ctxt->control_buffer);
|
|
|
|
return (res / 100);
|
|
}
|
|
|
|
/**
|
|
* ftp_send_user
|
|
* Sends the user authentication.
|
|
*/
|
|
static int ftp_send_user(ftp_context_pointer ctxt)
|
|
{
|
|
char buf[200];
|
|
int len;
|
|
int res;
|
|
|
|
if (ctxt->user == NULL)
|
|
snprintf(buf, sizeof(buf), "USER anonymous\r\n");
|
|
else
|
|
snprintf(buf, sizeof(buf), "USER %s\r\n", ctxt->user);
|
|
|
|
buf[sizeof(buf) - 1] = 0;
|
|
len = strlen(buf);
|
|
res = send(ctxt->control_file_desc, buf, len, 0);
|
|
|
|
if (res < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: send failed in ftp_send_user");
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ftp_send_passwd
|
|
* Sends the password authentication.
|
|
*/
|
|
static int ftp_send_passwd(ftp_context_pointer ctxt)
|
|
{
|
|
char buf[200];
|
|
int len;
|
|
int res;
|
|
|
|
if (ctxt->passwd == NULL)
|
|
snprintf(buf, sizeof(buf), "PASS anonymous@\r\n");
|
|
else
|
|
snprintf(buf, sizeof(buf), "PASS %s\r\n", ctxt->passwd);
|
|
|
|
buf[sizeof(buf) - 1] = 0;
|
|
len = strlen(buf);
|
|
res = send(ctxt->control_file_desc, buf, len, 0);
|
|
|
|
if (res < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: send failed in ftp_send_passwd");
|
|
return res;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ftp_quit
|
|
*
|
|
* Send a QUIT command to the server
|
|
*
|
|
* Parameters:
|
|
*
|
|
* ctxt pointer to an FTP context
|
|
*
|
|
* Returns -1 in case of error, 0 otherwise
|
|
*/
|
|
static int ftp_quit(ftp_context_pointer ctxt)
|
|
{
|
|
char buf[200];
|
|
int len, res;
|
|
|
|
if ((ctxt == NULL) || (ctxt->control_file_desc < 0))
|
|
return -1;
|
|
|
|
snprintf(buf, sizeof(buf), "QUIT\r\n");
|
|
len = strlen(buf);
|
|
res = send(ctxt->control_file_desc, buf, len, 0);
|
|
|
|
if (res < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: send failed in ftp_quit");
|
|
return res;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ftp_connect
|
|
*
|
|
* Tries to open a control connection.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* ctxt an FTP context
|
|
*
|
|
* Returns -1 in case of error, 0 otherwise.
|
|
*/
|
|
int ftp_connect(netcam_context_ptr netcam)
|
|
{
|
|
ftp_context_pointer ctxt;
|
|
struct hostent *hp;
|
|
int port;
|
|
int res;
|
|
int addrlen = sizeof (struct sockaddr_in);
|
|
|
|
if (netcam == NULL)
|
|
return -1;
|
|
|
|
ctxt = netcam->ftp;
|
|
|
|
if (ctxt == NULL)
|
|
return -1;
|
|
|
|
if (netcam->connect_host == NULL)
|
|
return -1;
|
|
|
|
/* Do the blocking DNS query. */
|
|
port = netcam->connect_port;
|
|
|
|
if (port == 0)
|
|
port = 21;
|
|
|
|
memset (&ctxt->ftp_address, 0, sizeof(ctxt->ftp_address));
|
|
|
|
hp = gethostbyname (netcam->connect_host);
|
|
|
|
if (hp == NULL) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: gethostbyname failed in ftp_connect");
|
|
return -1;
|
|
}
|
|
|
|
if ((unsigned int) hp->h_length >
|
|
sizeof(((struct sockaddr_in *)&ctxt->ftp_address)->sin_addr)) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: gethostbyname address mismatch "
|
|
"in ftp_connect");
|
|
return -1;
|
|
}
|
|
|
|
/* Prepare the socket */
|
|
((struct sockaddr_in *)&ctxt->ftp_address)->sin_family = AF_INET;
|
|
memcpy (&((struct sockaddr_in *)&ctxt->ftp_address)->sin_addr, hp->h_addr_list[0], hp->h_length);
|
|
((struct sockaddr_in *)&ctxt->ftp_address)->sin_port = (u_short)htons ((unsigned short)port);
|
|
ctxt->control_file_desc = socket (AF_INET, SOCK_STREAM, 0);
|
|
addrlen = sizeof (struct sockaddr_in);
|
|
|
|
if (ctxt->control_file_desc < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: socket failed");
|
|
return -1;
|
|
}
|
|
|
|
/* Do the connect. */
|
|
if (connect(ctxt->control_file_desc, (struct sockaddr *) &ctxt->ftp_address,
|
|
addrlen) < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: Failed to create a connection");
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
/* Wait for the HELLO from the server. */
|
|
res = ftp_get_response(ctxt);
|
|
|
|
if (res != 2) {
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
/* Do the authentication */
|
|
res = ftp_send_user(ctxt);
|
|
|
|
if (res < 0) {
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
res = ftp_get_response(ctxt);
|
|
|
|
switch (res) {
|
|
case 2:
|
|
return 0;
|
|
case 3:
|
|
break;
|
|
case 1:
|
|
case 4:
|
|
case 5:
|
|
case -1:
|
|
default:
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
res = ftp_send_passwd(ctxt);
|
|
|
|
if (res < 0) {
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
res = ftp_get_response(ctxt);
|
|
switch (res) {
|
|
case 2:
|
|
break;
|
|
case 3:
|
|
MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO, "%s: FTP server asking for ACCT on anonymous");
|
|
case 1:
|
|
case 4:
|
|
case 5:
|
|
case -1:
|
|
default:
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
ctxt->control_file_desc = -1;
|
|
return-1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ftp_get_connection
|
|
*
|
|
* Try to open a data connection to the server.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* ctxt pointer to an FTP context.
|
|
*
|
|
* Returns -1 in case of error, 0 otherwise
|
|
*/
|
|
static int ftp_get_connection(ftp_context_pointer ctxt)
|
|
{
|
|
char buf[200], *cur;
|
|
int len, i;
|
|
int res;
|
|
int on;
|
|
unsigned char ad[6], *adp, *portp;
|
|
unsigned int temp[6];
|
|
struct sockaddr_in data_address;
|
|
unsigned int data_address_length;
|
|
|
|
if (ctxt == NULL)
|
|
return -1;
|
|
|
|
/* Set up a socket for our data address. */
|
|
if (ctxt->data_file_desc != -1)
|
|
close(ctxt->data_file_desc);
|
|
|
|
memset (&data_address, 0, sizeof(data_address));
|
|
ctxt->data_file_desc = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
|
|
if (ctxt->data_file_desc < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: socket failed");
|
|
return -1;
|
|
}
|
|
|
|
on = 1;
|
|
|
|
if (setsockopt(ctxt->data_file_desc, SOL_SOCKET, SO_REUSEADDR,
|
|
(char *)&on, sizeof(on)) < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: setting socket option SO_REUSEADDR");
|
|
return -1;
|
|
}
|
|
|
|
((struct sockaddr_in *)&data_address)->sin_family = AF_INET;
|
|
data_address_length = sizeof (struct sockaddr_in);
|
|
|
|
if (ctxt->passive) {
|
|
/* Send PASV command over control channel. */
|
|
snprintf (buf, sizeof(buf), "PASV\r\n");
|
|
len = strlen (buf);
|
|
res = send(ctxt->control_file_desc, buf, len, 0);
|
|
|
|
if (res < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: send failed in ftp_get_connection");
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return res;
|
|
}
|
|
/* Check server's answer */
|
|
res = ftp_get_response(ctxt);
|
|
|
|
if (res != 2) {
|
|
if (res == 5) {
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return -1;
|
|
} else {
|
|
/* Retry with an active connection. */
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
ctxt->passive = 0;
|
|
}
|
|
}
|
|
/* Parse the IP address and port supplied by the server. */
|
|
cur = &ctxt->control_buffer[ctxt->control_buffer_answer];
|
|
|
|
while (((*cur < '0') || (*cur > '9')) && *cur != '\0')
|
|
cur++;
|
|
|
|
if (sscanf(cur, "%u,%u,%u,%u,%u,%u", &temp[0], &temp[1], &temp[2],
|
|
&temp[3], &temp[4], &temp[5]) != 6) {
|
|
MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO, "%s: Invalid answer to PASV");
|
|
if (ctxt->data_file_desc != -1) {
|
|
close (ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < 6; i++)
|
|
ad[i] = (unsigned char) (temp[i] & 0xff) ;
|
|
|
|
memcpy (&((struct sockaddr_in *)&data_address)->sin_addr, &ad[0], 4);
|
|
memcpy (&((struct sockaddr_in *)&data_address)->sin_port, &ad[4], 2);
|
|
|
|
/* Now try to connect to the data port. */
|
|
if (connect(ctxt->data_file_desc, (struct sockaddr *) &data_address,
|
|
data_address_length) < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: Failed to create a data connection");
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return -1;
|
|
}
|
|
} else {
|
|
/*
|
|
* We want to bind to a port to receive the data. To do this,
|
|
* we need the address of our host. One easy way to get it is
|
|
* to get the info from the control connection that we have
|
|
* with the remote server.
|
|
*/
|
|
getsockname(ctxt->control_file_desc, (struct sockaddr *)&data_address,
|
|
&data_address_length);
|
|
((struct sockaddr_in *)&data_address)->sin_port = 0;
|
|
|
|
/* Bind to the socket - should give us a unique port. */
|
|
if (bind(ctxt->data_file_desc, (struct sockaddr *) &data_address,
|
|
data_address_length) < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: bind failed");
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
/* We get the port number by reading back in the sockaddr. */
|
|
getsockname(ctxt->data_file_desc, (struct sockaddr *)&data_address,
|
|
&data_address_length);
|
|
|
|
/* Set up a 'listen' on the port to get the server's connection. */
|
|
if (listen(ctxt->data_file_desc, 1) < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: listen failed");
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
/* Now generate the PORT command. */
|
|
adp = (unsigned char *) &((struct sockaddr_in *)&data_address)->sin_addr;
|
|
portp = (unsigned char *) &((struct sockaddr_in *)&data_address)->sin_port;
|
|
snprintf(buf, sizeof(buf), "PORT %d,%d,%d,%d,%d,%d\r\n",
|
|
adp[0] & 0xff, adp[1] & 0xff, adp[2] & 0xff, adp[3] & 0xff,
|
|
portp[0] & 0xff, portp[1] & 0xff);
|
|
|
|
buf[sizeof(buf) - 1] = 0;
|
|
len = strlen(buf);
|
|
|
|
/* Send the PORT command to the server. */
|
|
res = send(ctxt->control_file_desc, buf, len, 0);
|
|
|
|
if (res < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: send failed in ftp_get_connection");
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return res;
|
|
}
|
|
|
|
res = ftp_get_response(ctxt);
|
|
|
|
if (res != 2) {
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return ctxt->data_file_desc;
|
|
}
|
|
|
|
/**
|
|
* ftp_close_connection
|
|
*
|
|
* Close the data connection from the server.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* ctxt Pointer to an FTP context.
|
|
*
|
|
* Returns -1 in case of error, 0 otherwise
|
|
*/
|
|
static int ftp_close_connection(ftp_context_pointer ctxt)
|
|
{
|
|
int res;
|
|
fd_set rfd, efd;
|
|
struct timeval tv;
|
|
|
|
if ((ctxt == NULL) || (ctxt->control_file_desc < 0))
|
|
return -1;
|
|
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
|
|
/* Check for data on the control channel. */
|
|
tv.tv_sec = 15;
|
|
tv.tv_usec = 0;
|
|
FD_ZERO(&rfd);
|
|
FD_SET(ctxt->control_file_desc, &rfd);
|
|
FD_ZERO(&efd);
|
|
FD_SET(ctxt->control_file_desc, &efd);
|
|
res = select(ctxt->control_file_desc + 1, &rfd, NULL, &efd, &tv);
|
|
|
|
if (res < 0) {
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
if (res == 0) { /* Timeout */
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
} else { /* Read the response */
|
|
res = ftp_get_response(ctxt);
|
|
|
|
if (res != 2) { /* Should be positive completion (2) */
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ftp_get_socket
|
|
*
|
|
* Initiate fetch of the given file from the server.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* ctxt an FTP context
|
|
*
|
|
* Returns the socket for the data connection, or <0 in case of error
|
|
*/
|
|
int ftp_get_socket(ftp_context_pointer ctxt)
|
|
{
|
|
char buf[300];
|
|
int res, len;
|
|
int acfd;
|
|
|
|
if ((ctxt == NULL) || (ctxt->path == NULL))
|
|
return -1;
|
|
|
|
/* Set up the data connection. */
|
|
ctxt->data_file_desc = ftp_get_connection(ctxt);
|
|
|
|
if (ctxt->data_file_desc == -1)
|
|
return -1;
|
|
|
|
/* Generate a "retrieve" command for the file. */
|
|
snprintf(buf, sizeof(buf), "RETR %s\r\n", ctxt->path);
|
|
buf[sizeof(buf) - 1] = 0;
|
|
len = strlen(buf);
|
|
|
|
/* Send it to the server. */
|
|
res = send(ctxt->control_file_desc, buf, len, 0);
|
|
|
|
if (res < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: send failed in ftp_get_socket");
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return res;
|
|
}
|
|
|
|
/* Check the answer */
|
|
res = ftp_get_response(ctxt);
|
|
|
|
if (res != 1) {
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return -res;
|
|
}
|
|
|
|
/*
|
|
* If not a passive connection, need to do an accept to get the
|
|
* connection from the server.
|
|
*/
|
|
if (!ctxt->passive) {
|
|
struct sockaddr_in data_address;
|
|
unsigned int data_address_length = sizeof(struct sockaddr_in);
|
|
|
|
if ((acfd = accept(ctxt->data_file_desc, (struct sockaddr *)&data_address,
|
|
&data_address_length)) < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: accept in ftp_get_socket");
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return -1;
|
|
}
|
|
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = acfd;
|
|
}
|
|
|
|
return ctxt->data_file_desc;
|
|
}
|
|
/**
|
|
* ftp_send_type
|
|
*
|
|
* Send a TYPE (either 'I' or 'A') command to the server.
|
|
*
|
|
* Parameters
|
|
*
|
|
* ctxt pointer to the ftp_context
|
|
* type ascii character ('I' or 'A')
|
|
*
|
|
* Returns 0 for success, negative error code for failure.
|
|
*
|
|
*/
|
|
int ftp_send_type(ftp_context_pointer ctxt, char type)
|
|
{
|
|
char buf[100], utype;
|
|
int len, res;
|
|
|
|
utype = toupper(type);
|
|
snprintf(buf, sizeof(buf), "TYPE %c\r\n", utype);
|
|
len = strlen(buf);
|
|
res = send(ctxt->control_file_desc, buf, len, 0);
|
|
|
|
if (res < 0) {
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: send failed in ftp_get_socket");
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return res;
|
|
}
|
|
|
|
res = ftp_get_response(ctxt);
|
|
|
|
if (res != 2) {
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
return -res;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ftp_read
|
|
*
|
|
* This function tries to read len bytes from the existing FTP
|
|
* connection and saves them in dest. This is a blocking call.
|
|
*
|
|
* Parameters:
|
|
* ctxt the FTP context
|
|
* dest a buffer
|
|
* len the buffer length
|
|
*
|
|
* Returns: the number of bytes read.
|
|
* 0 is an indication of an end of connection.
|
|
* -1 indicates a parameter error.
|
|
*/
|
|
int ftp_read(ftp_context_pointer ctxt, void *dest, int len)
|
|
{
|
|
if (ctxt == NULL)
|
|
return -1;
|
|
|
|
if (ctxt->data_file_desc < 0)
|
|
return 0;
|
|
|
|
if (dest == NULL)
|
|
return -1;
|
|
|
|
if (len <= 0)
|
|
return 0;
|
|
|
|
len = recv(ctxt->data_file_desc, dest, len, 0);
|
|
|
|
if (len <= 0) {
|
|
if (len < 0)
|
|
MOTION_LOG(ERR, TYPE_NETCAM, SHOW_ERRNO, "%s: recv failed in ftp_read");
|
|
ftp_close_connection(ctxt);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
/**
|
|
* ftp_close
|
|
*
|
|
* Close the connection and both control and transport.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* ctxt Pointer to an FTP context.
|
|
*
|
|
* Returns -1 in case of error, 0 otherwise.
|
|
*/
|
|
int ftp_close(ftp_context_pointer ctxt)
|
|
{
|
|
if (ctxt == NULL)
|
|
return -1;
|
|
|
|
if (ctxt->data_file_desc >= 0) {
|
|
close(ctxt->data_file_desc);
|
|
ctxt->data_file_desc = -1;
|
|
}
|
|
|
|
if (ctxt->control_file_desc >= 0) {
|
|
ftp_quit(ctxt);
|
|
close(ctxt->control_file_desc);
|
|
ctxt->control_file_desc = -1;
|
|
}
|
|
|
|
ftp_free_context(ctxt);
|
|
return 0;
|
|
}
|