/* ftpget.c (M. Andreoli) Heavy based on snarf code by Zachary Beane 1996 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAXLEN 4096 #define LOG(x,y) if(verbose) fprintf(stderr,"%s %s",x,y) struct url { char u_service[24]; char u_host[128]; unsigned short u_port; char u_path[255]; char u_file[128]; char user[16]; char password[16]; char cmd[256]; }; int resume=0, verbose=0; int upload_stdin=0; static int getresponse (int, char *) ; off_t getsize(const char *); enum {RETR, STOR, LIST}; int op; int rc; int usage() { printf("ftpget: connection-less FTP client (1999,by M.Andreoli) \n\ Usage: \tftpget -h host {FTP hostname}\n\ \t[-u user -p password ] {default: anonymous login}\n\ \t[-c command ] {optional FTP command, after auth phase}\n\ \t[-d dir/] \n\ \t[ -f file] {without -f, send LIST command}\n\ \t[-i] {upload from stdin, instead of download to stdout}\n\ \t[-v] {verbose log}\n\ \n\ "); exit(0); } #define BIG_BUFFER_SIZE 4096 /* Reads a \n terminated line (includes the \n) */ int readline (int fd, char *line) { int n, rc = 0; char c ; for (n = 1 ; n < BIG_BUFFER_SIZE ; n++) { if ((rc == read (fd, &c, 1)) != 1) { *line++ = c ; if (c == '\n') break ; } else if (rc == 0) { if (n == 1) return 0 ; /* EOF */ else break ; /* unexpected EOF */ } else return -1 ; } *line = 0 ; return n ; } int dump_data(int fd_in, int fd_out){ int i; char recv_buf[BIG_BUFFER_SIZE]; while(i = read(fd_in, recv_buf, sizeof(recv_buf))) write(fd_out, recv_buf, i); return(1); } int upload( int fd_out){ int n; char recv_buf[BIG_BUFFER_SIZE]; while ( 1 ) { n=read(0, recv_buf, sizeof(recv_buf) ); if (n == 0) break; /* EOF. */ if (n < 0) return -2; write(fd_out, recv_buf, n); } return(0); } /* -- fine misc --*/ int ftp_get (struct url u, char *outfile) { struct hostent *host ; struct sockaddr_in sin ; int s, control, data, tmp ; char send_buf[MAXLEN] ; char line[MAXLEN] ; unsigned char *a, *p ; int fd, open_flags = O_CREAT | O_WRONLY; int response; off_t file_offset; if(resume) open_flags |= O_APPEND; else open_flags |= O_TRUNC; /* get the host's IP addr */ if((host = gethostbyname(u.u_host)) == NULL) { fprintf(stderr, "%s: host not found.\n", u.u_host); return(1); } /* get the socket */ if((control = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Unable to create socket.\n"); return(1); } /* connect the socket, filling in the important stuff */ sin.sin_family = AF_INET; sin.sin_port = htons(u.u_port); bcopy(host->h_addr, &sin.sin_addr, host->h_length); if(connect(control, (struct sockaddr *)&sin, sizeof(sin)) < 0) { fprintf(stderr, "Connection to host %s refused.\n", u.u_host); return(1); } LOG("connected to",u.u_host); if (rc=getresponse (control, line) != 220) /* ready for new user */ return rc ; sprintf (send_buf, "USER %s\r\n", u.user) ; LOG("send",send_buf); send (control, send_buf, strlen (send_buf), 0) ; response = getresponse(control, line); if (response != 331 && response != 230) /* login ok, enter passwd - or, you're logged in without further info necessary - zpb */ return response ; sprintf (send_buf, "PASS %s\r\n", u.password) ; /* some ftp servers may require rest of address too - not done - actually most are pretty flexible, and just warn you if you don't put in the whole thing; i've yet to see one that actually requires the whole thing - zpb */ LOG("send",send_buf); send (control, send_buf, strlen (send_buf), 0) ; #ifdef DEBUG fprintf(stderr, "checking guest login ability\n"); #endif response = getresponse(control, line); if (response != 230 && response != 202) /* guest login ok - or no passwd needed - zpb */ return response ; if (strlen(u.u_path)!=0) { sprintf (send_buf, "CWD %s\r\n", u.u_path) ; LOG("send",send_buf); send (control, send_buf, strlen (send_buf), 0) ; if (rc=getresponse (control, line) != 250) /* cwd command successful */ return rc ; } sprintf (send_buf, "TYPE I\r\n") ; /* always use binary */ LOG("send",send_buf); send (control, send_buf, strlen (send_buf), 0) ; if (rc=getresponse (control, line) != 200) /* type set to I */ return rc ; /* user's command */ if ( strlen(u.cmd)!=0 ) { sprintf (send_buf, "%s\r\n", u.cmd) ; LOG("send",send_buf); send (control, send_buf, strlen (send_buf), 0) ; if (getresponse (control, line) != 250) /*return 0*/ ; } /* now for the dirty part - create data connection and suck data from server */ if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Unable to create socket, shitola.\n"); return(1); } tmp = sizeof (sin) ; /* We use PORT, rather than SO_REUSEADDR. However, you can easily modify the code if you want that */ getsockname (control, (struct sockaddr *)&sin, &tmp) ; sin.sin_port = 0 ; /* let system choose a port */ if (bind (s, (struct sockaddr *)&sin, sizeof (sin)) < 0) { fprintf (stderr, "Can't bind.\n") ; return 1 ; } getsockname (s, (struct sockaddr *)&sin, &tmp) ; listen (s, 1) ; a = (unsigned char *)&sin.sin_addr ; p = (unsigned char *)&sin.sin_port ; sprintf (send_buf, "PORT %d,%d,%d,%d,%d,%d\r\n", a[0], a[1], a[2], a[3], p[0], p[1]) ; #ifdef DEBUG fprintf (stderr, "PORT %d,%d,%d,%d,%d,%d\r\n", a[0], a[1], a[2], a[3], p[0], p[1]) ; #endif LOG("send",send_buf); send (control, send_buf, strlen (send_buf), 0) ; if (rc=getresponse (control, line) != 200) /* port command successful */ return rc ; /* get a file descriptor pointing at the right place */ if(strcmp(outfile, "-") == 0) fd = 1; else if((fd = open(outfile, open_flags, 0644)) < 0) { fprintf(stderr, "Unable to open output file %s for writing.\n", outfile); return(1); } /* if the resume option is set, get the size of the local file and tell the ftp server to restart the transfer at that offset in the remote file - xach */ if(resume){ file_offset = getsize(u.u_file); sprintf(send_buf, "REST %ld\r\n", file_offset); send(control, send_buf, strlen(send_buf), 0); if(rc=getresponse(control, line) != 350) return rc ; } /* if URL ends in a /, we ask for a directory listing */ if (strcmp (u.u_file, "")) { if ( upload_stdin) { op=STOR; sprintf (send_buf, "STOR %s\r\n", u.u_file) ; } else { op=RETR; sprintf (send_buf, "RETR %s\r\n", u.u_file) ; } } else { sprintf (send_buf, "LIST\r\n") ; op=LIST; } LOG("send",send_buf); send (control, send_buf, strlen (send_buf), 0) ; getresponse (control, line); /* if (rc=getresponse (control, line) != 150) return rc ; */ data = accept (s, (struct sockaddr *)&sin, &tmp) ; if (data < 0) { fprintf (stderr, "Accept error.\n") ; return 1 ; } switch(op) { case LIST: case RETR: LOG("dump data\n",""); dump_data(data, fd); break; case STOR: LOG("dump stdin\n",""); upload(data); close(data); } getresponse (control, line) ; sprintf (send_buf, "QUIT\r\n") ; LOG("send",send_buf); send (control, send_buf, strlen (send_buf), 0) ; getresponse (control, line) ; close (fd) ; close (control) ; close (data) ; close (s) ; return 0 ; } /* Gets replies from server, ignores informational lines, and returns reply code */ static int getresponse (int fd, char *line) { int read ; while ((read = readline (fd, line)) > 0) { #ifdef DEBUG fprintf(stderr, "%c\n", line[0]); #endif if (verbose) fprintf (stderr, "%s", line) ; if (line[3] != '-' && (isdigit(line[0]))) /* if it's not some informative message */ /* - actually, microsoft ftp servers (and perhaps others) are lame enough to reply with status messages that have a space as the first letter. stupid! i think. -zpb whoops...the rfc says you can do that, heh. you have an initial response that starts with a number and a dash, then iwhatever you like, and then a line with the actual status number - zpb */ break ; } if (read <= 0) return read ; else return atoi (line) ; } /* this function returns the size of a file (if it exists) or zero if it doesnt */ off_t getsize(const char *file){ struct stat file_info; if(stat(file, &file_info) == -1) return(0); else return(file_info.st_size); } main(int argc, char **argv) { int i=1; struct url u; u.u_port=21; sprintf(u.user,"anonymous"); sprintf(u.password,"a@b.it"); sprintf(u.u_path,"/"); sprintf(u.u_file,""); sprintf(u.u_host,""); sprintf(u.cmd,""); while (i<=argc-1) { if ( argv[i][0] == '-' ) switch( argv[i][1] ) { case 'h': { i++; strcpy(u.u_host,argv[i]); break;} case 'u': { i++; strcpy(u.user,argv[i]); break;} case 'p': { i++; strcpy(u.password,argv[i]); break;} case 'd': { i++; strcpy(u.u_path,argv[i]); break;} case 'f': { i++; strcpy(u.u_file,argv[i]); break;} case 'c': { i++; strcpy(u.cmd,argv[i]); break;} case 'i': { upload_stdin=1; break;} case 'v': { verbose=1; break;} default: usage(); } i++; } if ( strcmp(u.u_host,"") == 0 ) usage(); /* fprintf(stderr, "host=%s, user=%s, pass=%s, \ndir=%s, file=%s\n", u.u_host,u.user,u.password,u.u_path, u.u_file ); */ return ftp_get(u,"-"); }