Rewrite main (again). Add Gopher support. Add TLS support.
This commit is contained in:
parent
dacd0b5794
commit
f27c613480
8
CHANGES
8
CHANGES
@ -1,3 +1,5 @@
|
||||
2022/11/24 - 0419
|
||||
*Initial commit.
|
||||
*Minor update to license file to clarify that we're under GPLv3.
|
||||
2021/11/29 - v0.1b
|
||||
Features:
|
||||
-Doesn't segfault. Probably.
|
||||
-Has TLS.
|
||||
-It's smaller and more readable than hurl. Nyaaah.
|
11
TODO
11
TODO
@ -1,11 +0,0 @@
|
||||
>put file on disk
|
||||
>get file from remote host
|
||||
>send properly-formed request to host
|
||||
>establish TLS connection (if necessary)
|
||||
>connect the socket
|
||||
>generate appropriate sockaddr_in structure
|
||||
>call getaddrinfo
|
||||
>initialize hints structures
|
||||
>create socket
|
||||
>create fd
|
||||
>parse URI [done]
|
20
src/Makefile
20
src/Makefile
@ -1,13 +1,17 @@
|
||||
.POSIX:
|
||||
|
||||
OBJ = \
|
||||
connect.o \
|
||||
http.o \
|
||||
support.o \
|
||||
uri.o
|
||||
LIBS = -ltls
|
||||
|
||||
fetch: $(OBJ)
|
||||
$(CC) $(LDFLAGS) $(CFLAGS) -o fetch $(OBJ) main.c $(LIBS)
|
||||
OBJ = \
|
||||
main.c \
|
||||
connect.c \
|
||||
http.c \
|
||||
gopher.c \
|
||||
support.c \
|
||||
uri.c
|
||||
|
||||
apport: $(OBJ)
|
||||
$(CC) $(LDFLAGS) $(CFLAGS) -o apport $(OBJ) $(LIBS)
|
||||
|
||||
clean:
|
||||
rm *.o fetch
|
||||
rm apport
|
||||
|
@ -1,42 +1,55 @@
|
||||
#include "headers.h"
|
||||
|
||||
int dial(const char *fqdn, const char *proto)
|
||||
int dial(const char *fqdn, const char *proto, struct tls **tlsres)
|
||||
{
|
||||
int sd;
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *ainfo;
|
||||
struct tls_config *tlshints;
|
||||
|
||||
|
||||
if( !(sd = socket(AF_INET, SOCK_STREAM, 0)))
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* using hints at all right now seems to break our connectivity... */
|
||||
/* POSIX demands that all fields of a hints struct are initialized */
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
/* only return IPv6/4 addresses if the system can handle them */
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
/* we want both IPv4 and IPv6 to be configured */
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
/* connecting via TCP stream */
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
/* using the default protocol for TCP over inet */
|
||||
/* seems to break fetch on BSD? */
|
||||
/* hints.ai_protocol = IPPROTO_TCP; */
|
||||
|
||||
|
||||
if(getaddrinfo(fqdn, proto, NULL, &ainfo))
|
||||
if(getaddrinfo(fqdn, proto, 0, &ainfo))
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(connect(sd, ainfo->ai_addr, sizeof(struct sockaddr)))
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
if(tlsres != 0)
|
||||
{
|
||||
close(sd);
|
||||
|
||||
if( 0 == (*tlsres = tls_client()))
|
||||
{
|
||||
goto err_ssl;
|
||||
}
|
||||
|
||||
if( 0 == (tlshints = tls_config_new()))
|
||||
{
|
||||
goto err_ssl;
|
||||
}
|
||||
|
||||
if(tls_configure(*tlsres, tlshints))
|
||||
{
|
||||
goto err_ssl;
|
||||
}
|
||||
|
||||
|
||||
if( (tls_connect(*tlsres, fqdn, proto)))
|
||||
{
|
||||
goto err_ssl;
|
||||
}
|
||||
}
|
||||
|
||||
return(sd);
|
||||
|
||||
err_ssl:
|
||||
return(0);
|
||||
}
|
||||
|
@ -8,4 +8,4 @@
|
||||
/* sd -- successful connection returns a file descriptor connected to the fqdn */
|
||||
/* ERRCONN -- couldn't connect */
|
||||
/* ERRADDR -- couldn't get addrinfo */
|
||||
int dial(const char *fqdn, const char *proto);
|
||||
int dial(const char *fqdn, const char *proto, struct tls **tls_res);
|
||||
|
25
src/gopher.c
Normal file
25
src/gopher.c
Normal file
@ -0,0 +1,25 @@
|
||||
#include "headers.h"
|
||||
|
||||
const char REQ_GOPH[] =
|
||||
{
|
||||
"%s\n"
|
||||
};
|
||||
|
||||
|
||||
int reqgen_gopher(const char *path, char **nbuf)
|
||||
{
|
||||
int buflen;
|
||||
|
||||
buflen = (strlen(REQ_GOPH) + strlen(path) + 1);
|
||||
|
||||
if( !(*nbuf = malloc(buflen)))
|
||||
{
|
||||
return(ERRMEM);
|
||||
}
|
||||
memset(*nbuf, 0, buflen);
|
||||
|
||||
sprintf(*nbuf, REQ_GOPH, path);
|
||||
|
||||
|
||||
return(0);
|
||||
}
|
1
src/gopher.h
Normal file
1
src/gopher.h
Normal file
@ -0,0 +1 @@
|
||||
int reqgen_goph(const char *path, char **nbuf);
|
@ -11,11 +11,19 @@
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <tls.h>
|
||||
|
||||
/* routines and variables used globally */
|
||||
#include "support.h"
|
||||
|
||||
/* URI handling routines */
|
||||
#include "uri.h"
|
||||
|
||||
/* generic TCP/IP networking functions */
|
||||
#include "connect.h"
|
||||
|
||||
/* HTTP 1.0 handling */
|
||||
#include "http.h"
|
||||
|
||||
/* Gopher handling */
|
||||
#include "gopher.h"
|
||||
|
@ -12,13 +12,14 @@ int reqgen_http(const char *path, const char *fqdn, char **nbuf)
|
||||
|
||||
buflen = (strlen(REQ_HTTP) + strlen(path) + strlen(fqdn) + 1);
|
||||
|
||||
if( !(*nbuf = malloc(buflen)))
|
||||
if( !(*nbuf = malloc(buflen)))
|
||||
{
|
||||
return(ERRMEM);
|
||||
}
|
||||
memset(*nbuf, 0, buflen);
|
||||
memset(*nbuf, 0, buflen);
|
||||
|
||||
sprintf(*nbuf, REQ_HTTP, path, fqdn);
|
||||
|
||||
sprintf(*nbuf, REQ_HTTP, path, fqdn);
|
||||
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
int reqgen_http(const char *path, const char *fqdn, char **nbuf);int http_content_length(char *data);
|
||||
char *http_skip_header(char *data, int recvlen);
|
||||
char *http_skip_header(char *data, int recvlen);
|
||||
|
307
src/main.c
307
src/main.c
@ -11,7 +11,8 @@ enum
|
||||
/* note that if -q is passed explicitly, it will irrevocably override -v and -b */
|
||||
Q,
|
||||
/* increase the verbosity level. higher levels imply you want the content lower levels would provide */
|
||||
/* all verbose output is sent to stderr for ease of capture */
|
||||
/* all verbose output
|
||||
is sent to stderr for ease of capture */
|
||||
/* 0: no informational, statistical or debug output */
|
||||
/* 1: informational output (Sending request FOO to host BAR, Received response header BAZ, etc) */
|
||||
/* 2: statistical. informational+size of header and body in bytes, round-trip time, etc */
|
||||
@ -20,144 +21,180 @@ enum
|
||||
};
|
||||
|
||||
|
||||
char param[4] = {0};
|
||||
/* below is to accompany param[O] */
|
||||
char *outpath;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i, translen, gotheader;
|
||||
int sockd;
|
||||
char recvbuf[BUFSIZ+1] = {0};
|
||||
char *sendbufp, *offsetp, *errstr;
|
||||
FILE *filed;
|
||||
struct tls *tlsc;
|
||||
uri uristruct;
|
||||
|
||||
i = translen = gotheader = sockd = 0;
|
||||
sendbufp = offsetp = 0;
|
||||
filed = 0;
|
||||
|
||||
|
||||
for(i = 0; (i = getopt(argc, argv, "bo:qv")) != -1; i = 0)
|
||||
{
|
||||
int i, sockd, recvlen, recv_header;
|
||||
char recvbuf[BUFSIZ] = {0};
|
||||
char param[4] = {0};
|
||||
char *outpath, *sendbufp, *offsetp;
|
||||
FILE *filed;
|
||||
uri uristruct;
|
||||
switch(i)
|
||||
{
|
||||
case 'b':
|
||||
param[B] = 1;
|
||||
break;
|
||||
|
||||
outpath = sendbufp = offsetp = 0;
|
||||
i = sockd = recvlen = recv_header = 0;
|
||||
filed = 0;
|
||||
case 'o':
|
||||
if(!param[Q])
|
||||
{
|
||||
param[O] = 1;
|
||||
}
|
||||
outpath = optarg;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
param[Q] = 1;
|
||||
param[B] = 0;
|
||||
param[V] = 0;
|
||||
break;
|
||||
|
||||
if(argc < 2)
|
||||
{
|
||||
fprintf(stderr, "%s: need args\n", argv[0]);
|
||||
goto err;
|
||||
}
|
||||
case 'v':
|
||||
/* we handle v differently because we want to support different levels of verbosity */
|
||||
if(!param[Q])
|
||||
{
|
||||
param[V]++;
|
||||
}
|
||||
break;
|
||||
|
||||
for(i = 0; (i = getopt(argc, argv, "bo:qv")) != -1; i = 0)
|
||||
{
|
||||
switch(i)
|
||||
{
|
||||
case 'b':
|
||||
param[B] = 1;
|
||||
break;
|
||||
case 'o':
|
||||
if(!param[Q])
|
||||
{
|
||||
param[O] = 1;
|
||||
}
|
||||
outpath = optarg;
|
||||
break;
|
||||
case 'q':
|
||||
param[Q] = 1;
|
||||
param[B] = 0;
|
||||
param[V] = 0;
|
||||
break;
|
||||
case 'v':
|
||||
/* we handle v differently because we want to support different levels of verbosity */
|
||||
if(!param[Q])
|
||||
{
|
||||
param[V]++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
errno = ERRUSAGE;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(uri_parse(argv[optind], &uristruct))
|
||||
{
|
||||
fprintf(stderr, "%s: invalid URI.\n", argv[0]);
|
||||
goto err;
|
||||
}
|
||||
if(!strlen(uristruct.path))
|
||||
{
|
||||
uristruct.path = "/";
|
||||
}
|
||||
|
||||
if(!outpath && !param[O])
|
||||
{
|
||||
if(strlen(uristruct.path) == 0 || !strcmp(uristruct.path, "/"))
|
||||
{
|
||||
outpath = "default";
|
||||
}
|
||||
else
|
||||
{
|
||||
outpath = (1 + strrchr(uristruct.path, '/'));
|
||||
}
|
||||
}
|
||||
|
||||
sendbufp = reqgen(&uristruct);
|
||||
|
||||
if(!(filed = fopen(outpath, "w")))
|
||||
{
|
||||
printf("filed err\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if( !(sockd = dial(uristruct.fqdn, uristruct.proto)))
|
||||
{
|
||||
printf("%s: couldn't connect to host %s.\n", argv[0], uristruct.fqdn);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(!send(sockd, sendbufp, strlen(sendbufp), 0))
|
||||
{
|
||||
printf("send err\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
printf("%s\n", sendbufp);
|
||||
|
||||
for(recv_header = 0, recvlen =1; sockd; memset(recvbuf, 0, BUFSIZ))
|
||||
{
|
||||
recvlen = recv(sockd, recvbuf, BUFSIZ, 0);
|
||||
switch(recvlen)
|
||||
{
|
||||
case -1:
|
||||
goto err;
|
||||
|
||||
case 0:
|
||||
close(sockd);
|
||||
sockd = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(!recv_header)
|
||||
{
|
||||
offsetp = (strstr(recvbuf, "\r\n\r\n") + 4);
|
||||
i = (int) (offsetp - recvbuf);
|
||||
|
||||
recv_header = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
offsetp = recvbuf;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if(param[V] >= 2)
|
||||
{
|
||||
printf("%s: received %d bytes...\n", argv[0], recvlen);
|
||||
}
|
||||
|
||||
fwrite(offsetp, sizeof(char), recvlen-i, filed);
|
||||
}
|
||||
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
err:
|
||||
exit(EXIT_FAILURE);
|
||||
default:
|
||||
errstr = "apport: uri";
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* we should probably modify uri_parse to return a zero value on failure... */
|
||||
if(uri_parse(argv[optind], &uristruct))
|
||||
{
|
||||
errstr = "invalid URI.";
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* if outpath isn't set because we haven't received a -o param, */
|
||||
/* then we should set the outpath to the final component of the */
|
||||
/* URI. */
|
||||
/* If we can't do that, we should just set it to 'default'. */
|
||||
if(outpath == NULL && !strlen((outpath = (1 + strrchr(uristruct.path, '/')))))
|
||||
{
|
||||
outpath = "default";
|
||||
}
|
||||
|
||||
/* generate our request */
|
||||
if( !(sendbufp = reqgen(&uristruct)))
|
||||
{
|
||||
errstr = "Unknown protocol.";
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(param[V])
|
||||
{
|
||||
fprintf(stderr, "request: %s\n", sendbufp);
|
||||
}
|
||||
|
||||
/* having a routine or a global variable to track whether we need TLS would be */
|
||||
/* nice to add in the future */
|
||||
/* Note: at the moment, if the TLS pointer passed is non-NULL, */
|
||||
/* dial's return code can only be treated as an indicator of success. */
|
||||
|
||||
if(!strcmp(uristruct.proto, "https"))
|
||||
{
|
||||
if(! (sockd = dial(uristruct.fqdn, uristruct.proto, &tlsc)))
|
||||
{
|
||||
errstr = "Couldn't connect to host using the specified protocol.";
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(! (sockd = dial(uristruct.fqdn, uristruct.proto, NULL)))
|
||||
{
|
||||
errstr = "Couldn't connect to host using the specified protocol.";
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if(! (filed = fopen(outpath, "w")))
|
||||
{
|
||||
errstr = "Couldn't open file.";
|
||||
goto err;
|
||||
}
|
||||
|
||||
|
||||
if(!strcmp("https", uristruct.proto))
|
||||
{
|
||||
tls_write(tlsc, sendbufp, strlen(sendbufp));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(! (translen = send(sockd, sendbufp, strlen(sendbufp), 0)))
|
||||
{
|
||||
errstr = "Couldn't transmit request.";
|
||||
goto err;
|
||||
}
|
||||
else if(param[V])
|
||||
{
|
||||
fprintf(stderr, "sent: %d bytes\n", translen);
|
||||
}
|
||||
}
|
||||
|
||||
for(gotheader = 0, translen = 1; translen; memset(recvbuf, 0, BUFSIZ+1))
|
||||
{
|
||||
/* using an abstraction over tls_read/write and send/recv would eliminate */
|
||||
/* a lot of redundant code here. */
|
||||
if(!strcmp("https", uristruct.proto))
|
||||
{
|
||||
translen = tls_read(tlsc, recvbuf, BUFSIZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
translen = recv(sockd, recvbuf, BUFSIZ, 0);
|
||||
}
|
||||
|
||||
if(!translen)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(param[V])
|
||||
{
|
||||
printf("recv: %d bytes\n", translen);
|
||||
}
|
||||
|
||||
if(!gotheader)
|
||||
{
|
||||
offsetp = (strstr(recvbuf, "\r\n\r\n") + 4);
|
||||
i = (int) (offsetp - recvbuf);
|
||||
gotheader = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
offsetp = recvbuf;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
fwrite(offsetp, sizeof(char), translen-i, filed);
|
||||
}
|
||||
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
|
||||
err:
|
||||
fprintf(stderr, "%s: %s\n", argv[0], errstr);
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
}
|
||||
|
@ -4,11 +4,23 @@
|
||||
char *reqgen(uri *urip)
|
||||
{
|
||||
char *req;
|
||||
int is_tls = 0;
|
||||
|
||||
if(!strcmp("http", urip->proto))
|
||||
if(!strcmp("http", urip->proto) || !strcmp("https", urip->proto))
|
||||
{
|
||||
reqgen_http(urip->path, urip->fqdn, &req);
|
||||
|
||||
if(!req)
|
||||
{
|
||||
return(NULL);
|
||||
}
|
||||
|
||||
return(req);
|
||||
}
|
||||
else if(!strcmp(urip->proto, "gopher"))
|
||||
{
|
||||
reqgen_gopher(urip->path, &req);
|
||||
|
||||
if(!req)
|
||||
{
|
||||
return(NULL);
|
||||
@ -22,7 +34,7 @@ char *reqgen(uri *urip)
|
||||
|
||||
void throw(int errcode)
|
||||
{
|
||||
fprintf(stderr, "fetch: error %d.\n", errcode);
|
||||
fprintf(stderr, "apport: error %d.\n", errcode);
|
||||
exit(errcode);
|
||||
}
|
||||
|
||||
@ -45,4 +57,4 @@ char *substr_extract(const char *str, int start, int end)
|
||||
memcpy(substr, str+start, substr_len);
|
||||
|
||||
return(substr);
|
||||
}
|
||||
}
|
||||
|
16
src/uri.c
16
src/uri.c
@ -38,14 +38,18 @@ int uri_parse(const char *uristr, uri *res)
|
||||
res->proto = substr_extract(uristr, match[PROTO].rm_so, match[PROTO].rm_eo);
|
||||
res->fqdn = substr_extract(uristr, match[FQDN].rm_so, match[FQDN].rm_eo);
|
||||
|
||||
/* if (match[3].rm_eo - match[3].rm_so) is 1, we need to set it to 0 */
|
||||
/* all it will contain in a valid uri is a '/', which should be */
|
||||
/* handled the same as nothing. */
|
||||
if((match[PATH].rm_eo - match[PATH].rm_so) == 1)
|
||||
/* if the difference below is less than 1, our path doesn't exist. */
|
||||
/* Compensate by setting it to '/' which will always return a root */
|
||||
/* document from an HTTP server -- and, presumably, others. We'll */
|
||||
/* see, I suppose. */
|
||||
if((match[PATH].rm_eo - match[PATH].rm_so) < 1)
|
||||
{
|
||||
match[PATH].rm_eo = match[PATH].rm_so;
|
||||
res->path = "/";
|
||||
}
|
||||
res->path = substr_extract(uristr, match[PATH].rm_so, match[PATH].rm_eo);
|
||||
else
|
||||
{
|
||||
res->path = substr_extract(uristr, match[PATH].rm_so, match[PATH].rm_eo);
|
||||
}
|
||||
|
||||
|
||||
return(0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user