Created
May 21, 2015 02:28
-
-
Save pikhq/6bdf06310c09223434ac to your computer and use it in GitHub Desktop.
Naive http client -- does SSL, and does not much else.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _POSIX_C_SOURCE 200809L | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <netdb.h> | |
#include <string.h> | |
#include <stdio.h> | |
#include <errno.h> | |
#include <unistd.h> | |
#include <netinet/in.h> | |
#include <limits.h> | |
#include <openssl/ssl.h> | |
#include <openssl/err.h> | |
struct uri { | |
char *scheme; | |
char *user; | |
char *pass; | |
char *host; | |
char *port; | |
char *path; | |
char *query; | |
char *fragment; | |
}; | |
enum { | |
RAW_SOCK, | |
SSL_SOCK | |
}; | |
struct sock { | |
int type; | |
union { | |
int fd; | |
SSL *ssl; | |
} conn; | |
SSL_CTX *ssl_ctx; | |
}; | |
int parse_uri(char *uri, struct uri *p) { | |
char *tmp; | |
memset(p, 0, sizeof(*p)); | |
tmp = strchr(uri, ':'); | |
if(!tmp) return 1; | |
*tmp = 0; | |
p->scheme = uri; | |
uri = tmp+1; | |
if(*uri == '/' && uri[1] == '/') { | |
uri += 2; | |
tmp = strchr(uri, '@'); | |
if(tmp) { | |
*tmp = 0; | |
p->user = uri; | |
p->pass = strchr(uri, ':'); | |
if(p->pass) { | |
*(p->pass) = 0; | |
p->pass++; | |
} | |
uri = tmp+1; | |
} | |
if(*uri == '[') { | |
uri++; | |
tmp = strchr(uri, ']'); | |
if(tmp[1] != ':' && tmp[1] != '/') return 1; | |
} else { | |
tmp = strpbrk(uri, "/:"); | |
} | |
p->host = uri; | |
if(!tmp) return 0; | |
uri = tmp + 1; | |
if(*tmp == ':') { | |
*tmp = 0; | |
p->port = uri; | |
tmp = strchr(uri, '/'); | |
if(!tmp) return 0; | |
uri = tmp + 1; | |
} | |
*tmp = 0; | |
} | |
p->path = uri; | |
tmp = strpbrk(uri, "?#"); | |
if(!tmp) return 0; | |
if(*tmp == '?') { | |
*tmp = 0; | |
uri = tmp + 1; | |
p->query = uri; | |
tmp = strchr(uri, '#'); | |
} | |
if(tmp) { | |
*tmp = 0; | |
uri = tmp + 1; | |
p->fragment = uri; | |
} | |
return 0; | |
} | |
static int open_sock(char *host, char *port, const char **msg) | |
{ | |
struct addrinfo hints; | |
struct addrinfo *info; | |
struct addrinfo *p; | |
int status, fd; | |
memset(&hints, 0, sizeof hints); | |
hints.ai_family = AF_UNSPEC; | |
hints.ai_socktype = SOCK_STREAM; | |
hints.ai_flags = AI_PASSIVE; | |
if((status = getaddrinfo(host, port, &hints, &info)) != 0) { | |
*msg = gai_strerror(status); | |
return -1; | |
} | |
for(p = info; p; p = p->ai_next) { | |
fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); | |
if(fd == -1) continue; | |
if(connect(fd, p->ai_addr, p->ai_addrlen) == -1) { | |
close(fd); | |
fd = -1; | |
continue; | |
} | |
break; | |
} | |
if(fd == -1) { | |
*msg = strerror(errno); | |
return -1; | |
} | |
freeaddrinfo(info); | |
return fd; | |
} | |
static int open_conn(char *scheme, char *host, char *port, struct sock *sock, const char **msg) | |
{ | |
int fd; | |
fd = open_sock(host, port, msg); | |
if(fd == -1) return fd; | |
if(strcmp(scheme, "http") == 0) { | |
sock->type = RAW_SOCK; | |
sock->conn.fd = fd; | |
return 0; | |
} else if(strcmp(scheme, "https") != 0) { | |
*msg = "Only the http and https schemes are supported.\n"; | |
return -1; | |
} | |
sock->type = SSL_SOCK; | |
sock->ssl_ctx = SSL_CTX_new(SSLv23_client_method()); | |
if(!sock->ssl_ctx) { | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
goto clean_fd; | |
} | |
if(!SSL_CTX_set_cipher_list(sock->ssl_ctx, "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:" | |
"DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:" | |
"RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!EXPORT")) { | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
goto clean_sslctx; | |
} | |
if(!SSL_CTX_set_default_verify_paths(sock->ssl_ctx)) { | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
goto clean_sslctx; | |
} | |
SSL_CTX_set_options(sock->ssl_ctx, SSL_OP_NO_SSLv2); | |
SSL_CTX_set_options(sock->ssl_ctx, SSL_OP_NO_SSLv3); | |
SSL_CTX_clear_options(sock->ssl_ctx, SSL_OP_NO_TLSv1); | |
SSL_CTX_clear_options(sock->ssl_ctx, SSL_OP_NO_TLSv1_1); | |
SSL_CTX_clear_options(sock->ssl_ctx, SSL_OP_NO_TLSv1_2); | |
SSL_CTX_set_verify(sock->ssl_ctx, SSL_VERIFY_PEER, 0); | |
sock->conn.ssl = SSL_new(sock->ssl_ctx); | |
if(!sock->conn.ssl) { | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
goto clean_sslctx; | |
} | |
if(!SSL_set_fd(sock->conn.ssl, fd)) { | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
goto clean_ssl; | |
} | |
if(SSL_set_tlsext_host_name(sock->conn.ssl, host) == 0) { | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
goto clean_ssl; | |
} | |
if(SSL_connect(sock->conn.ssl) != 1) { | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
goto clean_ssl; | |
} | |
return 0; | |
clean_ssl: | |
SSL_shutdown(sock->conn.ssl); | |
SSL_free(sock->conn.ssl); | |
clean_sslctx: | |
SSL_CTX_free(sock->ssl_ctx); | |
clean_fd: | |
close(fd); | |
return -1; | |
} | |
static ssize_t write_conn(struct sock *sock, const void *buf, size_t n, const char **msg) | |
{ | |
ssize_t ret; | |
if(sock->type == RAW_SOCK) { | |
ret = write(sock->conn.fd, buf, n); | |
if(ret == -1) | |
*msg = strerror(errno); | |
} else { | |
if(n > INT_MAX) | |
n = INT_MAX; | |
ret = SSL_write(sock->conn.ssl, buf, n); | |
if(ret == -1) | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
} | |
return ret; | |
} | |
static ssize_t read_conn(struct sock *sock, void *buf, size_t n, const char **msg) | |
{ | |
ssize_t ret; | |
if(sock->type == RAW_SOCK) { | |
ret = read(sock->conn.fd, buf, n); | |
if(ret == -1) *msg = strerror(errno); | |
} else { | |
if(n > INT_MAX) | |
n = INT_MAX; | |
ret = SSL_read(sock->conn.ssl, buf, n); | |
if(ret == -1) | |
*msg = ERR_reason_error_string(ERR_get_error()); | |
} | |
return ret; | |
} | |
static ssize_t nom_headers(char *p, size_t n) | |
{ | |
static int done; | |
static char last[4]; | |
size_t i; | |
if(done) return 0; | |
for(i = 0; i < n; i++) { | |
memmove(last, last+1, 3); | |
last[3] = p[i]; | |
if(memcmp(last, "\r\n\r\n", 4) == 0) { | |
done = 1; | |
return i + 1; | |
} | |
} | |
} | |
int main(int argc, char **argv) | |
{ | |
struct uri uri; | |
int fd; | |
const char *msg; | |
char buf[4096]; | |
char *buf_p; | |
ssize_t n; | |
struct sock sock; | |
SSL_library_init(); | |
SSL_load_error_strings(); | |
if(argc < 2) { | |
fprintf(stderr, "Too few arguments.\n"); | |
return 1; | |
} | |
if(parse_uri(argv[1], &uri)) { | |
fprintf(stderr, "Parse error.\n"); | |
return 1; | |
} | |
if(!uri.port) | |
if(strcmp(uri.scheme, "http") == 0) | |
uri.port = "80"; | |
else | |
uri.port = "443"; | |
if(!uri.host) { | |
fprintf(stderr, "Missing host.\n"); | |
return 1; | |
} | |
if(open_conn(uri.scheme, uri.host, uri.port, &sock, &msg) == -1) { | |
fprintf(stderr, "%s\n", msg); | |
return 1; | |
} | |
if(uri.path == 0) uri.path = ""; | |
if(write_conn(&sock, "GET /", sizeof("GET /")-1, &msg) == -1 | |
|| write_conn(&sock, uri.path, strlen(uri.path), &msg) == -1 | |
|| write_conn(&sock, " HTTP/1.0\r\nHost: ", sizeof(" HTTP/1.0\r\nHost: ")-1, &msg) == -1 | |
|| write_conn(&sock, uri.host, strlen(uri.host), &msg) == -1 | |
|| write_conn(&sock, ":", 1, &msg) == -1 | |
|| write_conn(&sock, uri.port, strlen(uri.port), &msg) == -1 | |
|| write_conn(&sock, "\r\n\r\n", 4, &msg) == -1) { | |
fprintf(stderr, "%s\n", msg); | |
return 1; | |
} | |
while((n = read_conn(&sock, buf, sizeof buf, &msg)) > 0) { | |
buf_p = buf; | |
while(n) { | |
ssize_t count; | |
count = nom_headers(buf_p, n); | |
n -= count; | |
buf_p += count; | |
count = write(1, buf_p, n); | |
if(count < 0) { | |
fprintf(stderr, "%s\n", strerror(errno)); | |
return 1; | |
} | |
n -= count; | |
buf_p += count; | |
} | |
} | |
if(n < 0) { | |
fprintf(stderr, "%s\n", msg); | |
return 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment