mgr.c

00001 /*
00002  * mgr.c
00003  */
00004 
00005 #define LIBNFSNAPI_BUILDING_LIB
00006 
00007 #include <curl/curl.h>
00008 #include <limits.h>
00009 #include <openssl/sha.h>
00010 #include <stdio.h>
00011 #include <stdlib.h>
00012 #include <string.h>
00013 #include <sys/time.h>
00014 #include <time.h>
00015 #include "config.h"
00016 #include "error.h"
00017 #include "mgr.h"
00018 
00019 
00020 static size_t libnfsnapi_mgr_write_header(void *buffer, size_t size, size_t nmemb, void *userp);
00021 static size_t libnfsnapi_mgr_write_data(void *buffer, size_t size, size_t nmemb, void *userp);
00022 
00023 
00024 libnfsnapi_mgr_t *libnfsnapi_mgr_create(const char *login, const char *api_key,
00025                                         int *error_out)
00026 {
00027         libnfsnapi_mgr_t *mgr;
00028         struct timeval tp;
00029         int err = 0;
00030 
00031         mgr = (libnfsnapi_mgr_t *) malloc(sizeof(libnfsnapi_mgr_t));
00032         mgr->error = 0;
00033         if (!mgr) {
00034                 if (error_out)
00035                         *error_out = LIBNFSNAPI_ERROR_MEMORY;
00036                 return NULL;
00037         }
00038 
00039         mgr->login = strdup(login);
00040         if (!mgr->login) {
00041                 if (error_out)
00042                         *error_out = LIBNFSNAPI_ERROR_MEMORY;
00043                 free(mgr);
00044                 return NULL;
00045         }
00046         mgr->api_key = strdup(api_key);
00047         if (!mgr->api_key) {
00048                 if (error_out)
00049                         *error_out = LIBNFSNAPI_ERROR_MEMORY;
00050                 free(mgr->login);
00051                 free(mgr);
00052                 return NULL;
00053         }
00054 
00055         mgr->nudge = 0;
00056         mgr->buf = NULL;
00057         mgr->buf_read = mgr->buf_sz = 0;
00058         mgr->headers = NULL;
00059 
00060 
00061         /* Initialise libcurl */
00062         mgr->curl = NULL;
00063         if (curl_global_init(CURL_GLOBAL_SSL) != 0) {
00064                 libnfsnapi_mgr_destroy(mgr);
00065                 if (error_out)
00066                         *error_out = LIBNFSNAPI_ERROR_LIBCURL;
00067                 return NULL;
00068         }
00069         mgr->curl = curl_easy_init();
00070         if (!mgr->curl) {
00071                 curl_global_cleanup();
00072                 libnfsnapi_mgr_destroy(mgr);
00073                 if (error_out)
00074                         *error_out = LIBNFSNAPI_ERROR_LIBCURL;
00075                 return NULL;
00076         }
00077         /* err += curl_easy_setopt(mgr->curl, CURLOPT_VERBOSE, 1); */
00078         err += curl_easy_setopt(mgr->curl, CURLOPT_HEADER, 0);
00079         err += curl_easy_setopt(mgr->curl, CURLOPT_NOPROGRESS, 1);
00080 
00081         err += curl_easy_setopt(mgr->curl, CURLOPT_USERAGENT,
00082                         "libNFSNapi/" PACKAGE_VERSION);
00083 
00084         /* XXX: SSL certs aren't verified yet */
00085         err += curl_easy_setopt(mgr->curl, CURLOPT_SSL_VERIFYPEER, 0);
00086         err += curl_easy_setopt(mgr->curl, CURLOPT_SSL_VERIFYHOST, 0);
00087 
00088         err += curl_easy_setopt(mgr->curl, CURLOPT_WRITEFUNCTION,
00089                          libnfsnapi_mgr_write_data);
00090         err += curl_easy_setopt(mgr->curl, CURLOPT_WRITEDATA, mgr);
00091         err += curl_easy_setopt(mgr->curl, CURLOPT_HEADERFUNCTION,
00092                          libnfsnapi_mgr_write_header);
00093         err += curl_easy_setopt(mgr->curl, CURLOPT_HEADERDATA, mgr);
00094 
00095         if (err != 0) {
00096                 /* XXX: should we be more explicit, or remember which error? */
00097                 libnfsnapi_mgr_destroy(mgr);
00098                 if (error_out)
00099                         *error_out = LIBNFSNAPI_ERROR_LIBCURL;
00100                 return NULL;
00101         }
00102 
00103         /* Seed PRNG */
00104         gettimeofday(&tp, NULL);
00105         srandom(tp.tv_usec);
00106 
00107         /* Anything else to initialise? */
00108 
00109         return mgr;
00110 }
00111 
00112 void libnfsnapi_mgr_destroy(libnfsnapi_mgr_t *mgr)
00113 {
00114         free(mgr->login);
00115         free(mgr->api_key);
00116         if (mgr->buf)
00117                 free(mgr->buf);
00118         if (mgr->headers)
00119                 curl_slist_free_all(mgr->headers);
00120 
00121         if (mgr->curl) {
00122                 curl_easy_cleanup(mgr->curl);
00123                 curl_global_cleanup();
00124         }
00125 
00126         free(mgr);
00127 }
00128 
00129 int libnfsnapi_mgr_error(libnfsnapi_mgr_t *mgr)
00130 {
00131         return mgr->error;
00132 }
00133 
00134 const char *libnfsnapi_mgr_strerror(libnfsnapi_mgr_t *mgr)
00135 {
00136         return libnfsnapi_strerror(mgr->error);
00137 }
00138 
00139 void libnfsnapi_mgr_set_timenudge(libnfsnapi_mgr_t *mgr, int nudge)
00140 {
00141         mgr->nudge = nudge;
00142 }
00143 
00144 /* salt_dest should point to a buffer of at least 17 bytes so the
00145  * output salt can be NUL terminated.
00146  */
00147 static void libnfsnapi_mgr_gensalt(char *salt_dest)
00148 {
00149         static const char charset[62] =
00150                 "abcdefghijklmnopqrstuvwxyz"
00151                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
00152                 "0123456789";
00153         int i;
00154 
00155         for (i = 0; i < 16; ++i)
00156                 salt_dest[i] = charset[random() % sizeof(charset)];
00157         salt_dest[17] = '\0';
00158 }
00159 
00160 /* sha1_dest should point to a buffer of at least 41 bytes so the
00161  * output hash can be NUL terminated.
00162  */
00163 static void libnfsnapi_mgr_sha1(const char *input, char *sha1_dest)
00164 {
00165         unsigned char sha1_raw[20];
00166         int i;
00167         size_t len = input ? strlen(input) : 0;
00168 
00169         /* Call OpenSSL's SHA1 function */
00170         SHA1((const unsigned char *) input, len, sha1_raw);
00171 
00172         /* TODO: optimise this */
00173         for (i = 0; i < 20; ++i)
00174                 sprintf(&sha1_dest[i * 2], "%02x", sha1_raw[i]);
00175         sha1_dest[40] = '\0';
00176 }
00177 
00178 /* Generate the X-NFSN-Authentication HTTP header. */
00179 static char *libnfsnapi_mgr_genauthheader(libnfsnapi_mgr_t *mgr,
00180                                   const char *request_uri, const char *body)
00181 {
00182         time_t timestamp;
00183         char salt[17], hash[41], *check_str, *auth_str;
00184 
00185         timestamp = time(NULL);
00186         timestamp += mgr->nudge;
00187         libnfsnapi_mgr_gensalt(salt);
00188         /* printf("salt => \"%s\".\n", salt); */
00189 
00190         libnfsnapi_mgr_sha1(body, hash);
00191         /* printf("body_hash => \"%s\".\n", hash); */
00192 
00193         asprintf(&check_str, "%s;%lu;%s;%s;%s;%s",
00194                 mgr->login, (unsigned long) timestamp, salt,
00195                 mgr->api_key, request_uri, hash);
00196         if (!check_str)
00197                 return NULL;
00198         libnfsnapi_mgr_sha1(check_str, hash);
00199         free(check_str);
00200 
00201         asprintf(&auth_str, "X-NFSN-Authentication: %s;%lu;%s;%s",
00202                 mgr->login, (unsigned long) timestamp, salt, hash);
00203 
00204         return auth_str;
00205 }
00206 
00207 /* libcurl header callback; called once per HTTP header */
00208 static size_t libnfsnapi_mgr_write_header(void *buffer, size_t size, size_t nmemb, void *userp)
00209 {
00210         libnfsnapi_mgr_t *mgr = (libnfsnapi_mgr_t *) userp;
00211         size_t num_bytes = size * nmemb;
00212 
00213         char *hdr = (char *) malloc(num_bytes);
00214         if (!hdr) {
00215                 /* XXX: this doesn't get propogated back! */
00216                 mgr->error = LIBNFSNAPI_ERROR_MEMORY;
00217                 return 0;
00218         }
00219         memcpy(hdr, buffer, num_bytes);
00220         hdr[num_bytes] = '\0';
00221 
00222         /* Trim CRs and LFs */
00223         while ((hdr[0] != '\0') && strchr("\r\n", hdr[strlen(hdr) - 1]))
00224                 hdr[strlen(hdr) - 1] = '\0';
00225 
00226         mgr->headers = curl_slist_append(mgr->headers, hdr);
00227 
00228         if (strncmp(hdr, "HTTP/", 5) == 0)
00229                 printf("HTTP header (%d bytes): %s\n", num_bytes, hdr);
00230         free(hdr);
00231 
00232         return num_bytes;
00233 }
00234 /* libcurl write callback */
00235 static size_t libnfsnapi_mgr_write_data(void *buffer, size_t size, size_t nmemb, void *userp)
00236 {
00237         libnfsnapi_mgr_t *mgr = (libnfsnapi_mgr_t *) userp;
00238         char *nbuf;
00239         size_t num_bytes = size * nmemb, new_sz;
00240 
00241         /* If there's not enough room, enlarge the buffer.
00242          * This will also allocate the buffer on the first read.
00243          * Throughout reading, we keep at least one byte free, to make
00244          * processing the buffer easier later on.
00245          */
00246         if ((mgr->buf_sz - mgr->buf_read) <= num_bytes) {
00247                 new_sz = (mgr->buf_sz + 1) * 2;
00248                 while ((new_sz - mgr->buf_read - 1) < num_bytes)
00249                         new_sz *= 2;
00250                 nbuf = (char *) malloc(new_sz);
00251                 if (!nbuf)
00252                         return 0;       /* Damn! */
00253                 if (mgr->buf) {
00254                         memcpy(nbuf, mgr->buf, mgr->buf_read);
00255                         free(mgr->buf);
00256                 }
00257                 mgr->buf = nbuf;
00258                 mgr->buf_sz = new_sz;
00259         }
00260 
00261         memcpy(&mgr->buf[mgr->buf_read], buffer, num_bytes);
00262         mgr->buf_read += num_bytes;
00263 
00264         return num_bytes;
00265 }
00266 
00267 /* Returns non-zero on error, setting mgr->error to the same value. */
00268 int libnfsnapi_mgr_do(libnfsnapi_mgr_t *mgr, int method,
00269                         const char *request_uri, const char *data)
00270 {
00271         struct curl_slist *headers = NULL;
00272         char *full_uri, *auth_header;
00273         int err = 0;
00274         size_t i;
00275 
00276         /* Reset incoming buffers */
00277         mgr->buf_read = 0;
00278         if (mgr->headers) {
00279                 curl_slist_free_all(mgr->headers);
00280                 mgr->headers = NULL;
00281         }
00282 
00283         asprintf(&full_uri, "https://api.nearlyfreespeech.net%s", request_uri);
00284         if (!full_uri) {
00285                 mgr->error = LIBNFSNAPI_ERROR_MEMORY;
00286                 return mgr->error;
00287         }
00288         err += curl_easy_setopt(mgr->curl, CURLOPT_URL, full_uri);
00289 
00290         switch (method) {
00291                 case LIBNFSNAPI_MGR_HTTP_GET:
00292                         err += curl_easy_setopt(mgr->curl, CURLOPT_HTTPGET, 1);
00293                         break;
00294                 case LIBNFSNAPI_MGR_HTTP_PUT:
00295                         mgr->error = LIBNFSNAPI_ERROR_NOTIMPLEMENTED;
00296                         goto cleanup;                   /* XXX: NYI */
00297                         err += curl_easy_setopt(mgr->curl, CURLOPT_UPLOAD, 1);
00298                         break;
00299                 case LIBNFSNAPI_MGR_HTTP_POST:
00300                         err += curl_easy_setopt(mgr->curl, CURLOPT_POST, 1);
00301                         err += curl_easy_setopt(mgr->curl, CURLOPT_POSTFIELDS,
00302                                                 data);
00303                         break;
00304         }
00305 
00306         if (err != 0) {
00307                 mgr->error = LIBNFSNAPI_ERROR_LIBCURL;
00308                 goto cleanup;
00309         }
00310 
00311         /* Generate custom HTTP headers */
00312         auth_header = libnfsnapi_mgr_genauthheader(mgr, request_uri, data);
00313         if (!auth_header) {
00314                 mgr->error = LIBNFSNAPI_ERROR_MEMORY;
00315                 goto cleanup;
00316         }
00317         headers = curl_slist_append(headers, auth_header);
00318         free(auth_header);
00319         if (!headers) {
00320                 mgr->error = LIBNFSNAPI_ERROR_MEMORY;
00321                 goto cleanup;
00322         }
00323         if (curl_easy_setopt(mgr->curl, CURLOPT_HTTPHEADER, headers)) {
00324                 mgr->error = LIBNFSNAPI_ERROR_LIBCURL;
00325                 goto cleanup;
00326         }
00327 
00328         /* Perform! */
00329         if ((err = curl_easy_perform(mgr->curl)) != 0) {
00330                 /* Uh, oh! */
00331                 /* TODO: make this error more precise */
00332                 switch (err) {
00333                         case CURLE_COULDNT_RESOLVE_PROXY:
00334                         case CURLE_COULDNT_RESOLVE_HOST:
00335                         case CURLE_COULDNT_CONNECT:
00336                                 mgr->error = LIBNFSNAPI_ERROR_NETWORK;
00337                                 break;
00338                         default:
00339                                 printf("--> libcurl error is %d\n", err);
00340                                 mgr->error = LIBNFSNAPI_ERROR_LIBCURL;
00341                 }
00342                 goto cleanup;
00343         }
00344 
00345         if (!mgr->headers) {
00346                 mgr->error = LIBNFSNAPI_ERROR_PROTOCOL;
00347                 goto cleanup;
00348         }
00349 
00350         mgr->buf[mgr->buf_read] = '\0';
00351 
00352         /* TODO: do something useful! */
00353         printf("Response below: (%d bytes)\n", mgr->buf_read);
00354         printf("------------------------------------------\n");
00355         for (i = 0; i < mgr->buf_read; ++i)
00356                 putchar(mgr->buf[i]);
00357         if (mgr->buf[i-1] != '\n')
00358                 printf("[no EOL]\n");
00359         printf("------------------------------------------\n");
00360 
00361 cleanup:
00362         curl_easy_setopt(mgr->curl, CURLOPT_URL, NULL);
00363         curl_easy_setopt(mgr->curl, CURLOPT_HTTPHEADER, NULL);
00364 
00365         if (headers)
00366                 curl_slist_free_all(headers);
00367         free(full_uri);
00368         return mgr->error;
00369 }
00370 
00371 long libnfsnapi_mgr_result_long(libnfsnapi_mgr_t *mgr)
00372 {
00373         return strtol(mgr->buf, NULL, 10);
00374 }
00375 
00376 float libnfsnapi_mgr_result_float(libnfsnapi_mgr_t *mgr)
00377 {
00378         return strtof(mgr->buf, NULL);
00379 }
00380 
00381 char *libnfsnapi_mgr_result_string(libnfsnapi_mgr_t *mgr)
00382 {
00383         return strdup(mgr->buf);
00384 }

Generated on Sat Jan 13 17:42:28 2007 for libNFSNapi by  doxygen 1.4.6