Skip to content

Instantly share code, notes, and snippets.

@pikhq
Created May 21, 2015 02:28
Show Gist options
  • Save pikhq/6bdf06310c09223434ac to your computer and use it in GitHub Desktop.
Save pikhq/6bdf06310c09223434ac to your computer and use it in GitHub Desktop.
Naive http client -- does SSL, and does not much else.
#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