File: ftp.c

package info (click to toggle)
screem 0.2.1-1
  • links: PTS
  • area: main
  • in suites: potato
  • size: 3,748 kB
  • ctags: 2,270
  • sloc: ansic: 26,366; sh: 7,453; makefile: 512; sed: 93; perl: 18
file content (844 lines) | stat: -rw-r--r-- 23,837 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
/* 
   sitecopy, for managing remote web sites. Generic(ish) FTP routines.
   Copyright (C) 1998-99, Joe Orton <joe@orton.demon.co.uk> (except
   where otherwise indicated).
                                                                     
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
  
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  

   $Id: ftp.c,v 1.1.1.1 1999/11/21 19:47:02 davek Exp $
*/

/* This contains an FTP client implementation.
 * It performs transparent connection management - it it dies,
 * it will be reconnected automagically.
 */

#include <config.h>

#include <sys/types.h>

#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <time.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif 
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */

#ifndef HAVE_SNPRINTF
#include "snprintf.h"
#endif

#include "common.h"
#include "frontend.h"
#include "ftp.h"
#include "protocol.h"
#include "socket.h"

char ftp_error[BUFSIZ];

bool ftp_use_passive = true; /* whether we use PASV mode or not */

/* The address of the server for the DTP connection - 
 * this goes here because it's got from ftp_read() rather than in a decent
 * manner */
unsigned short ftp_dtp_port;
struct in_addr ftp_dtp_addr;
int ftp_dtp_socket;

struct in_addr ftp_pi_addr;
unsigned short ftp_pi_port;
int ftp_pi_socket;

bool ftp_connection; /* true when open */

/* Reply codes - these are returned by internal functions,
 * ftp_exec and ftp_open mainly. */
#define FTP_OK 0
#define FTP_NEEDPASSWORD 1
#define FTP_PASSIVE 2
#define FTP_READY 3
#define FTP_FILEMORE 4
#define FTP_MODTIME 5
#define FTP_SENT 6

#define FTP_CLOSED 101
#define FTP_FILEBAD 102
#define FTP_CONNECT 992
#define FTP_HELLO 993
#define FTP_LOGIN 994
#define FTP_BROKEN 995
#define FTP_DENIED 996
#define FTP_UNSUPPORTED 997
#define FTP_NOPASSIVE 998
#define FTP_ERROR 999

/* time from MDTM response */
time_t ftp_modtime;

/* remember these... we may have to log in more than once. */
char *ftp_username, *ftp_password;

/* Stores the current transfer type is:
 *  -1: Unknown
 *   0: Binary
 *   1: ASCII
 */
int ftp_using_ascii;

/* Opens the control connection if necessary.
 * Returns:
 *   FTP_OK on success
 *   FTP_CONNECT on failed socket connect
 *   FTP_HELLO if the greeting message couldn't be read
 *   FTP_LOGIN on failed login
 */
int ftp_open( void );
int ftp_close( void );

int ftp_fetch_gettree( const char *startdir, struct proto_file_t **files );
int ftp_fetch_walktree( const char *rotodir, struct proto_file_t *files );

int ftp_login( void ); /* Performs the login procedure */
int ftp_settype( bool ascii );
int ftp_active_open(const char * );
int ftp_data_open( const char * ); /* Opens the data connection */
int ftp_data_close( void ); /* Closes the data connection */
int ftp_connect_pasv( void );
int ftp_read_pasv( const char * ); /* Used by ftp_response to get the remote IP */
int ftp_read_mdtm( const char *response );

int ftp_response( const char *, const int ); /* Gets the return code */
int get_reply_code( const char * ); /* Used by ftp_response to get the actual code */

int ftp_exec( const char * ); /* Executes given command, reads response */
int ftp_read( void );


/* Initializes the driver + connection.
 * Returns PROTO_OK on success, or PROTO_LOOKUP if the hostname
 * could not be resolved.
 */
int ftp_init( const char *remote_root, const char *hostname, const int port,
	      const char *username, const char *password ) {
    ftp_pi_port = port;
    fe_connection( fe_namelookup );
    if( host_lookup( hostname, &ftp_pi_addr ) )
	return PROTO_LOOKUP;
    ftp_username = strdup( username );
    ftp_password = strdup( password );
    ftp_connection = false;
    ftp_using_ascii = -1; /* unknown */
    switch( ftp_open() ) {
    case FTP_CONNECT:
    case FTP_HELLO:
	return PROTO_CONNECT;
    case FTP_LOGIN:
	return PROTO_AUTH;
    default:
	return PROTO_OK;
    }
}

/* Cleans up and closes the control connection.
 * Returns PROTO_OK if the connection is closed, else PROTO_ERROR;
 */
int ftp_finish( void ) {
    free( ftp_username );
    free( ftp_password );
    if( ftp_connection )
	if( ftp_close( ) != FTP_CLOSED )
	    return PROTO_ERROR;
    return PROTO_OK;
}

/* Closes the PI connection. */
int ftp_close( ) {
    int ret;
    ret = ftp_exec( "QUIT" );
    close( ftp_pi_socket );
    ftp_connection = false; /* although it should have been done already */
    return ret;
}

/* Creates the given directory
 * FTP state response */
int ftp_mkdir( const char *dir ) {
    char command[BUFSIZ];
    snprintf( command, BUFSIZ, "MKD %s", dir );
    if( ftp_exec( command ) == FTP_OK ) {
	return PROTO_OK;
    } else {
	return PROTO_ERROR;
    }
}
 
/* Renames or moves a file */
int ftp_move( const char *from, const char *to ) {
    char command[BUFSIZ];
    snprintf( command, BUFSIZ, "RNFR %s", from );
    if( ftp_exec( command ) == FTP_FILEMORE ) {
	snprintf( command, BUFSIZ, "RNTO %s", to );
	if( ftp_exec( command ) == PROTO_OK ) {
	    return PROTO_OK;
	}
    }
    return PROTO_ERROR;
}

int ftp_delete( const char *filename ) {
    char command[BUFSIZ];
    snprintf( command, BUFSIZ, "DELE %s", filename );
    if( ftp_exec( command ) == FTP_OK ) {
	return PROTO_OK;
    } else {
	return PROTO_ERROR;
    }
}

int ftp_rmdir( const char *filename ) {
    char command[BUFSIZ];
    snprintf( command, BUFSIZ, "RMD %s", filename );
    if( ftp_exec( command ) == FTP_OK ) {
	return PROTO_OK;
    } else {
	return PROTO_ERROR;
    }
}

/* FTP non-PASV mode open */
int ftp_active_open(const char *command) {
    char *a, *p;
    int ret;
    ksize_t slen;
    int listener;
    char cmd[BUFSIZ];
    struct sockaddr_in gotaddr;
    struct sockaddr_in localaddr;

    if( ftp_open( ) != FTP_OK ) return FTP_ERROR;
    
    slen = sizeof( localaddr );
    if( getsockname( ftp_pi_socket, (struct sockaddr *)&localaddr, &slen ) < 0 ) {
	ftp_seterror_err( "Could not get socket name" );
	return FTP_ERROR;
    }

    /* But let bind pick a random port for us to use */
    localaddr.sin_port = 0;

    /* Create a local socket to accept the connection on */

    listener = socket( AF_INET, SOCK_STREAM, 0 );
    if ( listener < 0 ) {
	ftp_seterror_err( "Could not create socket" );
	return FTP_ERROR;
    }

    /* Bind it to a port. */
    if ( bind(listener, (struct sockaddr *)&localaddr, sizeof(struct sockaddr_in)) < 0 ) {
	ftp_seterror_err( "Could not bind socket." );
	close( listener );
	return FTP_ERROR;
    }

    slen = sizeof( struct sockaddr_in );
    if (getsockname(listener, (struct sockaddr *)&gotaddr, &slen) < 0) {
	ftp_seterror_err( "Could get get name of socket" );
	close( listener );
	return FTP_ERROR;
    }

    if (listen(listener, 1) < 0) {
	ftp_seterror_err( "Could not listen for connection" );
	close( listener );
	return FTP_ERROR;
    }

    /* Let the remote end know the address of our port.
     * Q(joe): Will this actually work on differently-endian machines? 
     */
#define	UC(b)	(((int)b)&0xff)
    a = (char *)&gotaddr.sin_addr.s_addr;
    p = (char *)&gotaddr.sin_port;
    
    snprintf(cmd, BUFSIZ, "PORT %d,%d,%d,%d,%d,%d",
	     UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
	     UC(p[0]), UC(p[1]) );

    /* Execute the PORT command */
    ret = ftp_exec(cmd);
    if( ret != FTP_OK ) {
	/* Failed to execute the PORT command - close the socket */
	DEBUG( DEBUG_FTP, "PORT command failed.\n" );
	close( listener );
	return ret;
    }

    /* Send the command.  This will make the remote end
     * initiate the connection.
     */
    ret = ftp_exec(command);
    
    if( ret != FTP_READY ) {
	/* Do they want it? */
	DEBUG( DEBUG_FTP, "Command failed.\n" );
	close( listener );
	return ret;
    }

    /* Now wait for a connection from the remote end.
     */
    ftp_dtp_socket = accept( listener, NULL, NULL );

    if (ftp_dtp_socket < 0) {
	close( listener );
	ftp_seterror_err( "Could not accept connection" );
	return FTP_ERROR;
    }

    close( listener );
    
    return ret;
}

int ftp_chmod( const char *filename, const mode_t mode ) {
    char command[BUFSIZ];
    snprintf( command, BUFSIZ, "SITE CHMOD %3o %s", mode & 0777, filename );
    if( ftp_exec( command ) == FTP_OK ) {
	return PROTO_OK;
    } else {
	return PROTO_ERROR;
    }
}

int ftp_data_open( const char *command ) {
    int ret;
    if( ftp_use_passive ) {
	ret = ftp_exec( "PASV" );
	if( ret == FTP_PASSIVE ) {
	    if( ftp_connect_pasv( ) ) {
		return ftp_exec( command );
	    } else {
		return FTP_ERROR;
	    }
	} else {
	    return FTP_NOPASSIVE;
	}
    } else {
	/* we are not using passive mode. */
	return ftp_active_open(command);
    }

}

/* Closes the data connection */
int ftp_data_close( ) {
    close( ftp_dtp_socket );
    /* sent okay? */
    return ftp_read();
}

/* Set the transfer type appropriately.
 * Only set it if it *needs* setting, though.
 * Returns FTP_OK on success, or something else otherwise. */
int ftp_settype( bool ascii ) {
    int newascii, ret;
    newascii = ascii?1:0;
    if( (ftp_using_ascii == -1) || (newascii != ftp_using_ascii ) ) {
	ret = ftp_exec( ascii?"TYPE A":"TYPE I" );
	if( ret == FTP_OK ) {
	    ftp_using_ascii = newascii;
	} else {
	    ftp_using_ascii = -1; /* unknown */
	}
    } else {
	ret = FTP_OK;
    }
    return ret;
}

/* upload the given file */
int ftp_put( const char *localfile, const char *remotefile, const bool ascii ) {
    char command[BUFSIZ];

    /* Set the transfer type correctly */
    if( ftp_settype( ascii ) != FTP_OK )
	return PROTO_ERROR;

    snprintf( command, BUFSIZ, "STOR %s", remotefile );
    if( ftp_data_open( command ) == FTP_READY ) {
	/* Send the file. FIXME: We rely on the server's return
	 * code to determine whether the transfer was successful. */
	if( ascii ) {
	    send_file_ascii( ftp_dtp_socket, localfile );
	} else {
	    send_file( ftp_dtp_socket, localfile );
	}
	if( ftp_data_close( ) == FTP_SENT ) {
	    return PROTO_OK;
	}
    }
    return PROTO_ERROR;
}

/* Slightly dodgy ftp_get in that you need to know the remote file
 * size. Works, but not very generic. */
int ftp_get( const char *localfile, const char *remotefile, 
	     const int remotesize, const bool ascii ) {
    char command[BUFSIZ];
    int ret;

    if(ascii) strcpy( command, "TYPE A" );
    else strcpy( command, "TYPE I");
    ret = ftp_exec( command );
    snprintf( command, BUFSIZ, "RETR %s", remotefile );
    if( ftp_data_open( command ) == FTP_READY ) {
	/* send the file */
	recv_file( ftp_dtp_socket, localfile, remotesize );
	if( ftp_data_close( ) == FTP_SENT ) {
	    return PROTO_OK;
	}
    }
    return PROTO_ERROR;
}

/* Connect to the given host 
 * Returns non-zero value if successfull */
int ftp_connect_pasv() {
    int sock;
    sock = socket_connect( ftp_dtp_addr, ftp_dtp_port );
    if( sock == -1 ) {
	ftp_seterror_err( "Could not connect socket" );
	return 0;
    } else {
	ftp_dtp_socket = sock;
	return 1;
    }
}

int ftp_open( ) {
    int ret;

    if( ftp_connection != false ) return FTP_OK;
    fe_connection( fe_connecting );
    DEBUG( DEBUG_FTP, "Opening socket to port %d\n", ftp_pi_port );
    /* Open the socket. */
    ftp_pi_socket = socket_connect( ftp_pi_addr, ftp_pi_port );
    if( ftp_pi_socket < 0 ) {
	return FTP_CONNECT;
    }
    /* Read the hello message */
    ret = ftp_read();
    if( ret != FTP_OK ) {
	return FTP_HELLO;
    }
    ftp_connection = true;
    if( ftp_login( ) != FTP_OK ) {
/*	ftp_seterror( "Could not log in to server." ); */
	ftp_connection = false;
	close( ftp_pi_socket );
	return FTP_LOGIN;
    }
    fe_connection( fe_connected );
    return FTP_OK;
}

int ftp_login( ) {
    char cmd[BUFSIZ];
    int ret;
    DEBUG( DEBUG_FTP,  "FTP: Logging in as %s...\n", ftp_username );
    snprintf( cmd, BUFSIZ, "USER %s", ftp_username );
    ret = ftp_exec( cmd );
    if( ret == FTP_NEEDPASSWORD ) {
	DEBUG( DEBUG_FTP,  "FTP: Supplying password...\n" );
	snprintf( cmd, BUFSIZ, "PASS %s", ftp_password );
	ret = ftp_exec( cmd );
    }
    return ret;
}

/* Returns anything which ftp_read() does */
int ftp_exec( const char *command ) {
    int tries = 0, ret = FTP_ERROR;
    while( ++tries < 3 ) {
	if( ftp_open( ) != FTP_OK ) break;
	DEBUG( DEBUG_SOCKET, "> %s\n", command );
	/* Send the line */
	if( send_line( ftp_pi_socket, (char *) command ) == 0 ) {
	    /* Sent the line... try and read the response */
	    ret = ftp_read();
	    if( ret != FTP_BROKEN ) {
		/* Read the response okay */
		break;
	    }
	}
    }
    /* Don't let FTP_BROKEN get out */
    if( ret == FTP_BROKEN ) 
	ret = FTP_ERROR;
    return ret;
}

/* Returns anything ftp_read() does, or FTP_BROKEN when the
 * socket read fails (indicating a broken socket).  
 */

int ftp_read() {
    int multiline, len, reply_code;
    char buffer[BUFSIZ];

    multiline = 0;

    for(;;) {
	if( (len = read_line(ftp_pi_socket, buffer, BUFSIZ )) < 0 ) {
	    /* It broke. */
	    ftp_connection = false;
	    break;
	}
	
	DEBUG( DEBUG_SOCKET, "< %s", buffer );
	if(len<5) /* this is a multi-liner, ignore it and carry on */
	    continue; 
	/* parse the reply code from the line */
	reply_code = get_reply_code(buffer);
	
	/* If we have a VALID reply code and we are currently
	 * in a multi line response then this is the end of the
	 * multi line response */
	if(multiline && reply_code)
	    multiline=0;
	
	/* Now, if we aren't in a multi line response... */
	if(!multiline) { 
	    if(buffer[3]=='-') {
		/* A dash in char four denotes beginning of multi
		 * line response  'xxx-' */
		multiline=1;
	    } else {
		/* Looks like we've got a real response line */
		return ftp_response( buffer, reply_code );
	    }
	}
    }
    return FTP_BROKEN;
}

void ftp_seterror( const char *error ) {
    memset( ftp_error, 0, BUFSIZ );
    strncpy( ftp_error, error, BUFSIZ );
/*    DEBUG( DEBUG_FTP, "FTP Error set: %s\n", ftp_error ); */
}

/* Sets the error string and appends ": strerror(errno)" */
void ftp_seterror_err( const char *error ) {
    snprintf( ftp_error, BUFSIZ, "%s: %s", error, strerror(errno) );
    DEBUG( DEBUG_FTP, "FTP Error set: %s\n", ftp_error );
}

int ftp_response( const char *response, const int code ) {
    char *newline;
    /* Set the error string up. */
    ftp_seterror( response );
    /* Chop the newline */
    newline = strrchr( ftp_error, '\r' );
    if( newline ) *newline = '\0';	
    switch( code ) {
    case 200: /* misc OK codes */
    case 220:
    case 230:
    case 250: /* completed file action */
    case 257: /* mkdir success */
	return FTP_OK;
    case 226: /* received file okay */
	return FTP_SENT;
    case 150: /* file transfer... ready for data */
    case 125:
	return FTP_READY;
    case 550: /* couldn't complete file action */
	return FTP_FILEBAD;
    case 331: /* got username, want password */
	return FTP_NEEDPASSWORD;
    case 350: /* file action pending further info - RNFR */
	return FTP_FILEMORE;
    case 221:
	/* They've closed the connection, the swine. */
	ftp_connection = false;
	return FTP_CLOSED;
    case 421: /* service denied */
	return FTP_DENIED;
    case 213: /* MDTM response, hopefully */
	return ftp_read_mdtm( response );
    case 227: /* PASV response, hopefully */
	return ftp_read_pasv( response );
    case 553: /* couldn't create directory */
	return FTP_ERROR;
    default:
	return FTP_ERROR;
    }
}

/* Parses the 213 response to a MDTM command... on success,
 * returns FTP_MODTIME and sets ftp_modtime to the time in the response.
 * On failute, returns FTP_ERROR. */
int ftp_read_mdtm( const char *response ) {
    struct tm time;
    char year[5], month[3], mday[3], hour[3], min[3], sec[3];
    char *pnt;

    if( (pnt = strrchr( response, '\n' ))!=NULL ) *pnt='\0';
    if( (pnt = strrchr( response, '\r' ))!=NULL ) *pnt='\0';
    DEBUG( DEBUG_FTP, "Reading modtime: %s\n", response );
    if( strlen( response ) != 18 ) {
	DEBUG( DEBUG_FTP, "Incorrect length response." );
	return FTP_ERROR;
    }
    if( sscanf( response, "213 %4s%2s%2s" "%2s%2s%2s",
		year, month, mday,   hour, min, sec ) < 6 ) {
	DEBUG( DEBUG_FTP, "sscanf couldn't parse it.\n" );
	return FTP_ERROR;
    }
    DEBUG( DEBUG_FTP, "Parsed: %d/%d/%d %s:%s:%s\n",
	   atoi(year), atoi(month), atoi(mday), hour, min, sec );
    
    memset( &time, 0, sizeof( struct tm ) );
    
    time.tm_year = atoi( year ) - 1900; /* years since 1900 */
    time.tm_mon = atoi( month ) - 1; /* months since jan */
    time.tm_mday = atoi( mday );
    
    time.tm_hour = atoi( hour );
    time.tm_min = atoi( min );
    time.tm_sec = atoi( sec );

    time.tm_isdst = -1;
    ftp_modtime = mktime( &time );

    DEBUG( DEBUG_FTP, "Converted to: %s", ctime( &ftp_modtime ) );

    return FTP_MODTIME;
}

/* Parses the response to a PASV command.
 * Sets ftp_dtp_port to the port and ftp_dtp_addr to the address given
 * in the response and returns FTP_PASSIVE on success.
 * On failure, returns FTP_ERROR;
 */
int ftp_read_pasv( const char *response ) {
    int h1, h2, h3, h4, p1, p2;
    char *start;
    start = strchr( response, '(' );
    /* get the host + port */
    if( sscanf( ++start, "%d,%d,%d,%d,%d,%d", &h1, &h2, &h3, &h4, &p1, &p2 ) < 6 )
	/* didn't match, give up */
	return FTP_ERROR;
    /* Record this for future reference */
    ftp_dtp_port = (p1<<8) | p2;
    ftp_dtp_addr.s_addr = htonl( (h1<<24) | (h2<<16) | (h3<<8) | h4 );
    return FTP_PASSIVE;
}

/* Takes the response line from an FTP command and returns the value
 * of the response code, or 0 if none is found.
 */
int get_reply_code( const char *buffer) {
    if(strlen(buffer) > 3)
	if( isdigit((unsigned)buffer[0]) &&
	    isdigit((unsigned)buffer[1]) && 
	    isdigit((unsigned)buffer[2]) )
	    /* looks good */
	    return atoi(buffer);
    return 0;
}

/* This one does the ls -laR, and tries it's best to parse the resulting
 * list. Currently implemented only for Unix-style servers, which go:
 * dirlist
 * new/directory/name:
 * dirlist
 * another/directory/name:
 * dirlist
 * etc. where dirlist is a straight ls -al listing
 */
int ftp_fetch_gettree( const char *startdir, struct proto_file_t **files ) {
    struct proto_file_t *this_file, *last_file;
    char command[BUFSIZ], buffer[BUFSIZ], *pnt;
    char *curdir;   /* Holds the path of the current directory */
    char perms[BUFSIZ], filename[BUFSIZ], tmp[BUFSIZ];
    int ret, filesize, buflen;
    bool afterblank;

    snprintf( command, BUFSIZ, "LIST -laR %s", startdir );
    if( (ret = ftp_data_open( command )) != FTP_READY ) {
	return PROTO_ERROR;
    }

    /* The current directory is a 0-length string. */
    curdir = malloc( 1 );
    *curdir = '\0';
    last_file = NULL;

    afterblank = false;
    while( read_line( ftp_dtp_socket, buffer, BUFSIZ ) >= 0 ) {
	/* Get rid of the EOL */
	if( (pnt = strrchr( buffer, '\n' ))!=NULL ) *pnt='\0';
	if( (pnt = strrchr( buffer, '\r' ))!=NULL ) *pnt='\0';
	DEBUG( DEBUG_FTP, "[ls] < %s\n", buffer );
	buflen = strlen( buffer );
	if( buflen > 0 ) {
	    if( strncmp( buffer, "total ", 6 ) == 0 ) {
		/* ignore the line */
		DEBUG( DEBUG_FTP, "Line ignored.\n" );
	    } else if( *(buffer+buflen-1) == ':' && afterblank ) {
		/* A new directory name indicator, which goes:
		 *    `directory/name/here:'
		 * We want directory names as:
		 *    `/directory/name/here' 
		 * Hence a bit of messing about. */
		free( curdir );
		buflen = buflen - strlen(startdir );
		curdir = malloc( buflen + 1 );
		strncpy( curdir, buffer+strlen(startdir), buflen );
		*(curdir+buflen-1) = '/';
		*(curdir+buflen) = '\0';
		DEBUG( DEBUG_FTP, "Now in directory: %s\n", curdir );
	    } else {
		/* Weird bit at the end should pick up everything
		 * to the EOL... filenames could have whitespace in.
		 */
		sscanf( buffer, "%s %s %s %s %d %s %s %s %[^*]",  
			perms, tmp, tmp, tmp, &filesize,
			tmp, tmp, tmp, filename);
		if( perms!=NULL && filename!=NULL ) {
		    if( *perms == '-' ) {
			/* Normal file. */
			DEBUG( DEBUG_FTP, "File: %s, size %d\n",
			       filename, filesize );
			this_file = malloc( sizeof(struct proto_file_t) );
			memset( this_file, 0, sizeof(struct proto_file_t) );
			this_file->next = *files;
			*files = this_file;
			if( last_file==NULL ) last_file = this_file;
			this_file->filename = strdup( filename );
			this_file->directory = strdup( curdir );
			this_file->isdir = false;
			this_file->size = filesize;
		    } else if( *perms == 'd' ) {
			/* Subdirectory */
			if( strcmp( filename, "." ) == 0 ||
			    strcmp( filename, ".." ) == 0 ) {
			    DEBUG( DEBUG_FTP, "Ignoring: %s\n", filename );
			} else {
			    DEBUG( DEBUG_FTP, "Subdir: %s\n", filename );
			    this_file = malloc( sizeof(struct proto_file_t) );
			    memset( this_file, 0, sizeof(struct proto_file_t) );
			    if( last_file==NULL ) {
				*files = this_file;
			    } else {
				last_file->next = this_file;
			    }
			    last_file = this_file;
			    this_file->filename = strdup( filename );
			    this_file->directory = strdup( curdir );
			    this_file->isdir = true;
			}
		    } else { 
			/* Summant else... ignore */
			DEBUG( DEBUG_FTP, "Ignoring: %s\n", filename );
		    }
		} else {
		    DEBUG( DEBUG_FTP, "Could not parse line.\n" );
		}
	    }
	} else {
	    DEBUG( DEBUG_FTP, "Blank line.\n" );
	    afterblank = true;
	}
    }
    DEBUG( DEBUG_FTP, "Fetch finished successfully.\n" );
    if( ftp_data_close( ) == FTP_SENT ) {
	return FTP_OK;
    } else {
	return FTP_ERROR;
    }
}

/* Sorts out the modtimes for all the files in the list.
 * Returns FTP_OK on success, else FTP_ERROR. */
int ftp_fetch_walktree( const char *rootdir, struct proto_file_t *files ) {
    struct proto_file_t *this_file;
    char command[BUFSIZ];
 
    /* Walk the files list. Okay, it's not really a tree */
    for( this_file=files; this_file!=NULL; this_file=this_file->next ) {
	if( this_file->isdir ) {
	    continue;
	}
	DEBUG( DEBUG_FTP, "File: %s%s%s\n", rootdir,
	       this_file->directory, this_file->filename );
	snprintf( command, BUFSIZ, "MDTM %s%s%s", 
		  rootdir, this_file->directory, this_file->filename );
	if( ftp_exec( command ) == FTP_MODTIME ) {
	    DEBUG( DEBUG_FTP, "Got modtime.\n" );
	    this_file->modtime = ftp_modtime;
	} else {
	    DEBUG( DEBUG_FTP, "Didn't get modtime.\n" );
	    return FTP_ERROR;
	}
    }
    DEBUG( DEBUG_FTP, "Walk finished ok.\n" );

    return FTP_OK;
}

/* Retrieves remote file listings.
 * This is a multi-stage operation.
 * First off, we do a LIST -R, which returns a recursive file listing.
 * Next, we go through the entire list of files returned and do a
 * MDTM on them to retrieve their accurate modtime.
 * If we can successfully retrieve every modtime, then we pass the
 * files one by one back up to the sites code. About as inefficient
 * as it gets.
 */
int ftp_fetch( const char *startdir, struct proto_file_t **files ) {
    int ret;

    ret = ftp_fetch_gettree( startdir, files );
    if( ret == FTP_OK ) {
	ret = ftp_fetch_walktree( startdir, *files );
    }
    
    if( ret == FTP_OK ) {
	return PROTO_OK;
    } else {
	return PROTO_ERROR;
    }   

}