#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <curl/curl.h>
#include <fcntl.h>

#if WIN32
#include <winsock2.h>
#include <windows.h>
#include <windowsx.h>
#include <winhttp.h>
#include <process.h>
#include <tlhelp32.h>
#include <winbase.h>
#include <conio.h>
#include <wincred.h>
#include <objbase.h>
#include <wincrypt.h>
#else
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <X11/Xlib.h>
#endif

#define dprintf(format, ...) printf(format, __VA_ARGS__)

#define die(format, ...) \
{ \
        char buf[BUFSIZ]; \
        snprintf(buf, sizeof(buf), \
                        "%s(%d): %d: %s: " format, \
                        __FILE__, getpid(), (int) __LINE__, __FUNCTION__,  __VA_ARGS__); \
        perror(buf); \
        exit(EXIT_FAILURE); \
}

#define cdie(format, ...) \
{ \
        printf("%s(%d): %d: %s: " format, \
                __FILE__, getpid(), (int) __LINE__, __FUNCTION__,  __VA_ARGS__); \
        exit(EXIT_FAILURE); \
}

#define wdie(format, ...) \
{ \
        printf("%s(%d): %d: %s: " format, \
                        __FILE__, getpid(), (int) __LINE__, __FUNCTION__,  __VA_ARGS__); \
        wprintf(L"Error: %d\n", GetLastError()); \
        exit(EXIT_FAILURE); \
}

#define PUBKEY_PIN "sha256//BVas8dKGCWxH57HW3+O8dZsfmKqJ63e0XEctrB3xyv8="

#define BUFFER_SIZE 8192

static char proxy[BUFFER_SIZE];
static char proxy_user[BUFFER_SIZE];
static char proxy_pass[BUFFER_SIZE];

#if WIN32
int
prompt_for_password(char *prompt, char *b, int size)
{
        int ch;
        int n = 0;

        memset(b, 0, size);

        _cputs(prompt);
        fflush(0);
        while(1) {
                ch = _getch();
                if (ch == '\r' || size < (n - 2)) {
                        break;
                }
                _putch('*');
                *b = (char) ch;
                n++;
                b++;
        }

        _putch( '\n' );
        return n;
}
#else
int
prompt_for_password(char *prompt, char *b, int size)
{
        memset(b, 0, size);

        printf("%s", prompt);
        fflush(0);
        if (fgets(b, size, stdin) == NULL) {
                return 0;
        }

        if (b[strlen(b) - 1] == '\n') {
                b[strlen(b) - 1] = '\0';
        }

        return strlen(b);
}
#endif


static int
wait_on_socket(curl_socket_t sockfd, int for_recv, long timeout_ms)
{
        struct timeval tv = {0};
        fd_set infd, outfd, errfd;
        int ret;

        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec= (timeout_ms % 1000) * 1000;

        FD_ZERO(&infd);
        FD_ZERO(&outfd);
        FD_ZERO(&errfd);

        FD_SET(sockfd, &errfd); /* always check for error */

        if (for_recv) {
                FD_SET(sockfd, &infd);

        } else {
                FD_SET(sockfd, &outfd);
        }

        /* select() returns the number of signalled sockets or -1 */
        ret = select(sockfd + 1, &infd, &outfd, &errfd, &tv);
        return ret;
}

void
prompt_for_proxy(void)
{
        printf("Proxy: ");
        fflush(0);
        if (fgets(proxy, BUFFER_SIZE, stdin) == NULL) {
                return;
        }

        if (proxy[strlen(proxy) - 1] == '\n') {
                proxy[strlen(proxy) - 1] = '\0';
        }
}

void
prompt_for_username_and_password(char *prompt, char *username, char *password)
{
        if (prompt)
                puts(prompt);

        printf("Username: ");
        fflush(0);
        if (fgets(username, BUFFER_SIZE, stdin) == NULL) {
                return;
        }

        if (username[strlen(username) - 1] == '\n') {
                username[strlen(username) - 1] = '\0';
        }

        memset(password, 0, BUFFER_SIZE);
        prompt_for_password("Password: ", password, BUFFER_SIZE);
}

void
standard()
{
        CURL *curl = NULL;
        CURLcode ret;
        long http_code = 0;
        char *errbuf;

        errbuf = malloc(CURL_ERROR_SIZE);
        if (errbuf == NULL)
                die("%s", "malloc(CURL_ERROR_SIZE)");

        curl = curl_easy_init();
        if (! curl) {
                die("%s", "curl_easy_init");
        }

        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
        curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, PUBKEY_PIN);
        curl_easy_setopt(curl, CURLOPT_URL, "https://gmvl.de/");
        curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);

        ret = curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
        if (ret != CURLE_OK) {
                cdie("curl_easy_setopt(curl, CURLOPT_PROXY, proxy): %s\n", curl_easy_strerror(ret));
        }
        ret = curl_easy_perform(curl);
        if (ret == CURLE_COULDNT_RESOLVE_PROXY) {
                /* clear proxy and try again */
                dprintf("%s", "Unable to resolve proxy, trying without proxy.\n");
                proxy[0] = '\0';
                ret = curl_easy_setopt(curl, CURLOPT_PROXY, "");
                if (ret != CURLE_OK) {
                        cdie("curl_easy_setopt(curl, CURLOPT_PROXY, \"\"): %s\n", curl_easy_strerror(ret));
                }
                ret = curl_easy_perform(curl);
        }

        curl_easy_getinfo(curl, CURLINFO_HTTP_CONNECTCODE, &http_code);
        dprintf("populate_terminal_server_list CURLINFO_HTTP_CONNECTCODE = %ld\n", http_code);
        if (http_code == 407) {
prompt_again:
                prompt_for_username_and_password("Proxy Credentials", proxy_user, proxy_pass);
                dprintf("proxy_user = '%s'\n", proxy_user);
                curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, proxy_user);
                curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, proxy_pass);
                ret = curl_easy_perform(curl);
                curl_easy_getinfo(curl, CURLINFO_HTTP_CONNECTCODE, &http_code);
                dprintf("populate_terminal_server_list with proxy credentials CURLINFO_HTTP_CONNECTCODE = %ld\n", http_code);
                if (http_code == 407) {
                        goto prompt_again;
                }
        }

        if (ret != CURLE_OK) {
                cdie("curl_easy_perform: CURLINFO_HTTP_CONNECTCODE = %ld; %s\n", http_code, curl_easy_strerror(ret));
        }

        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
        dprintf("populate_terminal_server_list CURLINFO_RESPONSE_CODE = %ld\n", http_code);

        curl_easy_cleanup(curl);
}

void
connect_only(void)
{
        CURL *curl = NULL;
        CURLcode ret;
        curl_socket_t sockfd;
        size_t iolen;
        char *request;
        char *errbuf;

        request = malloc(BUFFER_SIZE);
        if (request == NULL)
                die("%s", "malloc(BUFFER_SIZE)");

        errbuf = malloc(CURL_ERROR_SIZE);
        if (errbuf == NULL)
                die("%s", "malloc(CURL_ERROR_SIZE)");

        snprintf(request, BUFFER_SIZE, "GET / HTTP/1.0\r\nHost: gmvl.de\r\n\r\n");

        curl = curl_easy_init();
        if (! curl) {
                die("%s", "curl_easy_init");
        }

        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
        curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, PUBKEY_PIN);
        curl_easy_setopt(curl, CURLOPT_URL, "https://gmvl.de");
        curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L);
        curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, proxy_user);
        curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, proxy_pass);
        curl_easy_setopt(curl, CURLOPT_PROXY, proxy);

        ret = curl_easy_perform(curl);

        if (ret != CURLE_OK) {
                cdie("curl_easy_perform: %s\n", curl_easy_strerror(ret));
        }

        ret = curl_easy_getinfo(curl, CURLINFO_LASTSOCKET, (long *) &sockfd);
        if (ret != CURLE_OK) {
                cdie("curl_easy_getinfo: %s\n", curl_easy_strerror(ret));
        }

        /* sockfd is -1 because libcurl closed the SSL connection */
        if (sockfd < 0) {
                die("sockfd = %d", sockfd);
        }

        if (! wait_on_socket(sockfd, 0, 60000L)) {
                die("%s", "wait_on_socket");
        }

        ret = curl_easy_send(curl, request, strlen(request), &iolen);
        if (ret != CURLE_OK) {
                cdie("Error: %s\n", curl_easy_strerror(ret));
        }
        if (! wait_on_socket(sockfd, 1, 60000L)) {
                die("%s", "Error: timeout.\n");
        }
}

int
main(int argc, char **argv)
{
        prompt_for_proxy();
        /* this works */
        standard();
        /* this fails for some proxy servers (bluecoat, TMG/websense) but works for others (squid/Microsoft) */
        connect_only();

        return 0;
}

