1964 lines
55 KiB
C
1964 lines
55 KiB
C
#define _FILE_OFFSET_BITS 64
|
|
#define _GNU_SOURCE
|
|
#include "socket.h"
|
|
#include "byte.h"
|
|
#include "buffer.h"
|
|
#include "scan.h"
|
|
#include "ip6.h"
|
|
#include "str.h"
|
|
#include "fmt.h"
|
|
#include "ip4.h"
|
|
#include "io.h"
|
|
#include "case.h"
|
|
#include "stralloc.h"
|
|
#include "textcode.h"
|
|
#include "uint16.h"
|
|
#include "uint64.h"
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <stdlib.h>
|
|
#include <utime.h>
|
|
#ifdef __MINGW32__
|
|
#include <windows.h>
|
|
#include <fcntl.h>
|
|
#else
|
|
#include <sys/resource.h>
|
|
#include <sys/uio.h>
|
|
#endif
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#ifdef __MINGW32__
|
|
#include "windows.h"
|
|
#include <malloc.h>
|
|
#else
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netdb.h>
|
|
#include "havealloca.h"
|
|
#endif
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
#ifndef __linux__
|
|
char *strndup(const char *s,size_t n) {
|
|
char *tmp=!(n+1)?0:(char *)malloc(n+1);
|
|
if (!tmp) return 0;
|
|
strncpy(tmp,s,n);
|
|
tmp[n]=0;
|
|
return tmp;
|
|
}
|
|
#endif
|
|
|
|
int dostats;
|
|
int dosync;
|
|
time_t ims=0;
|
|
int verbose=0;
|
|
int ignoreeof;
|
|
|
|
char* todel;
|
|
|
|
void alarm_handler(int dummy) {
|
|
(void)dummy;
|
|
if (todel) unlink(todel);
|
|
exit(1);
|
|
}
|
|
|
|
static void clearstats();
|
|
|
|
static void carp(const char* routine) {
|
|
clearstats();
|
|
buffer_puts(buffer_2,"dl: ");
|
|
buffer_puts(buffer_2,routine);
|
|
if (routine[0] && routine[str_len(routine)-1]!='\n') {
|
|
buffer_puts(buffer_2,": ");
|
|
buffer_puterror(buffer_2);
|
|
buffer_putnlflush(buffer_2);
|
|
} else
|
|
buffer_flush(buffer_2);
|
|
}
|
|
|
|
static void panic(const char* routine) {
|
|
carp(routine);
|
|
exit(111);
|
|
}
|
|
|
|
static unsigned long long int total;
|
|
static unsigned long long resumeofs;
|
|
|
|
static int statsprinted;
|
|
|
|
void printstats(unsigned long long nextchunk,int fd) {
|
|
static unsigned long long int finished;
|
|
static struct timeval start,now,prev;
|
|
finished+=nextchunk;
|
|
if (start.tv_sec==0) {
|
|
gettimeofday(&start,0); now=start; prev=start;
|
|
return;
|
|
}
|
|
prev=now; gettimeofday(&now,0);
|
|
if (prev.tv_sec!=now.tv_sec) {
|
|
char received[FMT_ULONG], totalsize[FMT_ULONG], timedone[FMT_ULONG], percent[10];
|
|
char speed[FMT_ULONG+20];
|
|
size_t i,j;
|
|
#ifndef __MINGW32__
|
|
if (dosync) fsync(fd);
|
|
#endif
|
|
if (!dostats) return;
|
|
if (total) {
|
|
if (total>1000000000)
|
|
i=finished/(total/10000);
|
|
else
|
|
i=finished*10000/total;
|
|
j=fmt_ulong(percent,i/100);
|
|
percent[j]='.';
|
|
percent[j+1]=((i/10)%10)+'0';
|
|
percent[j+2]=(i%10)+'0';
|
|
percent[j+3]=0;
|
|
} else
|
|
strcpy(percent,"100.00");
|
|
j=fmt_humank(received,resumeofs+finished);
|
|
if (received[j-1]<='9') received[j++]='i';
|
|
received[j]=0;
|
|
j=fmt_humank(totalsize,resumeofs+total);
|
|
if (totalsize[j-1]<='9') totalsize[j++]='i';
|
|
totalsize[j]=0;
|
|
|
|
if (now.tv_sec-start.tv_sec>=60) {
|
|
j=fmt_ulong(timedone,(now.tv_sec-start.tv_sec)/60);
|
|
timedone[j]=':';
|
|
i=(now.tv_sec-start.tv_sec)%60;
|
|
timedone[j+1]=(i/10)+'0';
|
|
timedone[j+2]=(i%10)+'0';
|
|
timedone[j+3]=0;
|
|
} else {
|
|
j=fmt_ulong(timedone,now.tv_sec-start.tv_sec);
|
|
j+=fmt_str(timedone+j," sec");
|
|
timedone[j]=0;
|
|
}
|
|
|
|
if (now.tv_sec-start.tv_sec>1 && total) {
|
|
unsigned long timediff=(now.tv_sec-start.tv_sec)*100;
|
|
timediff += (now.tv_usec-start.tv_usec)/10000;
|
|
|
|
i=finished*100/timediff;
|
|
j=fmt_str(speed," (");
|
|
j+=fmt_humank(speed+j,i);
|
|
j+=fmt_str(speed+j,"iB/sec)"+(i>1000));
|
|
speed[j]=0;
|
|
} else
|
|
speed[0]=0;
|
|
|
|
if (now.tv_sec > start.tv_sec+3 && now.tv_sec-start.tv_sec) {
|
|
unsigned long long int bps=finished/(now.tv_sec-start.tv_sec);
|
|
size_t k=(total-finished)/bps;
|
|
char lm[FMT_ULONG];
|
|
|
|
if (k>=60) {
|
|
j=fmt_ulong(lm,k/60);
|
|
lm[j]=':';
|
|
i=k%60;
|
|
lm[j+1]=(i/10)+'0';
|
|
lm[j+2]=(i%10)+'0';
|
|
lm[j+3]=0;
|
|
} else {
|
|
j=fmt_ulong(lm,k);
|
|
j+=fmt_str(lm+j," sec");
|
|
lm[j]=0;
|
|
}
|
|
|
|
buffer_putm(buffer_2,"\r",percent,"% done; got ",received,"B ");
|
|
if (total)
|
|
buffer_putm(buffer_2,"of ",totalsize,"B ");
|
|
buffer_putmflush(buffer_2,"in ",timedone,speed,", ",lm," to go. ");
|
|
} else {
|
|
buffer_putm(buffer_2,"\r",percent,"% done; got ",received,"B ");
|
|
if (total)
|
|
buffer_putm(buffer_2,"of ",totalsize,"B ");
|
|
buffer_putmflush(buffer_2,"in ",timedone,speed,". ");
|
|
}
|
|
statsprinted=1;
|
|
}
|
|
}
|
|
|
|
static void clearstats() {
|
|
if (statsprinted) buffer_putsflush(buffer_2,"\r\e[K");
|
|
}
|
|
|
|
|
|
static int make_connection(char* ip,uint16 port,uint32 scope_id) {
|
|
int v6=byte_diff(ip,12,V4mappedprefix);
|
|
int s;
|
|
if (v6) {
|
|
s=socket_tcp6b();
|
|
if (socket_connect6(s,ip,port,scope_id)==-1) {
|
|
#if 0
|
|
char a[100],b[100],c[100];
|
|
a[fmt_ulong(a,port)]=0;
|
|
b[fmt_ulong(b,scope_id)]=0;
|
|
c[fmt_ip6c(c,ip)]=0;
|
|
printf("socket_connect6(%s,%s,%s) failed!\n",c,a,b);
|
|
#endif
|
|
carp("socket_connect6");
|
|
close(s);
|
|
return -1;
|
|
}
|
|
} else {
|
|
s=socket_tcp4b();
|
|
if (socket_connect4(s,ip+12,port)==-1) {
|
|
carp("socket_connect4");
|
|
close(s);
|
|
return -1;
|
|
}
|
|
}
|
|
#ifdef TCP_NODELAY
|
|
{
|
|
int one=1;
|
|
setsockopt(s,IPPROTO_TCP,TCP_NODELAY,&one,sizeof(one));
|
|
}
|
|
#endif
|
|
return s;
|
|
}
|
|
|
|
struct cookie {
|
|
const char* domain, * path, * name, * value;
|
|
struct cookie* next;
|
|
}* cookies;
|
|
|
|
void addcookie(const char* s,const char* curdomain) {
|
|
struct cookie* n;
|
|
char* x;
|
|
char* t=strchr(s,'\n');
|
|
if (!t) t=strchr(s,0);
|
|
if (t>s && t[-1]=='\r') --t;
|
|
if (case_starts(s,"set-cookie:")) {
|
|
s += sizeof("set-cookie");
|
|
while (*s==' ' || *s=='\t') ++s;
|
|
}
|
|
if (s==t) return;
|
|
if (!(x=strndup(s,t-s))) return;
|
|
if (!(n=malloc(sizeof(*n)))) goto kaputt2;
|
|
n->name=x;
|
|
while (*x && *x!='=') ++x;
|
|
if (*x!='=') {
|
|
kaputt:
|
|
free(n);
|
|
kaputt2:
|
|
free(x);
|
|
return;
|
|
}
|
|
*x=0;
|
|
n->value=++x;
|
|
while (*x && *x!=';' && *x!=' ' && *x!='\t') ++x;
|
|
n->domain=curdomain; n->path="/";
|
|
if (*x) {
|
|
if (*x!=';') goto kaputt;
|
|
*x=0;
|
|
++x;
|
|
while (*x) {
|
|
char next;
|
|
while (*x==' ' || *x=='\t') ++x;
|
|
if (!(t=strchr(x,';'))) t=strchr(x,0);
|
|
next=*t; *t=0;
|
|
if (case_starts(x,"path=")) {
|
|
x+=sizeof("path");
|
|
n->path=x;
|
|
} else if (case_starts(x,"domain=")) {
|
|
x+=sizeof("domain");
|
|
n->domain=x;
|
|
}
|
|
if (!next) break;
|
|
x=t+1;
|
|
}
|
|
}
|
|
/* check if the domain is valid */
|
|
if (n->domain != curdomain) {
|
|
size_t i,a;
|
|
/* n->domain must be a suffic of curdomain or the other way around */
|
|
i=strlen(n->domain);
|
|
a=strlen(curdomain);
|
|
if (i<=a && case_diffs(n->domain,curdomain+a-i)) goto kaputt;
|
|
if (a<i && case_diffs(n->domain+i-a,curdomain)) goto kaputt;
|
|
/* can't set cookie for TLD */
|
|
for (i=a=0; n->domain[i]; ++i)
|
|
if (n->domain[i]=='.') ++a;
|
|
if (a<2) goto kaputt;
|
|
/* here we would have to check for cases like ".co.uk", but since
|
|
* this is just a trivial downloader without persistent cookies,
|
|
* I'll pass, knowingly breaking ".x.org" */
|
|
if (strlen(n->domain)<sizeof(".co.uk")) goto kaputt;
|
|
}
|
|
// printf("Cookie: \"%s\" = \"%s\", domain=\"%s\", path=\"%s\"\n",n->name,n->value,n->domain,n->path);
|
|
/* now see if the same cookie is already there */
|
|
{
|
|
struct cookie** c;
|
|
for (c=&cookies; *c; c=&((*c)->next)) {
|
|
if (!strcmp((*c)->name,n->name) && !strcmp((*c)->domain,n->domain)) {
|
|
(*c)->name=n->name;
|
|
(*c)->value=n->value;
|
|
(*c)->path=n->path;
|
|
(*c)->domain=n->domain;
|
|
free(n);
|
|
return;
|
|
}
|
|
}
|
|
*c=n;
|
|
n->next=0;
|
|
}
|
|
}
|
|
|
|
size_t fmt_cookies(char* dest,const char* domain,const char* path) {
|
|
struct cookie* c;
|
|
size_t sum=0;
|
|
size_t l=strlen(domain);
|
|
sum+=fmt_str(dest,"Cookie: ");
|
|
for (c=cookies; c; c=c->next) {
|
|
size_t k=strlen(c->domain);
|
|
if (l<k) continue;
|
|
if (case_equals(domain+l-k,c->domain) && case_starts(path,c->path))
|
|
sum+=fmt_strm(dest?dest+sum:0,c->name,"=",c->value,"; ");
|
|
}
|
|
if (sum>8) {
|
|
if (dest) {
|
|
dest[sum-2]='\r';
|
|
dest[sum-1]='\n';
|
|
}
|
|
return sum;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct utimbuf u;
|
|
|
|
char* location;
|
|
|
|
static int readanswer(int s,const char* filename,const char* curdomain,int onlyprintlocation,uint16_t port) {
|
|
char buf[8192];
|
|
int i,j,body=-1,r;
|
|
int64 d;
|
|
unsigned long httpcode;
|
|
unsigned long long rest;
|
|
int nocl;
|
|
i=0; d=-1; httpcode=0; todel=(char*)filename;
|
|
while ((r=read(s,buf+i,sizeof(buf)-i)) > 0) {
|
|
i+=r;
|
|
for (j=0; j+3<i; ++j) {
|
|
if (buf[j]=='\r' && buf[j+1]=='\n' && buf[j+2]=='\r' && buf[j+3]=='\n') {
|
|
unsigned long code;
|
|
body=j+4;
|
|
if (scan_ulong(buf+9,&code))
|
|
httpcode=code;
|
|
else
|
|
goto kaputt;
|
|
if (onlyprintlocation && (code/10 != 30)) return 0;
|
|
if (ims) {
|
|
/* some crappy web servers (*cough* dl.google.com *cough*) do
|
|
* not support If-Modified-Since, so do checking ourselves */
|
|
size_t i;
|
|
for (i=0; i+sizeof("Last-Modified: Fri, 22 Jan 2010 21:00:00")<j; ++i) {
|
|
if (case_starts(buf+i,"Last-Modified:")) {
|
|
i+=sizeof("Last-Modified:");
|
|
while (i<j && (buf[i]==' ' || buf[i]=='\t')) ++i;
|
|
if (buf[i+scan_httpdate(buf+i,&u.actime)]=='\r') {
|
|
if (u.actime<=ims) {
|
|
if (verbose)
|
|
buffer_putmflush(buffer_2,"File not modified (but server ignores If-Modified-Since), aborting download...\n");
|
|
close(d);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ((resumeofs && code==206 && io_appendfile(&d,filename)==0) ||
|
|
(!resumeofs && code==200 && ((strcmp(filename,""))?io_createfile(&d,filename)==0:((d=1)-1))))
|
|
panic("creat");
|
|
if (d==-1) {
|
|
if (httpcode==301 || httpcode==302 || httpcode==303) {
|
|
char* l=buf;
|
|
buf[r]=0;
|
|
/* extract cookies */
|
|
while ((l=strchr(l,'\n'))) {
|
|
++l;
|
|
if (case_starts(l,"set-cookie:"))
|
|
addcookie(l,curdomain);
|
|
}
|
|
/* extract and go to location */
|
|
if ((l=strstr(buf,"\nLocation:"))) {
|
|
l+=10;
|
|
while (*l == ' ' || *l == '\t') ++l;
|
|
location=l;
|
|
while (*l && *l != '\r' && *l != '\n') ++l;
|
|
*l=0;
|
|
if (*location=='/') {
|
|
char portbuf[FMT_ULONG];
|
|
l=location;
|
|
/* *sigh* relative redirect, take parts from old url */
|
|
location=malloc(l-location+strlen(curdomain)+100);
|
|
if (location) {
|
|
portbuf[fmt_ulong(portbuf,port)]=0;
|
|
location[fmt_strm(location,"http://",curdomain,":",portbuf,l)]=0;
|
|
}
|
|
} else
|
|
location=strndup(location,l-location);
|
|
return -2;
|
|
}
|
|
return -1;
|
|
}
|
|
for (j=0; buf[j]!='\n'; ++j) ;
|
|
write(2,buf,j+1);
|
|
return 0;
|
|
}
|
|
if (i-j-4)
|
|
if (write(d,buf+body,i-j-4)!=i-j-4) panic("write");
|
|
break;
|
|
}
|
|
}
|
|
if (body!=-1) {
|
|
if (byte_diff(buf,7,"HTTP/1.")) {
|
|
kaputt:
|
|
buffer_putsflush(buffer_2,"invalid HTTP response!\n");
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (r==-1) return -1;
|
|
if (d==1) dostats=!isatty(1);
|
|
if (httpcode!= (resumeofs?206:200)) return 0;
|
|
rest=-1; nocl=1;
|
|
buf[r]=0;
|
|
for (j=0; j<r; j+=str_chr(buf+j,'\n')) {
|
|
if (j+17<r && case_equalb(buf+j,17,"\nContent-Length: ")) {
|
|
char* c=buf+j+17;
|
|
if (c[scan_ulonglong(c,&rest)]!='\r') {
|
|
buffer_putsflush(buffer_2,"invalid Content-Length header!\n");
|
|
return -1;
|
|
}
|
|
nocl=0;
|
|
} else if (j+16<r && case_equalb(buf+j,16,"\nLast-Modified: ")) {
|
|
char* c=buf+j+16;
|
|
if (c[scan_httpdate(c,&u.actime)]!='\r') {
|
|
buffer_putsflush(buffer_2,"invalid Last-Modified header!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
++j;
|
|
}
|
|
total=rest;
|
|
rest-=(r-body);
|
|
printstats(total-rest,d);
|
|
while (nocl || rest) {
|
|
r=read(s,buf,nocl?sizeof(buf):(rest>sizeof(buf)?sizeof(buf):rest));
|
|
if (r<1) {
|
|
if (r==-1)
|
|
carp("read from HTTP socket");
|
|
else {
|
|
if (ignoreeof) nocl=1;
|
|
if (nocl) break;
|
|
buffer_puts(buffer_2,"early HTTP EOF; expected ");
|
|
buffer_putulong(buffer_2,rest);
|
|
buffer_putsflush(buffer_2," more bytes!\n");
|
|
return -1;
|
|
}
|
|
} else {
|
|
printstats(r,d);
|
|
if (write(d,buf,r)!=r)
|
|
panic("write");
|
|
rest-=r;
|
|
}
|
|
}
|
|
close(d);
|
|
chmod(filename,0644);
|
|
return 0;
|
|
}
|
|
|
|
static stralloc ftpresponse;
|
|
|
|
static int readftpresponse(buffer* b) {
|
|
char c;
|
|
int i,res,cont=0,num;
|
|
if (!stralloc_copys(&ftpresponse,"")) panic("malloc");
|
|
for (i=res=0; i<3; ++i) {
|
|
if (buffer_getc(b,&c)!=1) panic("ftp command response read error");
|
|
if (c<'0' || c>'9') panic("invalid ftp command response\n");
|
|
res=res*10+c-'0';
|
|
}
|
|
num=3;
|
|
for (i=3; ; ++i) {
|
|
if (buffer_getc(b,&c)!=1) panic("ftp command response read error");
|
|
if (!stralloc_append(&ftpresponse,&c)) panic("malloc");
|
|
if (i==0) {
|
|
cont=0; num=0;
|
|
if (c==' ' || c=='\t') cont=1;
|
|
}
|
|
if (i<3 && c>='0' && c<='9') ++num;
|
|
if (i==3 && num==3) cont=(c=='-');
|
|
if (c=='\n') {
|
|
if (cont) i=-1; else break;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int ftpcmd(int s,buffer* b,const char* cmd) {
|
|
int l=str_len(cmd);
|
|
if (write(s,cmd,l)!=l) panic("ftp command write error");
|
|
return readftpresponse(b);
|
|
}
|
|
|
|
static int ftpcmd2(int s,buffer* b,const char* cmd,const char* param) {
|
|
int l=str_len(cmd);
|
|
int l2=str_len(param);
|
|
#ifdef __MINGW32__
|
|
char* buf=alloca(l+l2+3);
|
|
memcpy(buf,cmd,l);
|
|
memcpy(buf+l,param,l2);
|
|
memcpy(buf+l+l2,"\r\n",2);
|
|
if (write(s,buf,l+l2+2)!=l+l2+2) panic("ftp command write error");
|
|
#else
|
|
struct iovec v[3];
|
|
v[0].iov_base=(char*)cmd; v[0].iov_len=l;
|
|
v[1].iov_base=(char*)param; v[1].iov_len=l2;
|
|
v[2].iov_base="\r\n"; v[2].iov_len=2;
|
|
if (writev(s,v,3)!=l+l2+2) panic("ftp command write error");
|
|
#endif
|
|
return readftpresponse(b);
|
|
}
|
|
|
|
static int scan_int2digit(const char* s, int* i) {
|
|
if (s[0]<'0' || s[0]>'9' || s[1]<'0' || s[1]>'9') return 0;
|
|
*i=(s[0]-'0')*10 + s[1]-'0';
|
|
return 2;
|
|
}
|
|
|
|
static inline int issafe(unsigned char c) {
|
|
return (c!='"' && c>' ' && c!='+');
|
|
}
|
|
|
|
size_t fmt_urlencoded(char* dest,const char* src,size_t len) {
|
|
register const unsigned char* s=(const unsigned char*) src;
|
|
unsigned long written=0,i;
|
|
for (i=0; i<len; ++i) {
|
|
if (!issafe(s[i])) {
|
|
if (dest) {
|
|
dest[written]='%';
|
|
dest[written+1]=fmt_tohex(s[i]>>4);
|
|
dest[written+2]=fmt_tohex(s[i]&15);
|
|
}
|
|
written+=3;
|
|
} else {
|
|
if (dest) dest[written]=s[i]; ++written;
|
|
}
|
|
}
|
|
return written;
|
|
}
|
|
|
|
static int validatesmb(char* buf,size_t wanted,unsigned char type,unsigned char wordcount,
|
|
unsigned short bytecount,unsigned short tid,unsigned short mid) {
|
|
if (wanted<wordcount*2+0x23+bytecount) return -1; // too short?
|
|
if (!byte_equal(buf,4,"\xffSMB")) return -1; // SMB magic?
|
|
if ((unsigned char)buf[4]!=type) return -1; // wrong message type?
|
|
if (uint16_read(buf+12)!=0) return -1; // process id high == 0?
|
|
if (uint16_read(buf+24)!=tid) return -1; // right tree id?
|
|
if (uint16_read(buf+26)!=23) return -1; // right process id?
|
|
if (uint16_read(buf+30)!=mid) return -1; // right multiplex id?
|
|
if (buf[0x20]<wordcount) return -1;
|
|
if (uint16_read(buf+0x20+wordcount*2)<bytecount) return -1;
|
|
if (wanted<wordcount*2+0x22+uint16_read(buf+0x21+wordcount*2)) return -1; // too short
|
|
return 0;
|
|
}
|
|
|
|
static void readnetbios(buffer* b,char* buf,size_t* wanted) {
|
|
if (buffer_get(b,buf,4)!=4) panic("short read\n");
|
|
*wanted=(unsigned char)buf[1] * 65535 +
|
|
(unsigned char)buf[2] * 256 +
|
|
(unsigned char)buf[3];
|
|
}
|
|
|
|
static int negotiatesocksconnection(int sock,const char* host,unsigned short port,char* sockname,unsigned short* socknameport) {
|
|
char buf[300];
|
|
size_t hl=strlen(host);
|
|
if (verbose)
|
|
buffer_putsflush(buffer_1,"SOCKS handshake... ");
|
|
/* version 5, 1 auth method, auth method none */
|
|
if (write(sock,"\x05\x01\x00",3)!=3 ||
|
|
read(sock,buf,2)!=2 ||
|
|
buf[0]!=5 || buf[1]!=0) {
|
|
buffer_putsflush(buffer_2,"dl: SOCKS handshake failed\n");
|
|
return -1;
|
|
}
|
|
if (verbose)
|
|
buffer_putsflush(buffer_1,"SOCKS connect... ");
|
|
/* version 5, command: connect (1), reserved (0), address type: domain name (3) */
|
|
memcpy(buf,"\x05\x01\x00\x03",4);
|
|
if (hl>255) {
|
|
buffer_putsflush(buffer_2,"dl: host name too long (SOCKS only supports up to 255)\n");
|
|
return -1;
|
|
}
|
|
buf[4]=hl;
|
|
memcpy(buf+5,host,hl);
|
|
uint16_pack_big(buf+5+hl,port);
|
|
if (write(sock,buf,5+hl+2)!=5+hl+2 ||
|
|
read(sock,buf,4)!=4 ||
|
|
buf[0]!=5 || buf[2]!=0) {
|
|
kaputt:
|
|
buffer_putsflush(buffer_2,"dl: received invalid reply to SOCKS connect request\n");
|
|
return -1;
|
|
}
|
|
switch (buf[1]) {
|
|
case 0: errno=0; break;
|
|
case 2: errno=EACCES; break;
|
|
case 3: errno=ENETUNREACH; break;
|
|
case 4: errno=EHOSTUNREACH; break;
|
|
case 5: errno=ECONNREFUSED; break;
|
|
case 6: errno=ETIMEDOUT; break;
|
|
default: errno=EINVAL;
|
|
}
|
|
if (errno) panic("SOCKS connect");
|
|
{
|
|
size_t r;
|
|
switch (buf[3]) {
|
|
case 1: r=6; break;
|
|
case 4: r=18; break;
|
|
default:
|
|
goto kaputt;
|
|
}
|
|
if (read(sock,buf+4,r)!=r) goto kaputt;
|
|
if (verbose) {
|
|
if (buf[3]==1) {
|
|
if (sockname) {
|
|
memcpy(sockname,V4mappedprefix,12);
|
|
memcpy(sockname+12,buf+4,4);
|
|
}
|
|
if (socknameport) *socknameport=uint16_read_big(buf+4+4);
|
|
buf[100+fmt_ip4(buf+100,buf+4)]=0;
|
|
buf[200+fmt_ulong(buf+200,uint16_read_big(buf+4+4))]=0;
|
|
} else if (buf[3]==4) {
|
|
if (sockname) memcpy(sockname,buf+4,16);
|
|
if (socknameport) *socknameport=uint16_read_big(buf+4+16);
|
|
buf[100+fmt_ip6(buf+100,buf+4)]=0;
|
|
buf[200+fmt_ulong(buf+200,uint16_read_big(buf+4+16))]=0;
|
|
}
|
|
buffer_putmflush(buffer_1,"success! Bound to ",buf+100," port ",buf+200,".\n");
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void fmt_num2(char *dest,int i) {
|
|
dest[0]=i/10+'0';
|
|
dest[1]=i%10+'0';
|
|
}
|
|
|
|
int main(int argc,char* argv[]) {
|
|
int useport=0;
|
|
int usev4=0;
|
|
int newer=0;
|
|
int resume=0;
|
|
int keepalive=0;
|
|
int imode=0;
|
|
int longlist=0;
|
|
int onlyprintlocation=0;
|
|
char ip[16];
|
|
uint16 port=80, proxyport=0, connport=0, socksport=1080;
|
|
uint32 scope_id=0;
|
|
stralloc ips={0};
|
|
int s;
|
|
char* request=0;
|
|
int rlen=0;
|
|
char* filename=0;
|
|
char* pathname=0;
|
|
char* output=0;
|
|
char* useragent="dl/1.0";
|
|
char* referer=0;
|
|
enum {HTTP, FTP, SMB} mode;
|
|
int skip;
|
|
buffer ftpbuf;
|
|
char* host,* proxyhost=0,* connhost=0;
|
|
char* socksproxyhost=0;
|
|
char externalsocksip[16];
|
|
unsigned short externalsocksport;
|
|
|
|
#if 0
|
|
addcookie("Set-cookie: RMID=0478b6d1254f4816a29724b0; expires=Wednesday, 29-Apr-2009 04:22:47 GMT; path=/; domain=.nytimes.com\r\n","www.nytimes.com");
|
|
addcookie("Set-cookie: NYT_GR=4816a747-xr4Bk90ylLV96+EIoKqc+A; path=/; domain=.nytimes.com","www.nytimes.com");
|
|
addcookie("Set-cookie: NYT-S=0MOZ7vC0h8ZsDDXrmvxADeHCpDcwlNkC5FdeFz9JchiAI6GpR90PNu0YV.Ynx4rkFI; path=/; domain=.nytimes.com","www.nytimes.com");
|
|
{
|
|
char buf[1024];
|
|
size_t l;
|
|
l=fmt_cookies(0,"www.nytimes.com","/2008/04/29/washington/29scotus.html?partner=rssnyt&emc=rss");
|
|
printf("l=%zu\n",l);
|
|
if (l<1024) {
|
|
buf[fmt_cookies(buf,"www.nytimes.com","/2008/04/29/washington/29scotus.html?partner=rssnyt&emc=rss")]=0;
|
|
printf("buf=\"%s\" (%zu)\n",buf,strlen(buf));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
dostats=isatty(2);
|
|
|
|
#ifndef __MINGW32__
|
|
signal(SIGPIPE,SIG_IGN);
|
|
#endif
|
|
|
|
for (;;) {
|
|
int c=getopt(argc,argv,"i:ko4nvra:O:U:R:lsLI");
|
|
if (c==-1) break;
|
|
switch (c) {
|
|
case 'k':
|
|
keepalive=1;
|
|
break;
|
|
case 'I':
|
|
ignoreeof=1;
|
|
break;
|
|
case 'n':
|
|
newer=1;
|
|
break;
|
|
case 'i':
|
|
{
|
|
struct stat ss;
|
|
if (stat(optarg,&ss)==0) {
|
|
ims=ss.st_mtime;
|
|
imode=1;
|
|
}
|
|
}
|
|
break;
|
|
case 'r':
|
|
resume=1;
|
|
break;
|
|
case 'o':
|
|
useport=1;
|
|
break;
|
|
case '4':
|
|
usev4=1;
|
|
break;
|
|
case 'v':
|
|
verbose=1;
|
|
break;
|
|
case 'O':
|
|
output=optarg;
|
|
break;
|
|
case 'U':
|
|
useragent=optarg;
|
|
break;
|
|
case 'R':
|
|
referer=optarg;
|
|
break;
|
|
case 'l':
|
|
onlyprintlocation=1;
|
|
break;
|
|
case 's':
|
|
dosync=1;
|
|
break;
|
|
case 'L':
|
|
longlist=1;
|
|
break;
|
|
case 'a':
|
|
#ifndef __MINGW32__
|
|
{
|
|
unsigned long n;
|
|
signal(SIGALRM,alarm_handler);
|
|
if (optarg[scan_ulong(optarg,&n)]==0)
|
|
alarm(n);
|
|
break;
|
|
}
|
|
#endif
|
|
case '?':
|
|
usage:
|
|
buffer_putsflush(buffer_2,"usage: dl [-i file] [-no4v] url\n"
|
|
" -i fn only fetch file if it is newer than fn\n"
|
|
" -n only fetch file if it is newer than local copy\n"
|
|
" -r resume\n"
|
|
" -4 use PORT and PASV instead of EPRT and EPSV, only connect using IPv4\n"
|
|
" -o use PORT and EPRT instead of PASV and EPSV\n"
|
|
" -a n abort after n seconds\n"
|
|
" -O fn write output to fn\n"
|
|
" -U s set User-Agent HTTP header\n"
|
|
" -R s set Referer HTTP header\n"
|
|
" -l just print value of Location: header\n"
|
|
" -L long ftp directory listing, not just names\n"
|
|
" -s sync after local write\n"
|
|
" -I do not treat early HTTP EOF as error\n"
|
|
" -v be verbose\n");
|
|
return 0;
|
|
}
|
|
}
|
|
#ifdef __MINGW32__
|
|
_fmode=O_BINARY;
|
|
#endif
|
|
|
|
if (!argv[optind]) goto usage;
|
|
again:
|
|
{
|
|
static int redirects=0;
|
|
if (++redirects>5) panic("too many redirects!\n");
|
|
}
|
|
|
|
{ // unescape url
|
|
size_t i,j;
|
|
i=scan_urlencoded2(argv[optind],argv[optind],&j);
|
|
if (argv[optind][i]) panic("invalid urlencoding in url!\n");
|
|
argv[optind][j]=0;
|
|
}
|
|
|
|
mode=HTTP;
|
|
if (byte_diff(argv[optind],skip=7,"http://")) {
|
|
if (byte_diff(argv[optind],skip=6,"ftp://")) {
|
|
if (byte_diff(argv[optind],skip=6,"smb://")) goto usage;
|
|
mode=SMB;
|
|
port=445;
|
|
} else {
|
|
mode=FTP;
|
|
proxyhost=getenv("ftp_proxy");
|
|
port=21;
|
|
}
|
|
} else
|
|
proxyhost=getenv("http_proxy");
|
|
socksproxyhost=getenv("SOCKS5_SERVER");
|
|
if (!socksproxyhost) socksproxyhost=getenv("SOCKS_SERVER");
|
|
if (socksproxyhost) {
|
|
char* c=strchr(socksproxyhost,':');
|
|
if (c) {
|
|
*c=0;
|
|
if (c[1+scan_ushort(c+1,&socksport)]) {
|
|
buffer_putsflush(buffer_2,"invalid socks proxy environment syntax\n");
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* do we have a proxy? */
|
|
if (proxyhost && !proxyport) {
|
|
size_t i;
|
|
/* expect format "http://localhost:3128" */
|
|
if (byte_equal(proxyhost,7,"http://")) proxyhost+=7;
|
|
i=str_chr(proxyhost,'/');
|
|
if (proxyhost[i]=='/') proxyhost[i]=0;
|
|
i=str_rchr(proxyhost,':');
|
|
if (proxyhost[i]!=':' ||
|
|
proxyhost[i+1+scan_ushort(proxyhost+i+1,&proxyport)]) {
|
|
buffer_putsflush(buffer_2,"invalid proxy environment syntax\n");
|
|
return 1;
|
|
}
|
|
proxyhost[i]=0;
|
|
connhost=proxyhost;
|
|
connport=proxyport;
|
|
mode=HTTP;
|
|
}
|
|
|
|
{
|
|
int colon;
|
|
int slash;
|
|
char* c;
|
|
host=argv[optind]+skip;
|
|
colon=str_chr(host,':');
|
|
slash=str_chr(host,'/');
|
|
if (host[0]=='[') { /* ipv6 IP notation */
|
|
int tmp;
|
|
++host;
|
|
--colon; --slash;
|
|
tmp=str_chr(host,']');
|
|
if (host[tmp]==']') host[tmp]=0;
|
|
if (host[tmp+1]==':') colon=tmp+1;
|
|
if (colon<tmp+1) colon=tmp+1+str_len(host+tmp+1);
|
|
}
|
|
if (colon<slash) {
|
|
host[colon]=0;
|
|
c=host+colon+1;
|
|
if (c[scan_ushort(c,&port)]!='/') goto usage;
|
|
*c=0;
|
|
}
|
|
// host[colon]=0;
|
|
c=host+slash;
|
|
pathname=c;
|
|
*c=0;
|
|
{
|
|
char* tmp=alloca(str_len(host)+1);
|
|
tmp[fmt_str(tmp,host)]=0;
|
|
host=tmp;
|
|
}
|
|
*c='/';
|
|
{
|
|
int tmp=str_chr(host,'%');
|
|
if (host[tmp]) {
|
|
host[tmp]=0;
|
|
scope_id=socket_getifidx(host+tmp+1);
|
|
if (scope_id==0) {
|
|
buffer_puts(buffer_2,"dl: warning: network interface ");
|
|
buffer_puts(buffer_2,host+tmp+1);
|
|
buffer_putsflush(buffer_2," not found.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!proxyhost) {
|
|
connhost=host;
|
|
connport=port;
|
|
}
|
|
|
|
{
|
|
struct addrinfo hints, *ai, *aitop;
|
|
int gaierr;
|
|
char p[FMT_ULONG];
|
|
const char* tolookup=socksproxyhost?socksproxyhost:connhost;
|
|
p[fmt_ulong(p,connport)]=0;
|
|
memset(&hints,0,sizeof(hints));
|
|
hints.ai_family=AF_UNSPEC;
|
|
hints.ai_flags=0;
|
|
hints.ai_socktype=0;
|
|
|
|
ips.len=0;
|
|
if ((gaierr=scan_ip6(tolookup,ip)) && tolookup[gaierr]==0) {
|
|
/* ip given, no dns needed */
|
|
stralloc_catb(&ips,ip,16);
|
|
goto nodns;
|
|
}
|
|
if (verbose) buffer_putsflush(buffer_1,"DNS lookup... ");
|
|
if ((gaierr = getaddrinfo(tolookup,p,&hints,&aitop)) != 0 || !aitop) {
|
|
buffer_puts(buffer_2,"dl: could not resolve IP: ");
|
|
buffer_puts(buffer_2,tolookup);
|
|
buffer_putnlflush(buffer_2);
|
|
return 1;
|
|
}
|
|
ai=aitop;
|
|
while (ai) {
|
|
uint32_t scopeid;
|
|
if (ai->ai_family==AF_INET6) {
|
|
char* addr;
|
|
stralloc_catb(&ips,addr=(char*)&(((struct sockaddr_in6*)ai->ai_addr)->sin6_addr),16);
|
|
scopeid=((struct sockaddr_in6*)ai->ai_addr)->sin6_scope_id;
|
|
if (scopeid==0 || byte_diff(addr,8,"\xfe\x80\x00\x00\x00\x00\x00\x00")) scopeid=scope_id;
|
|
} else {
|
|
stralloc_catb(&ips,V4mappedprefix,12);
|
|
stralloc_catb(&ips,(char*)&(((struct sockaddr_in*)ai->ai_addr)->sin_addr),4);
|
|
scopeid=0;
|
|
}
|
|
stralloc_catb(&ips,(char*)&scopeid,4);
|
|
ai=ai->ai_next;
|
|
}
|
|
if (verbose) buffer_putsflush(buffer_1,"done\n");
|
|
}
|
|
nodns:
|
|
|
|
if (output)
|
|
filename=strcmp(output,"-")?output:"";
|
|
else
|
|
filename=c+str_rchr(c,'/')+1;
|
|
if (resume || newer) {
|
|
struct stat ss;
|
|
if (stat(filename,&ss)==0) {
|
|
if (resume) {
|
|
resumeofs=ss.st_size;
|
|
if (verbose) {
|
|
buffer_puts(buffer_1,"Resuming from ");
|
|
buffer_putulonglong(buffer_1,resumeofs);
|
|
buffer_putsflush(buffer_1,"...\n");
|
|
}
|
|
} else if (newer) {
|
|
if (verbose) buffer_putsflush(buffer_1,"Found old file as If-Modified-Since reference.\n");
|
|
ims=ss.st_mtime;
|
|
}
|
|
} else
|
|
resume=0;
|
|
}
|
|
|
|
if (mode==HTTP) {
|
|
size_t cookielen=fmt_cookies(0,host,c);
|
|
size_t referlen=referer?str_len(referer)+20:0;
|
|
if (proxyhost) c=argv[optind];
|
|
request=malloc(300+str_len(host)+3*str_len(c)+str_len(useragent)+referlen+cookielen);
|
|
|
|
if (!request) panic("malloc");
|
|
{
|
|
int i;
|
|
if (onlyprintlocation)
|
|
i=fmt_str(request,"HEAD ");
|
|
else
|
|
i=fmt_str(request,"GET ");
|
|
i+=fmt_urlencoded(request+i,c,str_len(c));
|
|
i+=fmt_str(request+i," HTTP/1.0\r\nHost: ");
|
|
i+=fmt_str(request+i,host);
|
|
if (port!=80) {
|
|
i+=fmt_str(request+i,":");
|
|
i+=fmt_ulong(request+i,port);
|
|
}
|
|
if (ims) {
|
|
i+=fmt_str(request+i,"\r\nIf-Modified-Since: ");
|
|
i+=fmt_httpdate(request+i,ims);
|
|
}
|
|
if (resumeofs) {
|
|
i+=fmt_str(request+i,"\r\nRange: bytes=");
|
|
i+=fmt_ulonglong(request+i,resumeofs);
|
|
i+=fmt_str(request+i,"-");
|
|
}
|
|
i+=fmt_str(request+i,"\r\nAccept: */*\r\nUser-Agent: ");
|
|
i+=fmt_str(request+i,useragent);
|
|
if (referer) {
|
|
i+=fmt_str(request+i,"\r\nReferer: ");
|
|
i+=fmt_str(request+i,referer);
|
|
}
|
|
i+=fmt_str(request+i,"\r\nConnection: ");
|
|
i+=fmt_str(request+i,keepalive?"keep-alive":"close");
|
|
i+=fmt_str(request+i,"\r\n");
|
|
i+=fmt_cookies(request+i,host,c);
|
|
i+=fmt_str(request+i,"\r\n");
|
|
rlen=i; request[rlen]=0;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
int i;
|
|
s=-1;
|
|
for (i=0; i+20<=ips.len; i+=20) {
|
|
uint32_t scopeid;
|
|
if (usev4 && !ip6_isv4mapped(ips.s+i)) continue;
|
|
if (verbose) {
|
|
char buf[IP6_FMT];
|
|
buffer_puts(buffer_1,"connecting to ");
|
|
buffer_put(buffer_1,buf,fmt_ip6c(buf,ips.s+i));
|
|
buffer_puts(buffer_1," port ");
|
|
buffer_putulong(buffer_1,socksproxyhost?socksport:connport);
|
|
buffer_putnlflush(buffer_1);
|
|
}
|
|
byte_copy((char*)&scopeid,4,ips.s+i+16);
|
|
s=make_connection(ips.s+i,socksproxyhost?socksport:connport,scopeid);
|
|
if (s!=-1) {
|
|
byte_copy(ip,16,ips.s+i);
|
|
break;
|
|
}
|
|
}
|
|
if (s==-1)
|
|
return 1;
|
|
}
|
|
/* connected; if we are in socks mode, negotiate connection */
|
|
if (socksproxyhost)
|
|
if (negotiatesocksconnection(s,connhost,connport,externalsocksip,&externalsocksport))
|
|
return 1;
|
|
|
|
if (mode==HTTP) {
|
|
if (write(s,request,rlen)!=rlen) panic("write");
|
|
switch (readanswer(s,filename,host,onlyprintlocation,port)) {
|
|
case -1: exit(1);
|
|
case -2: free(referer);
|
|
referer=strdup(argv[optind]);
|
|
argv[optind]=location;
|
|
if (onlyprintlocation) {
|
|
buffer_puts(buffer_1,location);
|
|
buffer_putnlflush(buffer_1);
|
|
return 0;
|
|
}
|
|
if (verbose) {
|
|
buffer_puts(buffer_1,"redirected to ");
|
|
buffer_puts(buffer_1,location);
|
|
buffer_putsflush(buffer_1,"...\n");
|
|
}
|
|
location=0;
|
|
goto again;
|
|
}
|
|
|
|
} else if (mode==FTP) {
|
|
char ip3[16];
|
|
char buf[2048];
|
|
int i;
|
|
int srv=-1,dataconn=-1;
|
|
buffer_init(&ftpbuf,(void*)read,s,buf,sizeof buf);
|
|
if (verbose) buffer_putsflush(buffer_1,"Waiting for FTP greeting...");
|
|
if ((readftpresponse(&ftpbuf)/100)!=2) panic("no 2xx ftp greeting.\n");
|
|
if (verbose) buffer_putsflush(buffer_1,"\nUSER anonymous...");
|
|
if ((i=(ftpcmd(s,&ftpbuf,"USER anonymous\r\n")/100))>3) panic("ftp login failed.\n");
|
|
if (i!=2) {
|
|
if (verbose) buffer_putsflush(buffer_1,"\nPASS luser@...");
|
|
if ((i=(ftpcmd(s,&ftpbuf,"PASS luser@\r\n")/100))!=2) panic("ftp login failed.\n");
|
|
}
|
|
|
|
if (verbose) buffer_putsflush(buffer_1,"\nTYPE I");
|
|
if ((i=(ftpcmd(s,&ftpbuf,"TYPE I\r\n")/100))!=2) panic("Switching to binary mode failed.\n");
|
|
|
|
if (verbose) {
|
|
buffer_puts(buffer_1,"\nMDTM ");
|
|
buffer_puts(buffer_1,pathname);
|
|
buffer_putsflush(buffer_1,"... ");
|
|
}
|
|
if (ftpcmd2(s,&ftpbuf,"MDTM ",pathname)==213) {
|
|
char* c=ftpresponse.s+1;
|
|
struct tm t;
|
|
int ok=1;
|
|
if (ftpresponse.len>15) {
|
|
int i=0;
|
|
if (c[0]=='1' && c[1]=='9' && c[15]>='0') {
|
|
/* y2k bug; "19100" instead of "2000" */
|
|
if (scan_int2digit(c+3,&i)!=2) ok=0;
|
|
t.tm_year=i;
|
|
++c;
|
|
} else {
|
|
if (scan_int2digit(c,&i)!=2) ok=0;
|
|
t.tm_year=i*100;
|
|
if (scan_int2digit(c+2,&i)!=2) ok=0;
|
|
t.tm_year+=i;
|
|
t.tm_year-=1900;
|
|
}
|
|
c+=4;
|
|
if (scan_int2digit(c ,&i)!=2) ok=0; t.tm_mon=i-1;
|
|
if (scan_int2digit(c+2 ,&i)!=2) ok=0; t.tm_mday=i;
|
|
if (scan_int2digit(c+4 ,&i)!=2) ok=0; t.tm_hour=i;
|
|
if (scan_int2digit(c+6 ,&i)!=2) ok=0; t.tm_min=i;
|
|
if (scan_int2digit(c+8 ,&i)!=2) ok=0; t.tm_sec=i;
|
|
if (c[10]!='\r') ok=0;
|
|
if (ok) {
|
|
time_t r=mktime(&t);
|
|
u.actime=r;
|
|
if (verbose) buffer_putsflush(buffer_1,"ok.\n");
|
|
if (ims && r<=ims) {
|
|
if (verbose) buffer_puts(buffer_1,"Remote file is not newer, skipping download.");
|
|
goto skipdownload;
|
|
}
|
|
} else
|
|
if (verbose) buffer_putsflush(buffer_1,"could not parse MDTM response.\n");
|
|
} else
|
|
if (verbose) buffer_putsflush(buffer_1,"invalid response format.\n");
|
|
} else
|
|
if (verbose) buffer_putsflush(buffer_1,"failed.\n");
|
|
|
|
if (resume) {
|
|
char* buf=alloca(str_len(filename)+10);
|
|
int i;
|
|
i=fmt_str(buf,"REST ");
|
|
i+=fmt_ulonglong(buf+i,resumeofs);
|
|
i+=fmt_str(buf+i,"\r\n");
|
|
buf[i]=0; ++i;
|
|
if (verbose) {
|
|
buffer_put(buffer_1,buf,i-3);
|
|
buffer_putsflush(buffer_1,"... ");
|
|
}
|
|
if (ftpcmd(s,&ftpbuf,buf)!=350) {
|
|
buffer_putsflush(buffer_1,verbose?"FAILED!\n":"Resume failed!\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (useport) {
|
|
uint16 port;
|
|
char ip2[16];
|
|
char buf[200];
|
|
if (usev4) {
|
|
int i,j;
|
|
/* TODO: if (socksproxyhost) socks_bind_request (rfc1928) */
|
|
srv=socket_tcp4b();
|
|
if (srv==-1) panic("socket");
|
|
socket_listen(srv,1);
|
|
if (socket_local4(s,ip2,0)) panic("getsockname");
|
|
if (socket_local4(srv,0,&port)) panic("getsockname");
|
|
i=fmt_str(buf,"PORT ");
|
|
for (j=0; j<4; ++j) {
|
|
i+=fmt_uint(buf+i,ip2[j]&0xff);
|
|
i+=fmt_str(buf+i,",");
|
|
}
|
|
i+=fmt_uint(buf+i,port>>8);
|
|
i+=fmt_str(buf+i,",");
|
|
i+=fmt_uint(buf+i,port&0xff);
|
|
i+=fmt_str(buf+i,"\r\n");
|
|
buf[i]=0;
|
|
if (verbose) buffer_putsflush(buffer_1,buf);
|
|
if (ftpcmd(s,&ftpbuf,buf) != 200) panic("PORT reply is not 200\n");
|
|
} else {
|
|
int i;
|
|
/* TODO: if (socksproxyhost) socks_bind_request (rfc1928) */
|
|
srv=socket_tcp6b();
|
|
if (srv==-1) panic("socket");
|
|
socket_listen(srv,1);
|
|
if (socket_local6(s,ip2,0,0)) panic("getsockname");
|
|
if (socket_local6(srv,0,&port,0)) panic("getsockname");
|
|
i=fmt_str(buf,"EPRT |");
|
|
if (byte_equal(ip2,12,V4mappedprefix))
|
|
i+=fmt_str(buf+i,"1|");
|
|
else
|
|
i+=fmt_str(buf+i,"2|");
|
|
i+=fmt_ip6c(buf+i,ip2);
|
|
i+=fmt_str(buf+i,"|");
|
|
i+=fmt_ulong(buf+i,port);
|
|
i+=fmt_str(buf+i,"|\r\n");
|
|
buf[i]=0;
|
|
if (verbose) buffer_putsflush(buffer_1,buf);
|
|
if (ftpcmd(s,&ftpbuf,buf) != 200) panic("EPRT reply is not 200\n");
|
|
}
|
|
} else {
|
|
int srv;
|
|
tryv4:
|
|
if (usev4) {
|
|
int i;
|
|
if (verbose) buffer_putsflush(buffer_1,"PASV... ");
|
|
if (ftpcmd(s,&ftpbuf,"PASV\r\n")!=227) panic("PASV reply is not 227\n");
|
|
/* Passive Mode OK (127,0,0,1,204,228) */
|
|
for (i=0; i<ftpresponse.len-1; ++i) {
|
|
if (ftpresponse.s[i]==',' && ftpresponse.s[i+1]>='0' && ftpresponse.s[i+1]<='9') {
|
|
unsigned long j;
|
|
if (scan_ulong(ftpresponse.s+i+1,&j) && j<256)
|
|
port=port*256+j;
|
|
}
|
|
}
|
|
/* TODO: if (socksproxyhost) socks_connect (rfc1928) */
|
|
if ((srv=socket_tcp4b())==-1) panic("socket");
|
|
if (verbose) buffer_putsflush(buffer_1,"connecting... ");
|
|
if (socket_connect4(srv,ip+12,port)==-1) panic("connect");
|
|
if (verbose) buffer_putsflush(buffer_1,"done.\n");
|
|
dataconn=srv;
|
|
} else {
|
|
if (verbose) buffer_putsflush(buffer_1,"EPSV... ");
|
|
if (ftpcmd(s,&ftpbuf,"EPSV\r\n")!=229) {
|
|
usev4=1;
|
|
goto tryv4;
|
|
panic("EPSV reply is not 229\n");
|
|
}
|
|
/* Passive Mode OK (|||52470|) */
|
|
for (i=0; i<ftpresponse.len-1; ++i) {
|
|
if (ftpresponse.s[i]>='0' && ftpresponse.s[i]<='9') {
|
|
unsigned long j;
|
|
if (scan_ulong(ftpresponse.s+i,&j) && j<65536) {
|
|
port=j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* TODO: if (socksproxyhost) socks_connect (rfc1928) */
|
|
if ((srv=socket_tcp6b())==-1) panic("socket");
|
|
if (verbose) buffer_putsflush(buffer_1,"connecting... ");
|
|
if (socket_connect6(srv,ip,port,scope_id)==-1) panic("connect");
|
|
if (verbose) buffer_putsflush(buffer_1,"done.\n");
|
|
dataconn=srv;
|
|
}
|
|
}
|
|
if (!filename[0]) {
|
|
if (verbose) {
|
|
buffer_puts(buffer_1,"CWD ");
|
|
buffer_puts(buffer_1,pathname);
|
|
buffer_putsflush(buffer_1,"... ");
|
|
}
|
|
if ((ftpcmd2(s,&ftpbuf,"CWD ",pathname)/100)!=2) goto tryretr;
|
|
if (longlist) {
|
|
if (verbose) buffer_putsflush(buffer_2,"\nLIST\n");
|
|
if (((i=ftpcmd(s,&ftpbuf,"LIST\r\n"))!=150) && i!=125) panic("No 125/150 response to LIST\n");
|
|
} else {
|
|
if (verbose) buffer_putsflush(buffer_2,"\nNLST\n");
|
|
if (((i=ftpcmd(s,&ftpbuf,"NLST\r\n"))!=150) && i!=125) panic("No 125/150 response to NLST\n");
|
|
}
|
|
} else
|
|
tryretr:
|
|
{
|
|
int i;
|
|
if (verbose) {
|
|
buffer_puts(buffer_1,"RETR ");
|
|
buffer_puts(buffer_1,pathname);
|
|
buffer_putsflush(buffer_1,"... ");
|
|
}
|
|
if (((i=ftpcmd2(s,&ftpbuf,"RETR ",pathname))!=150) && i!=125) {
|
|
stralloc_0(&ftpresponse);
|
|
buffer_puts(buffer_2,"dl: RETR failed:");
|
|
buffer_putsaflush(buffer_2,&ftpresponse);
|
|
return 1;
|
|
}
|
|
if (verbose) buffer_putsflush(buffer_1,"ok. Downloading...\n");
|
|
total=0;
|
|
if (stralloc_0(&ftpresponse)) {
|
|
char* c=strchr(ftpresponse.s,'(');
|
|
if (c) {
|
|
++c;
|
|
if (!scan_ulonglong(c,&total))
|
|
total=0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if we were in active mode, accept connection now */
|
|
if (useport) {
|
|
if (usev4) {
|
|
if (verbose) buffer_putsflush(buffer_1,"Waiting for connection...");
|
|
dataconn=socket_accept4(srv,ip3,0);
|
|
if (verbose) buffer_putsflush(buffer_1," there it is.\n");
|
|
if (byte_diff(ip3,4,ip+12)) panic("PORT stealing attack!\n");
|
|
} else {
|
|
if (verbose) buffer_putsflush(buffer_1,"Waiting for connection...");
|
|
dataconn=socket_accept6(srv,ip3,0,0);
|
|
if (verbose) buffer_putsflush(buffer_1," there it is.\n");
|
|
if (byte_diff(ip3,16,ip)) panic("EPRT stealing attack!\n");
|
|
}
|
|
close(srv);
|
|
}
|
|
|
|
{
|
|
char buf[8192];
|
|
unsigned int l;
|
|
int64 d;
|
|
if (filename[0]) {
|
|
if ((resume?io_appendfile(&d,filename):io_createfile(&d,filename))==0)
|
|
panic("creat");
|
|
} else {
|
|
d=1;
|
|
dostats=!isatty(1);
|
|
}
|
|
while ((l=read(dataconn,buf,sizeof buf))>0) {
|
|
printstats(l,d);
|
|
if (d==1) {
|
|
unsigned int i,j;
|
|
for (i=j=0; i<l; ++i)
|
|
if (buf[i]!='\r') {
|
|
buf[j]=buf[i];
|
|
++j;
|
|
}
|
|
l=j;
|
|
}
|
|
if (write(d,buf,l) != l) panic("short write");
|
|
}
|
|
if (l==-1) panic("read");
|
|
if (d!=1) close(d);
|
|
}
|
|
close(dataconn);
|
|
if (verbose) buffer_putsflush(buffer_1,"Download finished... Waiting for server to acknowledge... ");
|
|
if ((readftpresponse(&ftpbuf)/100)!=2) panic("no 2xx ftp retr response.\n");
|
|
skipdownload:
|
|
if (verbose) buffer_putsflush(buffer_1,"\nQUIT\n");
|
|
ftpcmd(s,&ftpbuf,"QUIT\r\n");
|
|
|
|
} else if (mode==SMB) {
|
|
|
|
unsigned int mid=4;
|
|
char inbuf[65*1024];
|
|
char buf[8192];
|
|
char* readbuf;
|
|
char domain[200];
|
|
size_t dlen;
|
|
size_t wanted;
|
|
unsigned short uid,tid,fid;
|
|
size_t readsize;
|
|
unsigned long long filesize;
|
|
buffer ib=BUFFER_INIT(read,s,inbuf,sizeof(inbuf));
|
|
|
|
/* Step 1: Negotiate dialect. We only offer one */
|
|
if (verbose) buffer_putsflush(buffer_1,"Negotiating SMB dialect... ");
|
|
if (write(s,"\x00\x00\x00\x2f" // NetBIOS
|
|
"\xffSMB" // SMB
|
|
"\x72\x00\x00\x00\x00\x00\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x17\x00\x00\x00\x01\x00\x00\x0c"
|
|
"\x00\x02NT LM 0.12",0x2f+4)!=0x2f+4) panic("Protocol negotiation request short write\n");
|
|
|
|
readnetbios(&ib,buf,&wanted);
|
|
|
|
if (wanted>sizeof(buf)) panic("packet too large");
|
|
if (buffer_get(&ib,buf,wanted)!=wanted) panic("Protocol negotiation response short read\n");
|
|
if (validatesmb(buf,wanted,0x72,17,0,0,1)) panic("Received invalid SMB response\n");
|
|
if (uint16_read(buf+0x21)!=0) panic("Server requested invalid dialect\n");
|
|
|
|
{
|
|
char* x=buf+0x20+2*17;
|
|
char* max=x+3+uint16_read(x+1);
|
|
x+=3+(unsigned char)x[0];
|
|
if (max>x && max-x<sizeof(domain)) {
|
|
dlen=max-x; // we are opportunistic bastards
|
|
byte_copy(domain,dlen,x); // in session setup we claim to come from the server's workgroup
|
|
if (verbose) {
|
|
int i;
|
|
buffer_puts(buffer_1,"ok, got domain \"");
|
|
for (i=0; i<dlen; i+=2) {
|
|
if (domain[i+1] || !isprint(domain[i])) {
|
|
if (domain[i]==0) break;
|
|
buffer_put(buffer_1,".",1);
|
|
} else
|
|
buffer_put(buffer_1,domain+i,1);
|
|
}
|
|
buffer_putsflush(buffer_1,"\".\nSession Setup... ");
|
|
}
|
|
} else
|
|
dlen=0;
|
|
}
|
|
|
|
if ((buf[0x33]&0x40)==0x40)
|
|
readsize=64000;
|
|
else {
|
|
readsize=uint32_read(buf+0x27);
|
|
if (readsize>64000) readsize=64000;
|
|
}
|
|
readbuf=malloc(readsize+300);
|
|
if (!readbuf) panic("out of memory");
|
|
|
|
/* Step 2: Session Setup. */
|
|
{
|
|
char *x;
|
|
static char req[300]=
|
|
"\x00\x00\x00\x00" // NetBIOS
|
|
"\xffSMB" // SMB
|
|
"\x73\x00\x00\x00\x00\x00\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x17\x00\x00\x00\x02\x00\x0d\xff"
|
|
"\x00\x00\x00\xff\xff\x02\x00\x17\x00\x17"
|
|
"\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00"
|
|
"\x00\x5c\x00\x00\x00"
|
|
"\x00\x00" // byte count
|
|
"\x00\x00\x00"
|
|
"G\x00U\x00""E\x00S\x00T\x00\x00\x00"; // "GUEST"
|
|
size_t i;
|
|
x=req+8+50+5+2+3+6*2;
|
|
if (dlen) {
|
|
byte_copy(x,dlen,domain);
|
|
x+=dlen;
|
|
}
|
|
byte_copy(x,11,"U\x00n\x00i\x00x\x00\x00\x00\x00");
|
|
x+=11;
|
|
for (i=0; useragent[i]; ++i) {
|
|
*x++=useragent[i];
|
|
*x++=0;
|
|
}
|
|
x[0]=x[1]=x[2]=0;
|
|
x+=3;
|
|
uint32_pack_big(req,x-req-4);
|
|
{
|
|
char* y=req+8+50+5;
|
|
uint16_pack(y,x-y-2);
|
|
}
|
|
if (write(s,req,x-req) != x-req) panic("Session Setup request short write");
|
|
}
|
|
|
|
readnetbios(&ib,buf,&wanted);
|
|
if (wanted>sizeof(buf)) panic("packet too large");
|
|
if (buffer_get(&ib,buf,wanted)!=wanted) panic("Session Setup response short read\n");
|
|
if (validatesmb(buf,wanted,0x73,3,0,0,2)) panic("Received invalid SMB response\n");
|
|
uid=uint16_read(buf+0x1c);
|
|
|
|
if (verbose) {
|
|
char* x,*y, * max;
|
|
x=buf+0x20;
|
|
x+=1+(unsigned char)x[0]*2;
|
|
max=x+2+uint16_read(x);
|
|
buffer_puts(buffer_1,"ok");
|
|
x+=2;
|
|
if ((uintptr_t)x&1) ++x;
|
|
y=x;
|
|
while (y<max && *y) y+=2;
|
|
y+=2;
|
|
if (y<max) {
|
|
buffer_puts(buffer_1,", server \"");
|
|
while (y<max) {
|
|
if (y[1] || !isprint(y[0])) {
|
|
if (!y[0]) break;
|
|
buffer_put(buffer_1,".",1);
|
|
} else
|
|
buffer_put(buffer_1,y,1);
|
|
y+=2;
|
|
}
|
|
buffer_puts(buffer_1,"\" on \"");
|
|
while (x<max) {
|
|
if (x[1] || !isprint(x[0])) {
|
|
if (!x[0]) break;
|
|
buffer_put(buffer_1,".",1);
|
|
} else
|
|
buffer_put(buffer_1,x,1);
|
|
x+=2;
|
|
}
|
|
}
|
|
buffer_putsflush(buffer_1,"\".\nTree Connect... ");
|
|
}
|
|
|
|
/* Step 3: Tree Connect */
|
|
{
|
|
char *x;
|
|
char req[200+(strlen(host)+strlen(pathname))*2];
|
|
size_t i;
|
|
byte_copy(req,8+30+7+2+1,
|
|
"\x00\x00\x00\x00" // NetBIOS
|
|
"\xffSMB" // SMB
|
|
"\x75\x00\x00\x00\x00\x00\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x17\x00\x00\x00\x03\x00\x04\xff"
|
|
"\x00\x00\x00\x00\x00\x01\x00"
|
|
"\x00\x00" // byte count
|
|
"\x00");
|
|
x=req+8+30+7+2+1;
|
|
x[0]=x[2]='\\';
|
|
x[1]=x[3]=0;
|
|
x+=4;
|
|
for (i=0; host[i]; ++i) {
|
|
x[0]=host[i];
|
|
x[1]=0;
|
|
x+=2;
|
|
}
|
|
x[0]='\\'; x[1]=0; x+=2;
|
|
if (*pathname=='/' || *pathname=='\\') ++pathname;
|
|
for (i=0; pathname[i] && pathname[i]!='/' && pathname[i]!='\\'; ++i) {
|
|
x[0]=pathname[i];
|
|
x[1]=0;
|
|
x+=2;
|
|
}
|
|
byte_copy(x,8,"\x00\x00?????");
|
|
x+=8;
|
|
uint32_pack_big(req,x-req-4);
|
|
{
|
|
char* y=req+8+30+7;
|
|
uint16_pack(y,x-y-2);
|
|
}
|
|
uint16_pack(req+4+0x1c,uid);
|
|
if (write(s,req,x-req) != x-req) panic("Tree Connect request short write");
|
|
}
|
|
|
|
readnetbios(&ib,buf,&wanted);
|
|
if (wanted>sizeof(buf)) panic("packet too large");
|
|
if (buffer_get(&ib,buf,wanted)!=wanted) panic("Tree Connect response short read\n");
|
|
tid=uint16_read(buf+24);
|
|
if (validatesmb(buf,wanted,0x75,3,0,tid,3)) panic("Received invalid SMB response\n");
|
|
if (verbose) {
|
|
buffer_puts(buffer_1,"ok, tid=");
|
|
buffer_putulong(buffer_1,tid);
|
|
buffer_putsflush(buffer_1,".\nCreateFile... ");
|
|
}
|
|
|
|
/* Step 4: CreateFile */
|
|
{
|
|
char *x,*y;
|
|
char req[200+(strlen(pathname))*2];
|
|
byte_copy(req,8+80+2,
|
|
"\x00\x00\x00\x00" // NetBIOS
|
|
"\xffSMB" // SMB
|
|
"\xa2\x00\x00\x00\x00\x00\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x17\x00\x00\x00\x04\x00\x18\xff"
|
|
"\x00\x00\x00\x00\xFE\xFE\x10\x00\x00\x00"
|
|
"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x07\x00\x00\x00\x01\x00\x00\x00\x40\x00"
|
|
"\x00\x00\x01\x00\x00\x00\x01"
|
|
"\x00\x00" // byte count
|
|
"\x00\\\x00");
|
|
uint16_pack(req+4+24,tid);
|
|
uint16_pack(req+4+0x1c,uid);
|
|
x=req+8+80+2;
|
|
y=pathname;
|
|
|
|
while (*y=='/' || *y=='\\') ++y;
|
|
while (*y && *y!='/' && *y!='\\') ++y;
|
|
while (*y=='/' || *y=='\\') ++y;
|
|
|
|
uint16_pack(req+8+34,(strlen(y)+1)*2);
|
|
while (*y) {
|
|
x[0]=*y;
|
|
if (x[0]=='/') x[0]='\\';
|
|
x[1]=0;
|
|
x+=2;
|
|
++y;
|
|
}
|
|
uint32_pack_big(req,x-req-4);
|
|
{
|
|
char* y=req+8+77;
|
|
uint16_pack(y,x-y-2);
|
|
}
|
|
if (write(s,req,x-req) != x-req) panic("CreateFile request short write");
|
|
}
|
|
readnetbios(&ib,buf,&wanted);
|
|
if (wanted>sizeof(buf)) panic("packet too large");
|
|
if (buffer_get(&ib,buf,wanted)!=wanted) panic("CreateFile response short read\n");
|
|
if (validatesmb(buf,wanted,0xa2,34,0,tid,4)) panic("Received invalid SMB response\n");
|
|
fid=uint16_read(buf+0x20+6);
|
|
filesize=uint64_read(buf+0x58);
|
|
u.actime=(uint64_read(buf+0x44) / 10000000ll) - 11644473600ll;
|
|
if (verbose) {
|
|
char tbuf[30];
|
|
tbuf[fmt_httpdate(tbuf,u.actime)]=0;
|
|
buffer_putm(buffer_1,"ok, is a ",buf[0x20+68]==0?"file":"directory",", fid=");
|
|
buffer_putulong(buffer_1,fid);
|
|
buffer_puts(buffer_1,", size=");
|
|
buffer_putulonglong(buffer_1,filesize);
|
|
buffer_putmflush(buffer_1,", mtime=",tbuf,".\n");
|
|
}
|
|
|
|
if (buf[0x20+68]==1) {
|
|
// is a directory, do FindFirst/FindNext instead of ReadFile
|
|
|
|
time_t now;
|
|
char *x,*y;
|
|
char req[200+2048];
|
|
char* filename=0;
|
|
|
|
if (strlen(pathname)>1024) panic("file name too long\n");
|
|
|
|
now=time(0);
|
|
|
|
byte_copy(req,4+78,
|
|
"\x00\x00\x00\x58" // 0 NetBIOS
|
|
"\xffSMB" // 4+0 SMB
|
|
"\x32\x00\x00\x00\x00\x08\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00" // 4+24 Tree ID
|
|
"\x17\x00" // 4+26 Process ID
|
|
"\x00\x00" // 4+28 User ID
|
|
"\x00\x00" // 4+30 Multiplex ID
|
|
// Trans2 Request
|
|
"\x0f" // 4+32 Word Count (15)
|
|
"\x12\x00" // 4+33 Total Parameter Count (18)
|
|
"\x00\x00\x0a\x00\x38\x1f\x00\x00\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00"
|
|
"\x12\x00" // 4+51 Parameter Count (18)
|
|
"\x42\x00" // 4+53 Parameter Offset (66)
|
|
"\x00\x00" // 4+55 Data Count
|
|
"\x58\x00" // 4+57 Data Offset (88)
|
|
"\x01\x00"
|
|
"\x01\x00" // 4+61 FIND_FIRST2
|
|
// word count from 4+32 points here
|
|
"\x15\x00" // 4+63 Byte Count, starts counting here
|
|
"\x00" // Padding
|
|
// FIND_FIRST2 Parameters; 4+53 points here, parameter count from 4+33 and 4+51 starts counting here
|
|
"\x17\x00" // 4+66 search attributes: +hidden +system +directory +readonly
|
|
"\x56\x05" // 4+68 search count: 1366 (!?!?)
|
|
"\x06\x00" // 4+70 flags: return resume keys + close on eos
|
|
"\x04\x01" // 4+72 level of interest: find file both directory info (260)
|
|
"\x00\x00\x00\x00"); // 4+74 storage type
|
|
|
|
uint16_pack(req+4+24,tid);
|
|
uint16_pack(req+4+28,uid);
|
|
uint16_pack(req+4+30,++mid);
|
|
|
|
x = req + 4+78;
|
|
y = pathname;
|
|
|
|
{
|
|
uint32_t ch;
|
|
size_t i,pathlen;
|
|
|
|
y = pathname;
|
|
while (*y=='/' || *y=='\\') ++y;
|
|
while (*y && *y!='/' && *y!='\\') ++y;
|
|
while (*y=='/' || *y=='\\') ++y;
|
|
|
|
for (i=0; y[i]; ) {
|
|
size_t r;
|
|
r=scan_utf8(y+i,5,&ch);
|
|
if (r)
|
|
y+=r;
|
|
else {
|
|
ch=(unsigned char)y[i];
|
|
++y;
|
|
}
|
|
uint16_pack(x,ch);
|
|
x+=2;
|
|
}
|
|
if (ch!='\\') {
|
|
uint16_pack(x,'\\');
|
|
x+=2;
|
|
}
|
|
uint16_pack(x,'*');
|
|
x+=2;
|
|
uint16_pack(x,0);
|
|
x+=2;
|
|
pathlen=x-(req+4+78); // length in bytes
|
|
|
|
uint16_pack(req+4+63, 13+pathlen); // byte count
|
|
uint16_pack(req+4+33, 12+pathlen); // total parameter count
|
|
uint16_pack(req+4+51, 12+pathlen); // parameter count
|
|
uint16_pack(req+4+57, 76+pathlen); // data offset
|
|
uint32_pack_big(req,x-req-4);
|
|
if (write(s,req,x-req) != x-req) panic("FindFirst request short write");
|
|
}
|
|
|
|
for (;;) {
|
|
int end_of_search;
|
|
uint32_t fnlen=0;
|
|
uint16_t search_id=0;
|
|
|
|
readnetbios(&ib,buf,&wanted);
|
|
if (wanted>sizeof(buf)) panic("packet too large");
|
|
if (buffer_get(&ib,buf,wanted)!=wanted) panic(filename?"FindNext response short read\n":"FindFirst response short read\n");
|
|
if (validatesmb(buf,wanted,0x32,filename?8:10,0,tid,mid)) panic("Received invalid SMB response\n");
|
|
|
|
// Unfortunately, the reply does not say whether it is replying to a FIND_FIRST2 or a FIND_NEXT2
|
|
// So we look at the parameter count. For FIND_FIRST2 it is 10, for FIND_NEXT2 it is 8.
|
|
{
|
|
char* x=buf+0x20;
|
|
size_t pcount=uint16_read(x+1);
|
|
size_t pofs=uint16_read(x+9);
|
|
size_t dcount=uint16_read(x+13);
|
|
size_t dofs=uint16_read(x+15);
|
|
size_t bcount=uint16_read(x+21);
|
|
if (dofs+dcount>wanted || 0x20+21+bcount>wanted)
|
|
panic("SMB protocol violation: data count does not fit into packet\n");
|
|
if (pofs+pcount>dofs)
|
|
panic("SMB protocol violation: parameters overlap with data\n");
|
|
if (pcount != uint16_read(x+7))
|
|
panic("SMB protocol violation: parameter count != total parameter count\n");
|
|
if (dcount != uint16_read(x+3))
|
|
panic("SMB protocol violation: byte count != total data count\n");
|
|
if (pofs<56)
|
|
panic("SMB protocol violation: parameter offset too small\n");
|
|
if (buf[0x21]==10) {
|
|
search_id = uint16_read(buf+pofs);
|
|
end_of_search = uint16_read(buf+pofs+4);
|
|
} else
|
|
end_of_search = uint16_read(buf+pofs+2);
|
|
}
|
|
|
|
/* we got a superficially valid looking reply; dump all the file names */
|
|
{
|
|
char* x=buf+0x20;
|
|
char* last=buf+wanted;
|
|
size_t datacount = uint16_read(x+3);
|
|
x = buf+uint16_read(x+15);
|
|
if (x+datacount > last)
|
|
panic("SMB protocol violation: data + datacount > packet\n");
|
|
while (x+4<last) {
|
|
time_t mtime;
|
|
uint64_t filesize;
|
|
uint32_t attr;
|
|
uint32_t ofs=uint32_read(x);
|
|
if (ofs>datacount || ofs<(120-26) || x+ofs>last)
|
|
panic("SMB protocol violation: invalid ofs in filename record\n");
|
|
mtime=(uint64_read(x+24) / 10000000ll) - 11644473600ll;
|
|
filesize=uint64_read(x+40);
|
|
attr=uint32_read(x+56);
|
|
fnlen=uint32_read(x+60);
|
|
filename=x+94;
|
|
if (filename+fnlen > x+ofs)
|
|
panic("SMB protocol violation: invalid file name length in filename record\n");
|
|
{
|
|
char a[FMT_ULONG];
|
|
char b[100];
|
|
char buf[11];
|
|
size_t n;
|
|
struct tm* T;
|
|
static char *smonths[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
|
char* fn;
|
|
a[n=fmt_ulonglong(a,filesize)]=0;
|
|
b[fmt_pad(b,a,n,10,20)]=0;
|
|
T=localtime(&mtime);
|
|
memset(buf,' ',sizeof(buf));
|
|
fmt_num2(buf+1,T->tm_mday);
|
|
if (buf[1]=='0') buf[1]=' ';
|
|
if (mtime>now||now-mtime>60*60*24*365/2) {
|
|
fmt_num2(buf+5,(T->tm_year+1900)/100);
|
|
fmt_num2(buf+7,(T->tm_year+1900)%100);
|
|
} else {
|
|
fmt_num2(buf+4,T->tm_hour);
|
|
buf[6]=':';
|
|
fmt_num2(buf+7,T->tm_min);
|
|
}
|
|
buf[10]=0;
|
|
if (attr&0x10)
|
|
buffer_puts(buffer_1,"drwxr-xr-x");
|
|
else if (attr&1)
|
|
buffer_puts(buffer_1,"-r-xr-xr-x");
|
|
else
|
|
buffer_puts(buffer_1,"-rwxr-xr-x");
|
|
buffer_putm(buffer_1," 1 root root ",b," ",smonths[T->tm_mon],buf," ");
|
|
fn=filename;
|
|
y=fn+fnlen;
|
|
while (fn<y) {
|
|
uint32_t ch=uint16_read(fn);
|
|
buffer_put(buffer_1,b,fmt_utf8(b,ch));
|
|
fn+=2;
|
|
}
|
|
buffer_putnlflush(buffer_1);
|
|
}
|
|
x+=ofs;
|
|
}
|
|
}
|
|
|
|
/* now see if there is a continuation or not */
|
|
if (end_of_search) break;
|
|
|
|
/* there are more file names, we need to send a FIND_NEXT2 */
|
|
if (fnlen>2048) panic("filename too long\n");
|
|
|
|
byte_copy(req,4+78,
|
|
"\x00\x00\x00\x6c" // 0 NetBIOS
|
|
"\xffSMB" // 4+0 SMB
|
|
"\x32\x00\x00\x00\x00\x08\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00" // 4+24 Tree ID
|
|
"\x17\x00" // 4+26 Process ID
|
|
"\x00\x00" // 4+28 User ID
|
|
"\x00\x00" // 4+30 Multiplex ID
|
|
// Trans2 Request
|
|
"\x0f" // 4+32 Word Count (15)
|
|
"\x26\x00" // 4+33 Total Parameter Count (38)
|
|
"\x00\x00\x0a\x00\x38\x1f\x00\x00\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00"
|
|
"\x26\x00" // 4+51 Parameter Count (38)
|
|
"\x42\x00" // 4+53 Parameter Offset (66)
|
|
"\x00\x00" // 4+55 Data Count
|
|
"\x6c\x00" // 4+57 Data Offset (108)
|
|
"\x01\x00"
|
|
"\x02\x00" // 4+61 FIND_NEXT2
|
|
// word count from 4+32 points here
|
|
"\x2b\x00" // 4+63 Byte Count, starts counting here
|
|
"\x00" // Padding
|
|
// FIND_FIRST2 Parameters; 4+53 points here, parameter count from 4+33 and 4+51 starts counting here
|
|
"\x01\x00" // 4+66 search id (comes from FIND_FIRST2 response)
|
|
"\x56\x05" // 4+68 search count: 1366 (!?!?)
|
|
"\x04\x01" // 4+70 level of interest: find file both directory info (260)
|
|
"\x00\x00\x00\x00" // 4+72 resume key
|
|
"\x06\x00"); // 4+76 flags
|
|
|
|
uint16_pack(req+4+24,tid);
|
|
uint16_pack(req+4+28,uid);
|
|
uint16_pack(req+4+30,++mid);
|
|
uint16_pack(req+4+66,search_id);
|
|
|
|
x=req+4+78; byte_copy(x,fnlen,filename);
|
|
x+=fnlen; byte_copy(x,2,"\x00\x00");
|
|
x+=2;
|
|
|
|
uint16_pack(req+4+63, 13+fnlen+2); // byte count
|
|
uint16_pack(req+4+33, 12+fnlen+2); // total parameter count
|
|
uint16_pack(req+4+51, 12+fnlen+2); // parameter count
|
|
uint16_pack(req+4+57, 76+fnlen+2); // data offset
|
|
uint32_pack_big(req,x-req-4);
|
|
if (write(s,req,x-req) != x-req) panic("FindNext request short write");
|
|
|
|
}
|
|
|
|
goto closeanddone;
|
|
}
|
|
|
|
if (filesize<=resumeofs) {
|
|
if (verbose) buffer_putsflush(buffer_1,"File already fully transmitted.\n");
|
|
goto closeanddone;
|
|
}
|
|
if (ims && u.actime<=ims) {
|
|
if (verbose) buffer_putsflush(buffer_1,"The local file is as new as the remote file.\n");
|
|
goto closeanddone;
|
|
}
|
|
|
|
/* Step 5: ReadFile */
|
|
{
|
|
static char req[]=
|
|
"\x00\x00\x00\x3b" // NetBIOS
|
|
"\xffSMB" // SMB
|
|
"\x2e\x00\x00\x00\x00\x00\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x17\x00\x00\x00\x05\x00\x0c\xff"
|
|
"\x00\x00\x00w0u0__\x00"
|
|
"\xf0\x00\xf0\x00\x00\x00\x00\x00\xf0u"
|
|
"1__\x00\x00";
|
|
size_t rest;
|
|
size_t gotten;
|
|
unsigned long long curofs=resumeofs;
|
|
int nextwritten=0;
|
|
int64 d;
|
|
uint16_pack(req+4+0x1c,uid);
|
|
uint16_pack(req+4+24,tid);
|
|
uint16_pack(req+8+33,fid);
|
|
if (filename[0]) {
|
|
if ((resume?io_appendfile(&d,filename):io_createfile(&d,filename))==0)
|
|
panic("creat");
|
|
} else {
|
|
d=1;
|
|
dostats=!isatty(1);
|
|
}
|
|
total=filesize-resumeofs;
|
|
while (curofs<filesize) {
|
|
size_t dataofs;
|
|
|
|
uint16_pack(req+30+4,++mid);
|
|
uint32_pack(req+8+33+2,resumeofs&0xffffffff);
|
|
uint32_pack(req+8+49,resumeofs>>32);
|
|
rest=(filesize-curofs>readsize)?readsize:filesize-curofs;
|
|
uint16_pack(req+8+33+2+4,rest);
|
|
uint16_pack(req+8+33+2+6,rest);
|
|
uint16_pack(req+8+47,rest);
|
|
|
|
if (!nextwritten) {
|
|
if (write(s,req,0x3b+4)!=0x3b+4) panic("ReadFile request short write");
|
|
}
|
|
readnetbios(&ib,buf,&wanted);
|
|
if (wanted>readsize+300) panic("packet too large");
|
|
if (wanted<0x20+12*2+3) panic("SMB (ReadFile): Received invalid SMB response\n");
|
|
if (buffer_get(&ib,readbuf,0x20+12*2+3)!=0x20+12*2+3) panic("ReadFile response short read\n");
|
|
|
|
if (validatesmb(readbuf,wanted,0x2e,12,0,tid,mid)) panic("SMB (ReadFile): Received invalid SMB response\n");
|
|
gotten=uint16_read(readbuf+0x39);
|
|
dataofs=uint16_read(readbuf+0x2d);
|
|
if (dataofs+gotten>wanted) panic("invalid dataofs in ReadFile response");
|
|
if (gotten<rest) break; // someone truncated the file while we read?
|
|
|
|
/* pipeline next read request */
|
|
curofs+=gotten;
|
|
if (curofs<filesize) {
|
|
uint16_pack(req+30+4,mid+1);
|
|
uint32_pack(req+8+33+2,curofs&0xffffffff);
|
|
uint32_pack(req+8+49,curofs>>32);
|
|
rest=(filesize-curofs>readsize)?readsize:filesize-curofs;
|
|
uint16_pack(req+8+33+2+4,rest);
|
|
uint16_pack(req+8+33+2+6,rest);
|
|
uint16_pack(req+8+47,rest);
|
|
if (write(s,req,0x3b+4)!=0x3b+4) panic("ReadFile request short write");
|
|
nextwritten=1;
|
|
}
|
|
|
|
if (buffer_get(&ib,readbuf+0x20+12*2+3,wanted-(0x20+12*2+3))!=wanted-(0x20+12*2+3)) panic("ReadFile response short read\n");
|
|
if (write(d,readbuf+dataofs,gotten)!=gotten) panic("short write. disk full?\n");
|
|
printstats(gotten,d);
|
|
}
|
|
|
|
io_close(d);
|
|
}
|
|
|
|
closeanddone:
|
|
|
|
if (verbose) buffer_putsflush(buffer_1,"Close... ");
|
|
|
|
/* Step 6: Close */
|
|
{
|
|
static char req[]=
|
|
"\x00\x00\x00\x29" // NetBIOS
|
|
"\xffSMB" // SMB
|
|
"\x04\x00\x00\x00\x00\x00\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\xFE\xFE\x17\x00\x00\x00\x05\x00\x03\xFE"
|
|
"\xFE\xff\xff\xff\xff\x00\x00";
|
|
uint16_pack(req+30+4,++mid);
|
|
uint16_pack(req+8+29,fid);
|
|
uint16_pack(req+8+20,tid);
|
|
uint16_pack(req+4+0x1c,uid);
|
|
if (write(s,req,8+37)!=8+37) panic("Close request short write");
|
|
readnetbios(&ib,buf,&wanted);
|
|
if (wanted>sizeof(buf)) panic("packet too large");
|
|
if (buffer_get(&ib,buf,wanted)!=wanted) panic("Close response short read\n");
|
|
if (validatesmb(buf,wanted,0x04,0,0,tid,mid)) panic("SMB (CloseFile): Received invalid SMB response\n");
|
|
}
|
|
|
|
if (verbose) buffer_putsflush(buffer_1,"ok.\nTree Disconnect... ");
|
|
|
|
/* Step 7: Tree Disconnect */
|
|
{
|
|
static char req[]=
|
|
"\x00\x00\x00\x23" // NetBIOS
|
|
"\xffSMB" // SMB
|
|
"\x71\x00\x00\x00\x00\x00\x01\xc0\x00\x00"
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
"\x00\x00\x17\x00\x00\x00\x05\x00\x00\x00";
|
|
uint16_pack(req+30+4,++mid);
|
|
uint16_pack(req+8+33,fid);
|
|
uint16_pack(req+28,tid);
|
|
uint16_pack(req+4+0x1c,uid);
|
|
if (write(s,req,0x23+4)!=0x23+4) panic("Tree Disconnect request short write");
|
|
readnetbios(&ib,buf,&wanted);
|
|
if (wanted>sizeof(buf)) panic("packet too large");
|
|
if (buffer_get(&ib,buf,wanted)!=wanted) panic("Tree Disconnect response short read\n");
|
|
if (validatesmb(buf,wanted,0x71,0,0,tid,mid)) panic("SMB (Tree Disconnect): Received invalid SMB response\n");
|
|
}
|
|
if (verbose) buffer_putsflush(buffer_1,"ok.\n");
|
|
|
|
} else
|
|
panic("invalid mode\n");
|
|
close(s);
|
|
if (filename[0] && u.actime) {
|
|
u.modtime=u.actime;
|
|
if (strcmp(filename,"-") && utime(filename,&u)==-1)
|
|
if (errno!=ENOENT || !imode)
|
|
panic("utime");
|
|
}
|
|
clearstats();
|
|
return 0;
|
|
}
|