diff options
author | Vadim Dashevskiy <watcherhd@gmail.com> | 2012-06-27 08:42:01 +0000 |
---|---|---|
committer | Vadim Dashevskiy <watcherhd@gmail.com> | 2012-06-27 08:42:01 +0000 |
commit | 76af3716fe56e728b2cad2df2bec7bb4a5b1c8df (patch) | |
tree | b46e5d15976e4025ce28cd3371eb69d5d7aa9a65 | |
parent | 09da3ed6d59beb5bc9bc34eba28e2fc4502415fb (diff) |
oauth re-added
git-svn-id: http://svn.miranda-ng.org/main/trunk@650 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
-rw-r--r-- | protocols/Twitter/oauth/Makefile.am | 8 | ||||
-rw-r--r-- | protocols/Twitter/oauth/hash.c | 492 | ||||
-rw-r--r-- | protocols/Twitter/oauth/oauth.c | 921 | ||||
-rw-r--r-- | protocols/Twitter/oauth/oauth.h | 764 | ||||
-rw-r--r-- | protocols/Twitter/oauth/oauth_http.c | 728 | ||||
-rw-r--r-- | protocols/Twitter/oauth/sha1.c | 317 | ||||
-rw-r--r-- | protocols/Twitter/oauth/xmalloc.c | 60 | ||||
-rw-r--r-- | protocols/Twitter/oauth/xmalloc.h | 10 |
8 files changed, 3300 insertions, 0 deletions
diff --git a/protocols/Twitter/oauth/Makefile.am b/protocols/Twitter/oauth/Makefile.am new file mode 100644 index 0000000000..b59c7c71df --- /dev/null +++ b/protocols/Twitter/oauth/Makefile.am @@ -0,0 +1,8 @@ +ACLOCAL_AMFLAGS= -I m4 +lib_LTLIBRARIES = liboauth.la +include_HEADERS = oauth.h + +liboauth_la_SOURCES=oauth.c config.h hash.c xmalloc.c xmalloc.h oauth_http.c +liboauth_la_LDFLAGS=@LIBOAUTH_LDFLAGS@ -version-info @VERSION_INFO@ +liboauth_la_LIBADD=@HASH_LIBS@ @CURL_LIBS@ +liboauth_la_CFLAGS=@LIBOAUTH_CFLAGS@ @HASH_CFLAGS@ @CURL_CFLAGS@ diff --git a/protocols/Twitter/oauth/hash.c b/protocols/Twitter/oauth/hash.c new file mode 100644 index 0000000000..ca00adaa8c --- /dev/null +++ b/protocols/Twitter/oauth/hash.c @@ -0,0 +1,492 @@ +/* + * hash algorithms used in OAuth + * + * Copyright 2007-2010 Robin Gareus <robin@gareus.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#if USE_BUILTIN_HASH // built-in / AVR -- TODO: check license of sha1.c +#include <stdio.h> +#include "oauth.h" // oauth_encode_base64 +#include "xmalloc.h" + +#include "sha1.c" // TODO: sha1.h ; Makefile.am: add sha1.c + +/* API */ +char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) { + sha1nfo s; + sha1_initHmac(&s, (const uint8_t*) k, kl); + sha1_write(&s, m, ml); + unsigned char *digest = sha1_resultHmac(&s); + return oauth_encode_base64(HASH_LENGTH, digest); +} + +char *oauth_sign_hmac_sha1 (const char *m, const char *k) { + return(oauth_sign_hmac_sha1_raw (m, strlen(m), k, strlen(k))); +} + +char *oauth_body_hash_file(char *filename) { + FILE *F= fopen(filename, "r"); + if (!F) return NULL; + + size_t len=0; + char fb[BUFSIZ]; + sha1nfo s; + sha1_init(&s); + + while (!feof(F) && (len=fread(fb,sizeof(char),BUFSIZ, F))>0) { + sha1_write(&s, fb, len); + } + fclose(F); + + unsigned char *dgst = xmalloc(HASH_LENGTH*sizeof(char)); // oauth_body_hash_encode frees the digest.. + memcpy(dgst, sha1_result(&s), HASH_LENGTH); + return oauth_body_hash_encode(HASH_LENGTH, dgst); +} + +char *oauth_body_hash_data(size_t length, const char *data) { + sha1nfo s; + sha1_init(&s); + for (;length--;) sha1_writebyte(&s, *data++); + + unsigned char *dgst = xmalloc(HASH_LENGTH*sizeof(char)); // oauth_body_hash_encode frees the digest.. + memcpy(dgst, sha1_result(&s), HASH_LENGTH); + return oauth_body_hash_encode(HASH_LENGTH, dgst); +} + +char *oauth_sign_rsa_sha1 (const char *m, const char *k) { + /* NOT RSA/PK11 support */ + return xstrdup("---RSA/PK11-is-not-supported-by-this-version-of-liboauth---"); +} + +int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *sig) { + /* NOT RSA/PK11 support */ + return -1; // mismatch , error +} + +#elif defined (USE_NSS) +/* use http://www.mozilla.org/projects/security/pki/nss/ for hash/sign */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "xmalloc.h" +#include "oauth.h" // oauth base64 encode fn's. + +// NSS includes +#include "pk11pub.h" +#include "nss.h" +#include "base64.h" +#include "keyhi.h" +#include "cryptohi.h" +#include "cert.h" + +#if 1 // work-around compiler-warning + // see http://bugzilla.mozilla.org/show_bug.cgi?id=243245#c3 + extern CERTCertificate * + __CERT_DecodeDERCertificate (SECItem *derSignedCert, PRBool copyDER, char *nickname); +#endif + +static const char NS_CERT_HEADER[] = "-----BEGIN CERTIFICATE-----"; +static const char NS_CERT_TRAILER[] = "-----END CERTIFICATE-----"; +static const char NS_PRIV_HEADER[] = "-----BEGIN PRIVATE KEY-----"; +static const char NS_PRIV_TRAILER[] = "-----END PRIVATE KEY-----"; + +void oauth_init_nss() { + static short nss_initialized = 0; + if (!nss_initialized) { NSS_NoDB_Init("."); nss_initialized=1;} +} + +/** + * Removes heading & trailing strings; used only internally. + * similar to NSS-source/nss/lib/pkcs7/certread.c + * + * the returned string (if not NULL) needs to be freed by the caller + */ +char *oauth_strip_pkcs(const char *txt, const char *h, const char *t) { + char *start, *end, *rv; + size_t len; + if ((start=strstr(txt, h))==NULL) return NULL; + start+=strlen(h); + while (*start=='\r' || *start=='\n') start++; + if ((end=strstr(start, t))==NULL) return NULL; + end--; + while (*end=='\r' || *end=='\n') end--; + len = end-start+2; + rv = xmalloc(len*sizeof(char)); + memcpy(rv,start,len); + rv[len-1]='\0'; + return rv; +} + +char *oauth_sign_hmac_sha1 (const char *m, const char *k) { + return(oauth_sign_hmac_sha1_raw (m, strlen(m), k, strlen(k))); +} + +char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) { + PK11SlotInfo *slot = NULL; + PK11SymKey *pkey = NULL; + PK11Context *context = NULL; + unsigned char digest[20]; // Is there a way to tell how large the output is? + unsigned int len; + SECStatus s; + SECItem keyItem, noParams; + char *rv=NULL; + + keyItem.type = siBuffer; + keyItem.data = (unsigned char*) k; + keyItem.len = kl; + + noParams.type = siBuffer; + noParams.data = NULL; + noParams.len = 0; + + oauth_init_nss(); + + slot = PK11_GetInternalKeySlot(); + if (!slot) goto looser; + pkey = PK11_ImportSymKey(slot, CKM_SHA_1_HMAC, PK11_OriginUnwrap, CKA_SIGN, &keyItem, NULL); + if (!pkey) goto looser; + context = PK11_CreateContextBySymKey(CKM_SHA_1_HMAC, CKA_SIGN, pkey, &noParams); + if (!context) goto looser; + + s = PK11_DigestBegin(context); + if (s != SECSuccess) goto looser; + s = PK11_DigestOp(context, (unsigned char*) m, ml); + if (s != SECSuccess) goto looser; + s = PK11_DigestFinal(context, digest, &len, sizeof digest); + if (s != SECSuccess) goto looser; + + rv=oauth_encode_base64(len, digest); + +looser: + if (context) PK11_DestroyContext(context, PR_TRUE); + if (pkey) PK11_FreeSymKey(pkey); + if (slot) PK11_FreeSlot(slot); + return rv; +} + +char *oauth_sign_rsa_sha1 (const char *m, const char *k) { + PK11SlotInfo *slot = NULL; + SECKEYPrivateKey *pkey = NULL; + SECItem signature; + SECStatus s; + SECItem der; + char *rv=NULL; + + char *key = oauth_strip_pkcs(k, NS_PRIV_HEADER, NS_PRIV_TRAILER); + if (!key) return NULL; + + oauth_init_nss(); + + slot = PK11_GetInternalKeySlot(); + if (!slot) goto looser; + s = ATOB_ConvertAsciiToItem(&der, key); + if (s != SECSuccess) goto looser; + s = PK11_ImportDERPrivateKeyInfoAndReturnKey(slot, &der, NULL, NULL, PR_FALSE, PR_TRUE, KU_ALL, &pkey, NULL); + SECITEM_FreeItem(&der, PR_FALSE); + if (s != SECSuccess) goto looser; + if (!pkey) goto looser; + if (pkey->keyType != rsaKey) goto looser; + s = SEC_SignData(&signature, (unsigned char*) m, strlen(m), pkey, SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE); + if (s != SECSuccess) goto looser; + + rv=oauth_encode_base64(signature.len, signature.data); + SECITEM_FreeItem(&signature, PR_FALSE); + +looser: + if (pkey) SECKEY_DestroyPrivateKey(pkey); + if (slot) PK11_FreeSlot(slot); + free(key); + return rv; +} + +int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *sig) { + PK11SlotInfo *slot = NULL; + SECKEYPublicKey *pkey = NULL; + CERTCertificate *cert = NULL; + SECItem signature; + SECStatus s; + SECItem der; + int rv=0; + + char *key = oauth_strip_pkcs(c, NS_CERT_HEADER, NS_CERT_TRAILER); + if (!key) return 0; + + oauth_init_nss(); + + s = ATOB_ConvertAsciiToItem(&signature, (char*) sig); // XXX cast (const char*) -> (char*) + if (s != SECSuccess) goto looser; + slot = PK11_GetInternalKeySlot(); + if (!slot) goto looser; + s = ATOB_ConvertAsciiToItem(&der, key); + if (s != SECSuccess) goto looser; + cert = __CERT_DecodeDERCertificate(&der, PR_TRUE, NULL); + SECITEM_FreeItem(&der, PR_FALSE); + if (!cert) goto looser; + pkey = CERT_ExtractPublicKey(cert); + if (!pkey) goto looser; + if (pkey->keyType != rsaKey) goto looser; + + s = VFY_VerifyData((unsigned char*) m, strlen(m), pkey, &signature, SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, NULL); + if (s == SECSuccess) rv=1; +#if 0 + else if (PR_GetError()!= SEC_ERROR_BAD_SIGNATURE) rv=-1; +#endif + +looser: + if (pkey) SECKEY_DestroyPublicKey(pkey); + if (slot) PK11_FreeSlot(slot); + free(key); + return rv; +} + +char *oauth_body_hash_file(char *filename) { + PK11SlotInfo *slot = NULL; + PK11Context *context = NULL; + unsigned char digest[20]; // Is there a way to tell how large the output is? + unsigned int len; + SECStatus s; + char *rv=NULL; + size_t bl; + unsigned char fb[BUFSIZ]; + + FILE *F= fopen(filename, "r"); + if (!F) return NULL; + + oauth_init_nss(); + + slot = PK11_GetInternalKeySlot(); + if (!slot) goto looser; + context = PK11_CreateDigestContext(SEC_OID_SHA1); + if (!context) goto looser; + + s = PK11_DigestBegin(context); + if (s != SECSuccess) goto looser; + while (!feof(F) && (bl=fread(fb,sizeof(char),BUFSIZ, F))>0) { + s = PK11_DigestOp(context, (unsigned char*) fb, bl); + if (s != SECSuccess) goto looser; + } + s = PK11_DigestFinal(context, digest, &len, sizeof digest); + if (s != SECSuccess) goto looser; + + unsigned char *dgst = xmalloc(len*sizeof(char)); // oauth_body_hash_encode frees the digest.. + memcpy(dgst, digest, len); + rv=oauth_body_hash_encode(len, dgst); + +looser: + fclose(F); + if (context) PK11_DestroyContext(context, PR_TRUE); + if (slot) PK11_FreeSlot(slot); + return rv; +} + +char *oauth_body_hash_data(size_t length, const char *data) { + PK11SlotInfo *slot = NULL; + PK11Context *context = NULL; + unsigned char digest[20]; // Is there a way to tell how large the output is? + unsigned int len; + SECStatus s; + char *rv=NULL; + + oauth_init_nss(); + + slot = PK11_GetInternalKeySlot(); + if (!slot) goto looser; + context = PK11_CreateDigestContext(SEC_OID_SHA1); + if (!context) goto looser; + + s = PK11_DigestBegin(context); + if (s != SECSuccess) goto looser; + s = PK11_DigestOp(context, (unsigned char*) data, length); + if (s != SECSuccess) goto looser; + s = PK11_DigestFinal(context, digest, &len, sizeof digest); + if (s != SECSuccess) goto looser; + + unsigned char *dgst = xmalloc(len*sizeof(char)); // oauth_body_hash_encode frees the digest.. + memcpy(dgst, digest, len); + rv=oauth_body_hash_encode(len, dgst); + +looser: + if (context) PK11_DestroyContext(context, PR_TRUE); + if (slot) PK11_FreeSlot(slot); + return rv; +} + +#else +/* use http://www.openssl.org/ for hash/sign */ + +#ifdef _GNU_SOURCE +/* + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "xmalloc.h" +#include "oauth.h" // base64 encode fn's. +#include <openssl/hmac.h> + +char *oauth_sign_hmac_sha1 (const char *m, const char *k) { + return(oauth_sign_hmac_sha1_raw (m, strlen(m), k, strlen(k))); +} + +char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) { + unsigned char result[EVP_MAX_MD_SIZE]; + unsigned int resultlen = 0; + + HMAC(EVP_sha1(), k, kl, + (unsigned char*) m, ml, + result, &resultlen); + + return(oauth_encode_base64(resultlen, result)); +} + +#include <openssl/evp.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/ssl.h> + +char *oauth_sign_rsa_sha1 (const char *m, const char *k) { + unsigned char *sig = NULL; + unsigned char *passphrase = NULL; + unsigned int len=0; + EVP_MD_CTX md_ctx; + + EVP_PKEY *pkey; + BIO *in; + in = BIO_new_mem_buf((unsigned char*) k, strlen(k)); + pkey = PEM_read_bio_PrivateKey(in, NULL, 0, passphrase); // generate sign + BIO_free(in); + + if (pkey == NULL) { + //fprintf(stderr, "liboauth/OpenSSL: can not read private key\n"); + return xstrdup("liboauth/OpenSSL: can not read private key"); + } + + len = EVP_PKEY_size(pkey); + sig = (unsigned char*)xmalloc((len+1)*sizeof(char)); + + EVP_SignInit(&md_ctx, EVP_sha1()); + EVP_SignUpdate(&md_ctx, m, strlen(m)); + if (EVP_SignFinal (&md_ctx, sig, &len, pkey)) { + char *tmp; + sig[len] = '\0'; + tmp = oauth_encode_base64(len,sig); + OPENSSL_free(sig); + EVP_PKEY_free(pkey); + return tmp; + } + return xstrdup("liboauth/OpenSSL: rsa-sha1 signing failed"); +} + +int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *s) { + EVP_MD_CTX md_ctx; + EVP_PKEY *pkey; + BIO *in; + X509 *cert = NULL; + unsigned char *b64d; + int slen, err; + + in = BIO_new_mem_buf((unsigned char*)c, strlen(c)); + cert = PEM_read_bio_X509(in, NULL, 0, NULL); + if (cert) { + pkey = (EVP_PKEY *) X509_get_pubkey(cert); + X509_free(cert); + } else { + pkey = PEM_read_bio_PUBKEY(in, NULL, 0, NULL); + } + BIO_free(in); + if (pkey == NULL) { + //fprintf(stderr, "could not read cert/pubkey.\n"); + return -2; + } + + b64d= (unsigned char*) xmalloc(sizeof(char)*strlen(s)); + slen = oauth_decode_base64(b64d, s); + + EVP_VerifyInit(&md_ctx, EVP_sha1()); + EVP_VerifyUpdate(&md_ctx, m, strlen(m)); + err = EVP_VerifyFinal(&md_ctx, b64d, slen, pkey); + EVP_MD_CTX_cleanup(&md_ctx); + EVP_PKEY_free(pkey); + free(b64d); + return (err); +} + + +/** + * http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html + */ +char *oauth_body_hash_file(char *filename) { + unsigned char fb[BUFSIZ]; + EVP_MD_CTX ctx; + size_t len=0; + unsigned char *md; + FILE *F= fopen(filename, "r"); + if (!F) return NULL; + + EVP_MD_CTX_init(&ctx); + EVP_DigestInit(&ctx,EVP_sha1()); + while (!feof(F) && (len=fread(fb,sizeof(char),BUFSIZ, F))>0) { + EVP_DigestUpdate(&ctx, fb, len); + } + fclose(F); + len=0; + md=(unsigned char*) xcalloc(EVP_MD_size(EVP_sha1()),sizeof(unsigned char)); + EVP_DigestFinal(&ctx, md,(unsigned int*) &len); + EVP_MD_CTX_cleanup(&ctx); + return oauth_body_hash_encode(len, md); +} + +char *oauth_body_hash_data(size_t length, const char *data) { + EVP_MD_CTX ctx; + size_t len=0; + unsigned char *md; + md=(unsigned char*) xcalloc(EVP_MD_size(EVP_sha1()),sizeof(unsigned char)); + EVP_MD_CTX_init(&ctx); + EVP_DigestInit(&ctx,EVP_sha1()); + EVP_DigestUpdate(&ctx, data, length); + EVP_DigestFinal(&ctx, md,(unsigned int*) &len); + EVP_MD_CTX_cleanup(&ctx); + return oauth_body_hash_encode(len, md); +} + +#endif + +// vi: sts=2 sw=2 ts=2 diff --git a/protocols/Twitter/oauth/oauth.c b/protocols/Twitter/oauth/oauth.c new file mode 100644 index 0000000000..0f205572dd --- /dev/null +++ b/protocols/Twitter/oauth/oauth.c @@ -0,0 +1,921 @@ +/* + * OAuth string functions in POSIX-C. + * + * Copyright 2007-2011 Robin Gareus <robin@gareus.org> + * + * The base64 functions are by Jan-Henrik Haukeland, <hauk@tildeslash.com> + * and un/escape_url() was inspired by libcurl's curl_escape under ISC-license + * many thanks to Daniel Stenberg <daniel@haxx.se>. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#define WIPE_MEMORY ///< overwrite sensitve data before free()ing it. + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <math.h> +#include <ctype.h> // isxdigit + +#include "xmalloc.h" +#include "oauth.h" + +#ifndef WIN32 // getpid() on POSIX systems +#include <sys/types.h> +#include <unistd.h> +#else +#define snprintf _snprintf +#define strncasecmp strnicmp +#endif + +/** + * Base64 encode one byte + */ +char oauth_b64_encode(unsigned char u) { + if(u < 26) return 'A'+u; + if(u < 52) return 'a'+(u-26); + if(u < 62) return '0'+(u-52); + if(u == 62) return '+'; + return '/'; +} + +/** + * Decode a single base64 character. + */ +unsigned char oauth_b64_decode(char c) { + if(c >= 'A' && c <= 'Z') return(c - 'A'); + if(c >= 'a' && c <= 'z') return(c - 'a' + 26); + if(c >= '0' && c <= '9') return(c - '0' + 52); + if(c == '+') return 62; + return 63; +} + +/** + * Return TRUE if 'c' is a valid base64 character, otherwise FALSE + */ +int oauth_b64_is_base64(char c) { + if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || (c == '+') || + (c == '/') || (c == '=')) { + return 1; + } + return 0; +} + +/** + * Base64 encode and return size data in 'src'. The caller must free the + * returned string. + * + * @param size The size of the data in src + * @param src The data to be base64 encode + * @return encoded string otherwise NULL + */ +char *oauth_encode_base64(int size, const unsigned char *src) { + int i; + char *out, *p; + + if(!src) return NULL; + if(!size) size= strlen((char *)src); + out= (char*) xcalloc(sizeof(char), size*4/3+4); + p= out; + + for(i=0; i<size; i+=3) { + unsigned char b1=0, b2=0, b3=0, b4=0, b5=0, b6=0, b7=0; + b1= src[i]; + if(i+1<size) b2= src[i+1]; + if(i+2<size) b3= src[i+2]; + + b4= b1>>2; + b5= ((b1&0x3)<<4)|(b2>>4); + b6= ((b2&0xf)<<2)|(b3>>6); + b7= b3&0x3f; + + *p++= oauth_b64_encode(b4); + *p++= oauth_b64_encode(b5); + + if(i+1<size) *p++= oauth_b64_encode(b6); + else *p++= '='; + + if(i+2<size) *p++= oauth_b64_encode(b7); + else *p++= '='; + } + return out; +} + +/** + * Decode the base64 encoded string 'src' into the memory pointed to by + * 'dest'. + * + * @param dest Pointer to memory for holding the decoded string. + * Must be large enough to receive the decoded string. + * @param src A base64 encoded string. + * @return the length of the decoded string if decode + * succeeded otherwise 0. + */ +int oauth_decode_base64(unsigned char *dest, const char *src) { + if(src && *src) { + unsigned char *p= dest; + int k, l= strlen(src)+1; + unsigned char *buf= (unsigned char*) xcalloc(sizeof(unsigned char), l); + + /* Ignore non base64 chars as per the POSIX standard */ + for(k=0, l=0; src[k]; k++) { + if(oauth_b64_is_base64(src[k])) { + buf[l++]= src[k]; + } + } + + for(k=0; k<l; k+=4) { + char c1='A', c2='A', c3='A', c4='A'; + unsigned char b1=0, b2=0, b3=0, b4=0; + c1= buf[k]; + + if(k+1<l) c2= buf[k+1]; + if(k+2<l) c3= buf[k+2]; + if(k+3<l) c4= buf[k+3]; + + b1= oauth_b64_decode(c1); + b2= oauth_b64_decode(c2); + b3= oauth_b64_decode(c3); + b4= oauth_b64_decode(c4); + + *p++=((b1<<2)|(b2>>4) ); + + if(c3 != '=') *p++=(((b2&0xf)<<4)|(b3>>2) ); + if(c4 != '=') *p++=(((b3&0x3)<<6)|b4 ); + } + free(buf); + dest[p-dest]='\0'; + return(p-dest); + } + return 0; +} + +/** + * Escape 'string' according to RFC3986 and + * http://oauth.net/core/1.0/#encoding_parameters. + * + * @param string The data to be encoded + * @return encoded string otherwise NULL + * The caller must free the returned string. + */ +char *oauth_url_escape(const char *string) { + size_t alloc, newlen; + char *ns = NULL, *testing_ptr = NULL; + unsigned char in; + size_t strindex=0; + size_t length; + + if (!string) return xstrdup(""); + + alloc = strlen(string)+1; + newlen = alloc; + + ns = (char*) xmalloc(alloc); + + length = alloc-1; + while(length--) { + in = *string; + + switch(in){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': case '~': case '.': case '-': + ns[strindex++]=in; + break; + default: + newlen += 2; /* this'll become a %XX */ + if(newlen > alloc) { + alloc *= 2; + testing_ptr = (char*) xrealloc(ns, alloc); + ns = testing_ptr; + } + snprintf(&ns[strindex], 4, "%%%02X", in); + strindex+=3; + break; + } + string++; + } + ns[strindex]=0; + return ns; +} + +#ifndef ISXDIGIT +# define ISXDIGIT(x) (isxdigit((int) ((unsigned char)x))) +#endif + +/** + * Parse RFC3986 encoded 'string' back to unescaped version. + * + * @param string The data to be unescaped + * @param olen unless NULL the length of the returned string is stored there. + * @return decoded string or NULL + * The caller must free the returned string. + */ +char *oauth_url_unescape(const char *string, size_t *olen) { + size_t alloc, strindex=0; + char *ns = NULL; + unsigned char in; + long hex; + + if (!string) return NULL; + alloc = strlen(string)+1; + ns = (char*) xmalloc(alloc); + + while(--alloc > 0) { + in = *string; + if(('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { + char hexstr[3]; // '%XX' + hexstr[0] = string[1]; + hexstr[1] = string[2]; + hexstr[2] = 0; + hex = strtol(hexstr, NULL, 16); + in = (unsigned char)hex; /* hex is always < 256 */ + string+=2; + alloc-=2; + } + ns[strindex++] = in; + string++; + } + ns[strindex]=0; + if(olen) *olen = strindex; + return ns; +} + +/** + * returns plaintext signature for the given key. + * + * the returned string needs to be freed by the caller + * + * @param m message to be signed + * @param k key used for signing + * @return signature string + */ +char *oauth_sign_plaintext (const char *m, const char *k) { + return(oauth_url_escape(k)); +} + +/** + * encode strings and concatenate with '&' separator. + * The number of strings to be concatenated must be + * given as first argument. + * all arguments thereafter must be of type (char *) + * + * @param len the number of arguments to follow this parameter + * @param ... string to escape and added (may be NULL) + * + * @return pointer to memory holding the concatenated + * strings - needs to be free(d) by the caller. or NULL + * in case we ran out of memory. + */ +char *oauth_catenc(int len, ...) { + va_list va; + int i; + char *rv = (char*) xmalloc(sizeof(char)); + *rv='\0'; + va_start(va, len); + for(i=0;i<len;i++) { + char *arg = va_arg(va, char *); + char *enc; + int len; + enc = oauth_url_escape(arg); + if(!enc) break; + len = strlen(enc) + 1 + ((i>0)?1:0); + if(rv) len+=strlen(rv); + rv=(char*) xrealloc(rv,len*sizeof(char)); + + if(i>0) strcat(rv, "&"); + strcat(rv, enc); + free(enc); + } + va_end(va); + return(rv); +} + +/** + * splits the given url into a parameter array. + * (see \ref oauth_serialize_url and \ref oauth_serialize_url_parameters for the reverse) + * + * NOTE: Request-parameters-values may include an ampersand character. + * However if unescaped this function will use them as parameter delimiter. + * If you need to make such a request, this function since version 0.3.5 allows + * to use the ASCII SOH (0x01) character as alias for '&' (0x26). + * (the motivation is convenience: SOH is /untypeable/ and much more + * unlikely to appear than '&' - If you plan to sign fancy URLs you + * should not split a query-string, but rather provide the parameter array + * directly to \ref oauth_serialize_url) + * + * @param url the url or query-string to parse. + * @param argv pointer to a (char *) array where the results are stored. + * The array is re-allocated to match the number of parameters and each + * parameter-string is allocated with strdup. - The memory needs to be freed + * by the caller. + * @param qesc use query parameter escape (vs post-param-escape) - if set + * to 1 all '+' are treated as spaces ' ' + * + * @return number of parameter(s) in array. + */ +int oauth_split_post_paramters(const char *url, char ***argv, short qesc) { + int argc=0; + char *token, *tmp, *t1; + if (!argv) return 0; + if (!url) return 0; + t1=xstrdup(url); + + // '+' represents a space, in a URL query string + while ((qesc&1) && (tmp=strchr(t1,'+'))) *tmp=' '; + + tmp=t1; + while((token=strtok(tmp,"&?"))) { + if(!strncasecmp("oauth_signature=",token,16)) continue; + (*argv)=(char**) xrealloc(*argv,sizeof(char*)*(argc+1)); + while (!(qesc&2) && (tmp=strchr(token,'\001'))) *tmp='&'; + if (argc>0 || (qesc&4)) + (*argv)[argc]=oauth_url_unescape(token, NULL); + else + (*argv)[argc]=xstrdup(token); + if (argc==0 && strstr(token, ":/")) { + // HTTP does not allow empty absolute paths, so the URL + // 'http://example.com' is equivalent to 'http://example.com/' and should + // be treated as such for the purposes of OAuth signing (rfc2616, section 3.2.1) + // see http://groups.google.com/group/oauth/browse_thread/thread/c44b6f061bfd98c?hl=en + char *slash=strstr(token, ":/"); + while (slash && *(++slash) == '/') ; // skip slashes eg /xxx:[\/]*/ +#if 0 + // skip possibly unescaped slashes in the userinfo - they're not allowed by RFC2396 but have been seen. + // the hostname/IP may only contain alphanumeric characters - so we're safe there. + if (slash && strchr(slash,'@')) slash=strchr(slash,'@'); +#endif + if (slash && !strchr(slash,'/')) { +#ifdef DEBUG_OAUTH + fprintf(stderr, "\nliboauth: added trailing slash to URL: '%s'\n\n", token); +#endif + free((*argv)[argc]); + (*argv)[argc]= (char*) xmalloc(sizeof(char)*(2+strlen(token))); + strcpy((*argv)[argc],token); + strcat((*argv)[argc],"/"); + } + } + if (argc==0 && (tmp=strstr((*argv)[argc],":80/"))) { + memmove(tmp, tmp+3, strlen(tmp+2)); + } + tmp=NULL; + argc++; + } + + free(t1); + return argc; +} + +int oauth_split_url_parameters(const char *url, char ***argv) { + return oauth_split_post_paramters(url, argv, 1); +} + +/** + * build a url query string from an array. + * + * @param argc the total number of elements in the array + * @param start element in the array at which to start concatenating. + * @param argv parameter-array to concatenate. + * @return url string needs to be freed by the caller. + * + */ +char *oauth_serialize_url (int argc, int start, char **argv) { + return oauth_serialize_url_sep( argc, start, argv, "&", 0); +} + +/** + * encode query parameters from an array. + * + * @param argc the total number of elements in the array + * @param start element in the array at which to start concatenating. + * @param argv parameter-array to concatenate. + * @param sep separator for parameters (usually "&") + * @param mod - bitwise modifiers: + * 1: skip all values that start with "oauth_" + * 2: skip all values that don't start with "oauth_" + * 4: add double quotation marks around values (use with sep=", " to generate HTTP Authorization header). + * @return url string needs to be freed by the caller. + */ +char *oauth_serialize_url_sep (int argc, int start, char **argv, char *sep, int mod) { + char *tmp, *t1; + int i; + int first=1; + int seplen=strlen(sep); + char *query = (char*) xmalloc(sizeof(char)); + *query='\0'; + for(i=start; i< argc; i++) { + int len = 0; + if ((mod&1)==1 && (strncmp(argv[i],"oauth_",6) == 0 || strncmp(argv[i],"x_oauth_",8) == 0) ) continue; + if ((mod&2)==2 && (strncmp(argv[i],"oauth_",6) != 0 && strncmp(argv[i],"x_oauth_",8) != 0) && i!=0) continue; + + if (query) len+=strlen(query); + + if (i==start && i==0 && strstr(argv[i], ":/")) { + tmp=xstrdup(argv[i]); +#if 1 // encode white-space in the base-url + while ((t1=strchr(tmp,' '))) { +# if 0 + *t1='+'; +# else + size_t off = t1-tmp; + char *t2 = (char*) xmalloc(sizeof(char)*(3+strlen(tmp))); + strcpy(t2, tmp); + strcpy(t2+off+2, tmp+off); + *(t2+off)='%'; *(t2+off+1)='2'; *(t2+off+2)='0'; + free(tmp); + tmp=t2; +# endif +#endif + } + len+=strlen(tmp); + } else if(!(t1=strchr(argv[i], '='))) { + // see http://oauth.net/core/1.0/#anchor14 + // escape parameter names and arguments but not the '=' + tmp=xstrdup(argv[i]); + tmp=(char*) xrealloc(tmp,(strlen(tmp)+2)*sizeof(char)); + strcat(tmp,"="); + len+=strlen(tmp); + } else { + *t1=0; + tmp = oauth_url_escape(argv[i]); + *t1='='; + t1 = oauth_url_escape((t1+1)); + tmp=(char*) xrealloc(tmp,(strlen(tmp)+strlen(t1)+2+(mod&4?2:0))*sizeof(char)); + strcat(tmp,"="); + if (mod&4) strcat(tmp,"\""); + strcat(tmp,t1); + if (mod&4) strcat(tmp,"\""); + free(t1); + len+=strlen(tmp); + } + len+=seplen+1; + query=(char*) xrealloc(query,len*sizeof(char)); + strcat(query, ((i==start||first)?"":sep)); + first=0; + strcat(query, tmp); + if (i==start && i==0 && strstr(tmp, ":/")) { + strcat(query, "?"); + first=1; + } + free(tmp); + } + return (query); +} + +/** + * build a query parameter string from an array. + * + * This function is a shortcut for \ref oauth_serialize_url (argc, 1, argv). + * It strips the leading host/path, which is usually the first + * element when using oauth_split_url_parameters on an URL. + * + * @param argc the total number of elements in the array + * @param argv parameter-array to concatenate. + * @return url string needs to be freed by the caller. + */ +char *oauth_serialize_url_parameters (int argc, char **argv) { + return oauth_serialize_url(argc, 1, argv); +} + +/** + * generate a random string between 15 and 32 chars length + * and return a pointer to it. The value needs to be freed by the + * caller + * + * @return zero terminated random string. + */ +#if !defined HAVE_OPENSSL_HMAC_H && !defined USE_NSS +/* pre liboauth-0.7.2 and possible future versions that don't use OpenSSL or NSS */ +char *oauth_gen_nonce() { + char *nc; + static int rndinit = 1; + const char *chars = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_"; + unsigned int max = strlen( chars ); + int i, len; + + if(rndinit) {srand(time(NULL) +#ifndef WIN32 // quick windows check. + * getpid() +#endif + ); rndinit=0;} // seed random number generator - FIXME: we can do better ;) + + len=15+floor(rand()*16.0/(double)RAND_MAX); + nc = (char*) xmalloc((len+1)*sizeof(char)); + for(i=0;i<len; i++) { + nc[i] = chars[ rand() % max ]; + } + nc[i]='\0'; + return (nc); +} +#else // OpenSSL or NSS random number generator +#ifdef USE_NSS + void oauth_init_nss(); //decladed in hash.c +# include "pk11pub.h" +# define MY_RAND PK11_GenerateRandom +# define MY_SRAND oauth_init_nss(); +#else +# ifdef _GNU_SOURCE +/* Note: the OpenSSL/GPL exemption stated + * verbosely in hash.c applies to this code as well. */ +# endif +# include <openssl/rand.h> +# define MY_RAND RAND_bytes +# define MY_SRAND ; +#endif +char *oauth_gen_nonce() { + char *nc; + unsigned char buf; + const char *chars = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_"; + unsigned int max = strlen(chars); + int i, len; + + MY_SRAND + MY_RAND(&buf, 1); + len=15+(((short)buf)&0x0f); + nc = (char*) xmalloc((len+1)*sizeof(char)); + for(i=0;i<len; i++) { + MY_RAND(&buf, 1); + nc[i] = chars[ ((short)buf) % max ]; + } + nc[i]='\0'; + return (nc); +} +#endif + +/** + * string compare function for oauth parameters. + * + * used with qsort. needed to normalize request parameters. + * see http://oauth.net/core/1.0/#anchor14 + */ +int oauth_cmpstringp(const void *p1, const void *p2) { + char *v1,*v2; + char *t1,*t2; + int rv; + // TODO: this is not fast - we should escape the + // array elements (once) before sorting. + v1=oauth_url_escape(* (char * const *)p1); + v2=oauth_url_escape(* (char * const *)p2); + + // '=' signs are not "%3D" ! + if ((t1=strstr(v1,"%3D"))) { + t1[0]='\0'; t1[1]='='; t1[2]='='; + } + if ((t2=strstr(v2,"%3D"))) { + t2[0]='\0'; t2[1]='='; t2[2]='='; + } + + // compare parameter names + rv=strcmp(v1,v2); + if (rv!=0) { + if (v1) free(v1); + if (v2) free(v2); + return rv; + } + + // if parameter names are equal, sort by value. + if (t1) t1[0]='='; + if (t2) t2[0]='='; + if (t1 && t2) rv=strcmp(t1,t2); + else if (!t1 && !t2) rv=0; + else if (!t1) rv=-1; + else rv=1; + + if (v1) free(v1); + if (v2) free(v2); + return rv; +} + +/** + * search array for parameter key. + * @param argv length of array to search + * @param argc parameter array to search + * @param key key of parameter to check. + * + * @return FALSE (0) if array does not contain a parameter with given key, TRUE (1) otherwise. + */ +int oauth_param_exists(char **argv, int argc, char *key) { + int i; + size_t l= strlen(key); + for (i=0;i<argc;i++) + if (strlen(argv[i])>l && !strncmp(argv[i],key,l) && argv[i][l] == '=') return 1; + return 0; +} + +/** + * add query parameter to array + * + * @param argcp pointer to array length int + * @param argvp pointer to array values + * @param addparam parameter to add (eg. "foo=bar") + */ +void oauth_add_param_to_array(int *argcp, char ***argvp, const char *addparam) { + (*argvp)=(char**) xrealloc(*argvp,sizeof(char*)*((*argcp)+1)); + (*argvp)[(*argcp)++]= (char*) xstrdup(addparam); +} + +/** + * + */ +void oauth_add_protocol(int *argcp, char ***argvp, + OAuthMethod method, + const char *c_key, //< consumer key - posted plain text + const char *t_key //< token key - posted plain text in URL + ){ + char oarg[1024]; + + // add OAuth specific arguments + if (!oauth_param_exists(*argvp,*argcp,"oauth_nonce")) { + char *tmp; + snprintf(oarg, 1024, "oauth_nonce=%s", (tmp=oauth_gen_nonce())); + oauth_add_param_to_array(argcp, argvp, oarg); + free(tmp); + } + + if (!oauth_param_exists(*argvp,*argcp,"oauth_timestamp")) { + snprintf(oarg, 1024, "oauth_timestamp=%li", (long int) time(NULL)); + oauth_add_param_to_array(argcp, argvp, oarg); + } + + if (t_key) { + snprintf(oarg, 1024, "oauth_token=%s", t_key); + oauth_add_param_to_array(argcp, argvp, oarg); + } + + snprintf(oarg, 1024, "oauth_consumer_key=%s", c_key); + oauth_add_param_to_array(argcp, argvp, oarg); + + snprintf(oarg, 1024, "oauth_signature_method=%s", + method==0?"HMAC-SHA1":method==1?"RSA-SHA1":"PLAINTEXT"); + oauth_add_param_to_array(argcp, argvp, oarg); + + if (!oauth_param_exists(*argvp,*argcp,"oauth_version")) { + snprintf(oarg, 1024, "oauth_version=1.0"); + oauth_add_param_to_array(argcp, argvp, oarg); + } + +#if 0 // oauth_version 1.0 Rev A + if (!oauth_param_exists(argv,argc,"oauth_callback")) { + snprintf(oarg, 1024, "oauth_callback=oob"); + oauth_add_param_to_array(argcp, argvp, oarg); + } +#endif + +} + +char *oauth_sign_url (const char *url, char **postargs, + OAuthMethod method, + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ) { + return oauth_sign_url2(url, postargs, + method, NULL, + c_key, c_secret, + t_key, t_secret); +} + +char *oauth_sign_url2 (const char *url, char **postargs, + OAuthMethod method, + const char *http_method, //< HTTP request method + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ) { + int argc; + char **argv = NULL; + char *rv; + + if (postargs) + argc = oauth_split_post_paramters(url, &argv, 0); + else + argc = oauth_split_url_parameters(url, &argv); + + rv=oauth_sign_array2(&argc, &argv, postargs, + method, http_method, + c_key, c_secret, t_key, t_secret); + + oauth_free_array(&argc, &argv); + return(rv); +} + +char *oauth_sign_array (int *argcp, char***argvp, + char **postargs, + OAuthMethod method, + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ) { + return oauth_sign_array2 (argcp, argvp, + postargs, method, + NULL, + c_key, c_secret, + t_key, t_secret); +} + +void oauth_sign_array2_process (int *argcp, char***argvp, + char **postargs, + OAuthMethod method, + const char *http_method, //< HTTP request method + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ) { + char oarg[1024]; + char *query; + char *okey, *odat, *sign; + char *http_request_method; + + if (!http_method) { + http_request_method = xstrdup(postargs?"POST":"GET"); + } else { + int i; + http_request_method = xstrdup(http_method); + for (i=0;i<strlen(http_request_method);i++) + http_request_method[i]=toupper(http_request_method[i]); + } + + // add required OAuth protocol parameters + oauth_add_protocol(argcp, argvp, method, c_key, t_key); + + // sort parameters + qsort(&(*argvp)[1], (*argcp)-1, sizeof(char *), oauth_cmpstringp); + + // serialize URL - base-url + query= oauth_serialize_url_parameters(*argcp, *argvp); + + // generate signature + okey = oauth_catenc(2, c_secret, t_secret); + odat = oauth_catenc(3, http_request_method, (*argvp)[0], query); // base-string + free(http_request_method); +#ifdef DEBUG_OAUTH + fprintf (stderr, "\nliboauth: data to sign='%s'\n\n", odat); + fprintf (stderr, "\nliboauth: key='%s'\n\n", okey); +#endif + switch(method) { + case OA_RSA: + sign = oauth_sign_rsa_sha1(odat,okey); // XXX okey needs to be RSA key! + break; + case OA_PLAINTEXT: + sign = oauth_sign_plaintext(odat,okey); + break; + default: + sign = oauth_sign_hmac_sha1(odat,okey); + } +#ifdef WIPE_MEMORY + memset(okey,0, strlen(okey)); + memset(odat,0, strlen(odat)); +#endif + free(odat); + free(okey); + + // append signature to query args. + snprintf(oarg, 1024, "oauth_signature=%s",sign); + oauth_add_param_to_array(argcp, argvp, oarg); + free(sign); + if(query) free(query); +} + +char *oauth_sign_array2 (int *argcp, char***argvp, + char **postargs, + OAuthMethod method, + const char *http_method, //< HTTP request method + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ) { + + char *result; + oauth_sign_array2_process(argcp, argvp, postargs, method, http_method, c_key, c_secret, t_key, t_secret); + + // build URL params + result = oauth_serialize_url(*argcp, (postargs?1:0), *argvp); + + if(postargs) { + *postargs = result; + result = xstrdup((*argvp)[0]); + } + + return result; +} + + +/** + * free array args + * + * @param argcp pointer to array length int + * @param argvp pointer to array values to be free()d + */ +void oauth_free_array(int *argcp, char ***argvp) { + int i; + for (i=0;i<(*argcp);i++) { + free((*argvp)[i]); + } + if(*argvp) free(*argvp); +} + +/** + * base64 encode digest, free it and return a URL parameter + * with the oauth_body_hash + */ +char *oauth_body_hash_encode(size_t len, unsigned char *digest) { + char *sign=oauth_encode_base64(len,digest); + char *sig_url = (char*)xmalloc(17+strlen(sign)); + sprintf(sig_url,"oauth_body_hash=%s", sign); + free(sign); + free(digest); + return sig_url; +} + + +/** + * compare two strings in constant-time (as to not let an + * attacker guess how many leading chars are correct: + * http://rdist.root.org/2010/01/07/timing-independent-array-comparison/ ) + * + * @param a string to compare + * @param b string to compare + * @param len_a length of string a + * @param len_b length of string b + * + * returns 0 (false) if strings are not equal, and 1 (true) if strings are equal. + */ +int oauth_time_independent_equals_n(const char* a, const char* b, size_t len_a, size_t len_b) { + int diff, i, j; + if (a == NULL) return (b == NULL); + else if (b == NULL) return 0; + else if (len_b == 0) return (len_a == 0); + diff = len_a ^ len_b; + j=0; + for (i=0; i<len_a; ++i) { + diff |= a[i] ^ b[j]; + j = (j+1) % len_b; + } + return diff == 0; +} +int oauth_time_indepenent_equals_n(const char* a, const char* b, size_t len_a, size_t len_b) { + return oauth_time_independent_equals_n(a, b, len_a, len_b); +} + +int oauth_time_independent_equals(const char* a, const char* b) { + return oauth_time_independent_equals_n (a, b, a?strlen(a):0, b?strlen(b):0); +} + +int oauth_time_indepenent_equals(const char* a, const char* b) { + return oauth_time_independent_equals_n (a, b, a?strlen(a):0, b?strlen(b):0); +} + +/** + * xep-0235 - TODO + */ +char *oauth_sign_xmpp (const char *xml, + OAuthMethod method, + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_secret //< token secret - used as 2st part of secret-key + ) { + + return NULL; +} + +// vi: sts=2 sw=2 ts=2 diff --git a/protocols/Twitter/oauth/oauth.h b/protocols/Twitter/oauth/oauth.h new file mode 100644 index 0000000000..4efb472fc8 --- /dev/null +++ b/protocols/Twitter/oauth/oauth.h @@ -0,0 +1,764 @@ +/** + * @brief OAuth.net implementation in POSIX-C. + * @file oauth.h + * @author Robin Gareus <robin@gareus.org> + * + * Copyright 2007-2011 Robin Gareus <robin@gareus.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef _OAUTH_H +#define _OAUTH_H 1 + +#ifndef DOXYGEN_IGNORE +// liboauth version +#define LIBOAUTH_VERSION "0.9.6" +#define LIBOAUTH_VERSION_MAJOR 0 +#define LIBOAUTH_VERSION_MINOR 9 +#define LIBOAUTH_VERSION_MICRO 6 + +//interface revision number +//http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +#define LIBOAUTH_CUR 8 +#define LIBOAUTH_REV 3 +#define LIBOAUTH_AGE 8 +#endif + +#ifdef __GNUC__ +# define OA_GCC_VERSION_AT_LEAST(x,y) (__GNUC__ > x || __GNUC__ == x && __GNUC_MINOR__ >= y) +#else +# define OA_GCC_VERSION_AT_LEAST(x,y) 0 +#endif + +#ifndef attribute_deprecated +#if OA_GCC_VERSION_AT_LEAST(3,1) +# define attribute_deprecated __attribute__((deprecated)) +#else +# define attribute_deprecated +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** \enum OAuthMethod + * signature method to used for signing the request. + */ +typedef enum { + OA_HMAC=0, ///< use HMAC-SHA1 request signing method + OA_RSA, ///< use RSA signature + OA_PLAINTEXT ///< use plain text signature (for testing only) + } OAuthMethod; + +/** + * Base64 encode and return size data in 'src'. The caller must free the + * returned string. + * + * @param size The size of the data in src + * @param src The data to be base64 encode + * @return encoded string otherwise NULL + */ +char *oauth_encode_base64(int size, const unsigned char *src); + +/** + * Decode the base64 encoded string 'src' into the memory pointed to by + * 'dest'. + * + * @param dest Pointer to memory for holding the decoded string. + * Must be large enough to receive the decoded string. + * @param src A base64 encoded string. + * @return the length of the decoded string if decode + * succeeded otherwise 0. + */ +int oauth_decode_base64(unsigned char *dest, const char *src); + +/** + * Escape 'string' according to RFC3986 and + * http://oauth.net/core/1.0/#encoding_parameters. + * + * @param string The data to be encoded + * @return encoded string otherwise NULL + * The caller must free the returned string. + */ +char *oauth_url_escape(const char *string); + +/** + * Parse RFC3986 encoded 'string' back to unescaped version. + * + * @param string The data to be unescaped + * @param olen unless NULL the length of the returned string is stored there. + * @return decoded string or NULL + * The caller must free the returned string. + */ +char *oauth_url_unescape(const char *string, size_t *olen); + + +/** + * returns base64 encoded HMAC-SHA1 signature for + * given message and key. + * both data and key need to be urlencoded. + * + * the returned string needs to be freed by the caller + * + * @param m message to be signed + * @param k key used for signing + * @return signature string. + */ +char *oauth_sign_hmac_sha1 (const char *m, const char *k); + +/** + * same as \ref oauth_sign_hmac_sha1 but allows + * to specify length of message and key (in case they contain null chars). + * + * @param m message to be signed + * @param ml length of message + * @param k key used for signing + * @param kl length of key + * @return signature string. + */ +char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl); + +/** + * returns plaintext signature for the given key. + * + * the returned string needs to be freed by the caller + * + * @param m message to be signed + * @param k key used for signing + * @return signature string + */ +char *oauth_sign_plaintext (const char *m, const char *k); + +/** + * returns RSA-SHA1 signature for given data. + * the returned signature needs to be freed by the caller. + * + * @param m message to be signed + * @param k private-key PKCS and Base64-encoded + * @return base64 encoded signature string. + */ +char *oauth_sign_rsa_sha1 (const char *m, const char *k); + +/** + * verify RSA-SHA1 signature. + * + * returns the output of EVP_VerifyFinal() for a given message, + * cert/pubkey and signature. + * + * @param m message to be verified + * @param c public-key or x509 certificate + * @param s base64 encoded signature + * @return 1 for a correct signature, 0 for failure and -1 if some other error occurred + */ +int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *s); + +/** + * url-escape strings and concatenate with '&' separator. + * The number of strings to be concatenated must be + * given as first argument. + * all arguments thereafter must be of type (char *) + * + * @param len the number of arguments to follow this parameter + * + * @return pointer to memory holding the concatenated + * strings - needs to be free(d) by the caller. or NULL + * in case we ran out of memory. + */ +char *oauth_catenc(int len, ...); + +/** + * splits the given url into a parameter array. + * (see \ref oauth_serialize_url and \ref oauth_serialize_url_parameters for the reverse) + * (see \ref oauth_split_post_paramters for a more generic version) + * + * @param url the url or query-string to parse; may be NULL + * @param argv pointer to a (char *) array where the results are stored. + * The array is re-allocated to match the number of parameters and each + * parameter-string is allocated with strdup. - The memory needs to be freed + * by the caller. + * + * @return number of parameter(s) in array. + */ +int oauth_split_url_parameters(const char *url, char ***argv); + +/** + * splits the given url into a parameter array. + * (see \ref oauth_serialize_url and \ref oauth_serialize_url_parameters for the reverse) + * + * @param url the url or query-string to parse. + * @param argv pointer to a (char *) array where the results are stored. + * The array is re-allocated to match the number of parameters and each + * parameter-string is allocated with strdup. - The memory needs to be freed + * by the caller. + * @param qesc use query parameter escape (vs post-param-escape) - if set + * to 1 all '+' are treated as spaces ' ' + * + * @return number of parameter(s) in array. + */ +int oauth_split_post_paramters(const char *url, char ***argv, short qesc); + +/** + * build a url query string from an array. + * + * @param argc the total number of elements in the array + * @param start element in the array at which to start concatenating. + * @param argv parameter-array to concatenate. + * @return url string needs to be freed by the caller. + * + */ +char *oauth_serialize_url (int argc, int start, char **argv); + +/** + * encode query parameters from an array. + * + * @param argc the total number of elements in the array + * @param start element in the array at which to start concatenating. + * @param argv parameter-array to concatenate. + * @param sep separator for parameters (usually "&") + * @param mod - bitwise modifiers: + * 1: skip all values that start with "oauth_" + * 2: skip all values that don't start with "oauth_" + * 4: double quotation marks are added around values (use with sep ", " for HTTP Authorization header). + * @return url string needs to be freed by the caller. + */ +char *oauth_serialize_url_sep (int argc, int start, char **argv, char *sep, int mod); + +/** + * build a query parameter string from an array. + * + * This function is a shortcut for \ref oauth_serialize_url (argc, 1, argv). + * It strips the leading host/path, which is usually the first + * element when using oauth_split_url_parameters on an URL. + * + * @param argc the total number of elements in the array + * @param argv parameter-array to concatenate. + * @return url string needs to be freed by the caller. + */ +char *oauth_serialize_url_parameters (int argc, char **argv); + +/** + * generate a random string between 15 and 32 chars length + * and return a pointer to it. The value needs to be freed by the + * caller + * + * @return zero terminated random string. + */ +char *oauth_gen_nonce(); + +/** + * string compare function for oauth parameters. + * + * used with qsort. needed to normalize request parameters. + * see http://oauth.net/core/1.0/#anchor14 + */ +int oauth_cmpstringp(const void *p1, const void *p2); + + +/** + * search array for parameter key. + * @param argv length of array to search + * @param argc parameter array to search + * @param key key of parameter to check. + * + * @return FALSE (0) if array does not contain a parameter with given key, TRUE (1) otherwise. + */ +int oauth_param_exists(char **argv, int argc, char *key); + +/** + * add query parameter to array + * + * @param argcp pointer to array length int + * @param argvp pointer to array values + * @param addparam parameter to add (eg. "foo=bar") + */ +void oauth_add_param_to_array(int *argcp, char ***argvp, const char *addparam); + +/** + * free array args + * + * @param argcp pointer to array length int + * @param argvp pointer to array values to be free()d + */ +void oauth_free_array(int *argcp, char ***argvp); + +/** + * compare two strings in constant-time (as to not let an + * attacker guess how many leading chars are correct: + * http://rdist.root.org/2010/01/07/timing-independent-array-comparison/ ) + * + * @param a string to compare + * @param b string to compare + * @param len_a length of string a + * @param len_b length of string b + * + * returns 0 (false) if strings are not equal, and 1 (true) if strings are equal. + */ +int oauth_time_independent_equals_n(const char* a, const char* b, size_t len_a, size_t len_b); + +/** + * @deprecated Use oauth_time_independent_equals_n() instead. + */ +int oauth_time_indepenent_equals_n(const char* a, const char* b, size_t len_a, size_t len_b) attribute_deprecated; + +/** + * compare two strings in constant-time. + * wrapper to \ref oauth_time_independent_equals_n + * which calls strlen() for each argument. + * + * @param a string to compare + * @param b string to compare + * + * returns 0 (false) if strings are not equal, and 1 (true) if strings are equal. + */ +int oauth_time_independent_equals(const char* a, const char* b); + +/** + * @deprecated Use oauth_time_independent_equals() instead. + */ +int oauth_time_indepenent_equals(const char* a, const char* b) attribute_deprecated; + +/** + * calculate OAuth-signature for a given HTTP request URL, parameters and oauth-tokens. + * + * if 'postargs' is NULL a "GET" request is signed and the + * signed URL is returned. Else this fn will modify 'postargs' + * to point to memory that contains the signed POST-variables + * and returns the base URL. + * + * both, the return value and (if given) 'postargs' need to be freed + * by the caller. + * + * @param url The request URL to be signed. append all GET or POST + * query-parameters separated by either '?' or '&' to this parameter. + * + * @param postargs This parameter points to an area where the return value + * is stored. If 'postargs' is NULL, no value is stored. + * + * @param method specify the signature method to use. It is of type + * \ref OAuthMethod and most likely \ref OA_HMAC. + * + * @param http_method The HTTP request method to use (ie "GET", "PUT",..) + * If NULL is given as 'http_method' this defaults to "GET" when + * 'postargs' is also NULL and when postargs is not NULL "POST" is used. + * + * @param c_key consumer key + * @param c_secret consumer secret + * @param t_key token key + * @param t_secret token secret + * + * @return the signed url or NULL if an error occurred. + * + */ +char *oauth_sign_url2 (const char *url, char **postargs, + OAuthMethod method, + const char *http_method, //< HTTP request method + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ); + +/** + * @deprecated Use oauth_sign_url2() instead. + */ +char *oauth_sign_url (const char *url, char **postargs, + OAuthMethod method, + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ) attribute_deprecated; + + +/** + * the back-end behind by /ref oauth_sign_array2. + * however it does not serialize the signed URL again. + * The user needs to call /ref oauth_serialize_url (oA) + * and /ref oauth_free_array to do so. + * + * This allows to split parts of the URL to be used for + * OAuth HTTP Authorization header: + * see http://oauth.net/core/1.0a/#consumer_req_param + * the oauthtest2 example code does so. + * + * + * @param argcp pointer to array length int + * @param argvp pointer to array values + * (argv[0]="http://example.org:80/" argv[1]="first=QueryParamater" .. + * the array is modified: fi. oauth_ parameters are added) + * These arrays can be generated with /ref oauth_split_url_parameters + * or /ref oauth_split_post_paramters. + * + * @param postargs This parameter points to an area where the return value + * is stored. If 'postargs' is NULL, no value is stored. + * + * @param method specify the signature method to use. It is of type + * \ref OAuthMethod and most likely \ref OA_HMAC. + * + * @param http_method The HTTP request method to use (ie "GET", "PUT",..) + * If NULL is given as 'http_method' this defaults to "GET" when + * 'postargs' is also NULL and when postargs is not NULL "POST" is used. + * + * @param c_key consumer key + * @param c_secret consumer secret + * @param t_key token key + * @param t_secret token secret + * + * @return void + * + */ +void oauth_sign_array2_process (int *argcp, char***argvp, + char **postargs, + OAuthMethod method, + const char *http_method, //< HTTP request method + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ); + +/** + * same as /ref oauth_sign_url + * with the url already split into parameter array + * + * @param argcp pointer to array length int + * @param argvp pointer to array values + * (argv[0]="http://example.org:80/" argv[1]="first=QueryParamater" .. + * the array is modified: fi. oauth_ parameters are added) + * These arrays can be generated with /ref oauth_split_url_parameters + * or /ref oauth_split_post_paramters. + * + * @param postargs This parameter points to an area where the return value + * is stored. If 'postargs' is NULL, no value is stored. + * + * @param method specify the signature method to use. It is of type + * \ref OAuthMethod and most likely \ref OA_HMAC. + * + * @param http_method The HTTP request method to use (ie "GET", "PUT",..) + * If NULL is given as 'http_method' this defaults to "GET" when + * 'postargs' is also NULL and when postargs is not NULL "POST" is used. + * + * @param c_key consumer key + * @param c_secret consumer secret + * @param t_key token key + * @param t_secret token secret + * + * @return the signed url or NULL if an error occurred. + */ +char *oauth_sign_array2 (int *argcp, char***argvp, + char **postargs, + OAuthMethod method, + const char *http_method, //< HTTP request method + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ); + +/** + * @deprecated Use oauth_sign_array2() instead. + */ +char *oauth_sign_array (int *argcp, char***argvp, + char **postargs, + OAuthMethod method, + const char *c_key, //< consumer key - posted plain text + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_key, //< token key - posted plain text in URL + const char *t_secret //< token secret - used as 2st part of secret-key + ) attribute_deprecated; + + +/** + * calculate body hash (sha1sum) of given file and return + * a oauth_body_hash=xxxx parameter to be added to the request. + * The returned string needs to be freed by the calling function. + * + * see + * http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html + * + * @param filename the filename to calculate the hash for + * + * @return URL oauth_body_hash parameter string + */ +char *oauth_body_hash_file(char *filename); + +/** + * calculate body hash (sha1sum) of given data and return + * a oauth_body_hash=xxxx parameter to be added to the request. + * The returned string needs to be freed by the calling function. + * The returned string is not yet url-escaped and suitable to be + * passed as argument to \ref oauth_catenc. + * + * see + * http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html + * + * @param length length of the data parameter in bytes + * @param data to calculate the hash for + * + * @return URL oauth_body_hash parameter string + */ +char *oauth_body_hash_data(size_t length, const char *data); + +/** + * base64 encode digest, free it and return a URL parameter + * with the oauth_body_hash. The returned hash needs to be freed by the + * calling function. The returned string is not yet url-escaped and + * thus suitable to be passed to \ref oauth_catenc. + * + * @param len length of the digest to encode + * @param digest hash value to encode + * + * @return URL oauth_body_hash parameter string + */ +char *oauth_body_hash_encode(size_t len, unsigned char *digest); + +/** + * xep-0235 - TODO + */ +char *oauth_sign_xmpp (const char *xml, + OAuthMethod method, + const char *c_secret, //< consumer secret - used as 1st part of secret-key + const char *t_secret //< token secret - used as 2st part of secret-key + ); + +/** + * do a HTTP GET request, wait for it to finish + * and return the content of the reply. + * (requires libcurl or a command-line HTTP client) + * + * If compiled <b>without</b> libcurl this function calls + * a command-line executable defined in the environment variable + * OAUTH_HTTP_GET_CMD - it defaults to + * <tt>curl -sA 'liboauth-agent/0.1' '%%u'</tt> + * where %%u is replaced with the URL and query parameters. + * + * bash & wget example: + * <tt>export OAUTH_HTTP_CMD="wget -q -U 'liboauth-agent/0.1' '%u' "</tt> + * + * WARNING: this is a tentative function. it's convenient and handy for testing + * or developing OAuth code. But don't rely on this function + * to become a stable part of this API. It does not do + * much error checking or handling for one thing.. + * + * NOTE: \a u and \a q are just concatenated with a '?' in between, + * unless \a q is NULL. in which case only \a u will be used. + * + * @param u base url to get + * @param q query string to send along with the HTTP request or NULL. + * @return In case of an error NULL is returned; otherwise a pointer to the + * replied content from HTTP server. latter needs to be freed by caller. + */ +char *oauth_http_get (const char *u, const char *q); + +/** + * do a HTTP GET request, wait for it to finish + * and return the content of the reply. + * + * (requires libcurl) + * + * This is equivalent to /ref oauth_http_get but allows to + * specifiy a custom HTTP header and has + * has no support for commandline-curl. + * + * If liboauth is compiled <b>without</b> libcurl this function + * always returns NULL. + * + * @param u base url to get + * @param q query string to send along with the HTTP request or NULL. + * @param customheader specify custom HTTP header (or NULL for none) + * Multiple header elements can be passed separating them with "\r\n" + * @return In case of an error NULL is returned; otherwise a pointer to the + * replied content from HTTP server. latter needs to be freed by caller. + */ +char *oauth_http_get2 (const char *u, const char *q, const char *customheader); + + +/** + * do a HTTP POST request, wait for it to finish + * and return the content of the reply. + * (requires libcurl or a command-line HTTP client) + * + * If compiled <b>without</b> libcurl this function calls + * a command-line executable defined in the environment variable + * OAUTH_HTTP_CMD - it defaults to + * <tt>curl -sA 'liboauth-agent/0.1' -d '%%p' '%%u'</tt> + * where %%p is replaced with the postargs and %%u is replaced with + * the URL. + * + * bash & wget example: + * <tt>export OAUTH_HTTP_CMD="wget -q -U 'liboauth-agent/0.1' --post-data='%p' '%u' "</tt> + * + * NOTE: This function uses the curl's default HTTP-POST Content-Type: + * application/x-www-form-urlencoded which is the only option allowed + * by oauth core 1.0 spec. Experimental code can use the Environment variable + * to transmit custom HTTP headers or parameters. + * + * WARNING: this is a tentative function. it's convenient and handy for testing + * or developing OAuth code. But don't rely on this function + * to become a stable part of this API. It does not do + * much error checking for one thing.. + * + * @param u url to query + * @param p postargs to send along with the HTTP request. + * @return replied content from HTTP server. needs to be freed by caller. + */ +char *oauth_http_post (const char *u, const char *p); + +/** + * do a HTTP POST request, wait for it to finish + * and return the content of the reply. + * (requires libcurl) + * + * It's equivalent to /ref oauth_http_post, but offers + * the possibility to specify a custom HTTP header and + * has no support for commandline-curl. + * + * If liboauth is compiled <b>without</b> libcurl this function + * always returns NULL. + * + * @param u url to query + * @param p postargs to send along with the HTTP request. + * @param customheader specify custom HTTP header (or NULL for none) + * Multiple header elements can be passed separating them with "\r\n" + * @return replied content from HTTP server. needs to be freed by caller. + */ +char *oauth_http_post2 (const char *u, const char *p, const char *customheader); + + +/** + * http post raw data from file. + * the returned string needs to be freed by the caller + * (requires libcurl) + * + * see dislaimer: /ref oauth_http_post + * + * @param u url to retrieve + * @param fn filename of the file to post along + * @param len length of the file in bytes. set to '0' for autodetection + * @param customheader specify custom HTTP header (or NULL for default). + * Multiple header elements can be passed separating them with "\r\n" + * @return returned HTTP reply or NULL on error + */ +char *oauth_post_file (const char *u, const char *fn, const size_t len, const char *customheader); + +/** + * http post raw data + * the returned string needs to be freed by the caller + * (requires libcurl) + * + * see dislaimer: /ref oauth_http_post + * + * @param u url to retrieve + * @param data data to post + * @param len length of the data in bytes. + * @param customheader specify custom HTTP header (or NULL for default) + * Multiple header elements can be passed separating them with "\r\n" + * @return returned HTTP reply or NULL on error + */ +char *oauth_post_data (const char *u, const char *data, size_t len, const char *customheader); + +/** + * http post raw data, with callback. + * the returned string needs to be freed by the caller + * (requires libcurl) + * + * Invokes the callback - in no particular order - when HTTP-request status updates occur. + * The callback is called with: + * void * callback_data: supplied on function call. + * int type: 0=data received, 1=data sent. + * size_t size: amount of data received or amount of data sent so far + * size_t totalsize: original amount of data to send, or amount of data received + * + * @param u url to retrieve + * @param data data to post along + * @param len length of the file in bytes. set to '0' for autodetection + * @param customheader specify custom HTTP header (or NULL for default) + * Multiple header elements can be passed separating them with "\r\n" + * @param callback specify the callback function + * @param callback_data specify data to pass to the callback function + * @return returned HTTP reply or NULL on error + */ +char *oauth_post_data_with_callback (const char *u, + const char *data, + size_t len, + const char *customheader, + void (*callback)(void*,int,size_t,size_t), + void *callback_data); + +/** + * http send raw data. similar to /ref oauth_http_post but provides + * for specifying the HTTP request method. + * + * the returned string needs to be freed by the caller + * (requires libcurl) + * + * see dislaimer: /ref oauth_http_post + * + * @param u url to retrieve + * @param data data to post + * @param len length of the data in bytes. + * @param customheader specify custom HTTP header (or NULL for default) + * Multiple header elements can be passed separating them with "\r\n" + * @param httpMethod specify http verb ("GET"/"POST"/"PUT"/"DELETE") to be used. if httpMethod is NULL, a POST is executed. + * @return returned HTTP reply or NULL on error + */ +char *oauth_send_data (const char *u, + const char *data, + size_t len, + const char *customheader, + const char *httpMethod); + +/** + * http post raw data, with callback. + * the returned string needs to be freed by the caller + * (requires libcurl) + * + * Invokes the callback - in no particular order - when HTTP-request status updates occur. + * The callback is called with: + * void * callback_data: supplied on function call. + * int type: 0=data received, 1=data sent. + * size_t size: amount of data received or amount of data sent so far + * size_t totalsize: original amount of data to send, or amount of data received + * + * @param u url to retrieve + * @param data data to post along + * @param len length of the file in bytes. set to '0' for autodetection + * @param customheader specify custom HTTP header (or NULL for default) + * Multiple header elements can be passed separating them with "\r\n" + * @param callback specify the callback function + * @param callback_data specify data to pass to the callback function + * @param httpMethod specify http verb ("GET"/"POST"/"PUT"/"DELETE") to be used. + * @return returned HTTP reply or NULL on error + */ +char *oauth_send_data_with_callback (const char *u, + const char *data, + size_t len, + const char *customheader, + void (*callback)(void*,int,size_t,size_t), + void *callback_data, + const char *httpMethod); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif +/* vi:set ts=8 sts=2 sw=2: */ diff --git a/protocols/Twitter/oauth/oauth_http.c b/protocols/Twitter/oauth/oauth_http.c new file mode 100644 index 0000000000..0e06b10dae --- /dev/null +++ b/protocols/Twitter/oauth/oauth_http.c @@ -0,0 +1,728 @@ +/* + * OAuth http functions in POSIX-C. + * + * Copyright 2007, 2008, 2009, 2010 Robin Gareus <robin@gareus.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#ifdef WIN32 +# define snprintf _snprintf +#endif + +#include "xmalloc.h" +#include "oauth.h" + +#define OAUTH_USER_AGENT "liboauth-agent/" VERSION + +#ifdef HAVE_CURL /* HTTP requests via libcurl */ +#include <curl/curl.h> +#include <sys/stat.h> + +# define GLOBAL_CURL_ENVIROMENT_OPTIONS \ + if (getenv("CURLOPT_PROXYAUTH")){ \ + curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); \ + } \ + if (getenv("CURLOPT_SSL_VERIFYPEER")){ \ + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long) atol(getenv("CURLOPT_SSL_VERIFYPEER")) ); \ + } \ + if (getenv("CURLOPT_CAINFO")){ \ + curl_easy_setopt(curl, CURLOPT_CAINFO, getenv("CURLOPT_CAINFO") ); \ + } \ + if (getenv("CURLOPT_FOLLOWLOCATION")){ \ + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, (long) atol(getenv("CURLOPT_FOLLOWLOCATION")) ); \ + } \ + if (getenv("CURLOPT_FAILONERROR")){ \ + curl_easy_setopt(curl, CURLOPT_FAILONERROR, (long) atol(getenv("CURLOPT_FAILONERROR")) ); \ + } + +struct MemoryStruct { + char *data; + size_t size; //< bytes remaining (r), bytes accumulated (w) + + size_t start_size; //< only used with ..AndCall() + void (*callback)(void*,int,size_t,size_t); //< only used with ..AndCall() + void *callback_data; //< only used with ..AndCall() +}; + +static size_t +WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) { + size_t realsize = size * nmemb; + struct MemoryStruct *mem = (struct MemoryStruct *)data; + + mem->data = (char *)xrealloc(mem->data, mem->size + realsize + 1); + if (mem->data) { + memcpy(&(mem->data[mem->size]), ptr, realsize); + mem->size += realsize; + mem->data[mem->size] = 0; + } + return realsize; +} + +static size_t +ReadMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) { + struct MemoryStruct *mem = (struct MemoryStruct *)data; + size_t realsize = size * nmemb; + if (realsize > mem->size) realsize = mem->size; + memcpy(ptr, mem->data, realsize); + mem->size -= realsize; + mem->data += realsize; + return realsize; +} + +static size_t +WriteMemoryCallbackAndCall(void *ptr, size_t size, size_t nmemb, void *data) { + struct MemoryStruct *mem = (struct MemoryStruct *)data; + size_t ret=WriteMemoryCallback(ptr,size,nmemb,data); + mem->callback(mem->callback_data,0,mem->size,mem->size); + return ret; +} + +static size_t +ReadMemoryCallbackAndCall(void *ptr, size_t size, size_t nmemb, void *data) { + struct MemoryStruct *mem = (struct MemoryStruct *)data; + size_t ret=ReadMemoryCallback(ptr,size,nmemb,data); + mem->callback(mem->callback_data,1,mem->start_size-mem->size,mem->start_size); + return ret; +} + +/** + * cURL http post function. + * the returned string (if not NULL) needs to be freed by the caller + * + * @param u url to retrieve + * @param p post parameters + * @param customheader specify custom HTTP header (or NULL for none) + * @return returned HTTP + */ +char *oauth_curl_post (const char *u, const char *p, const char *customheader) { + CURL *curl; + CURLcode res; + struct curl_slist *slist=NULL; + + struct MemoryStruct chunk; + chunk.data=NULL; + chunk.size = 0; + + curl = curl_easy_init(); + if(!curl) return NULL; + curl_easy_setopt(curl, CURLOPT_URL, u); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, p); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + if (customheader) { + slist = curl_slist_append(slist, customheader); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + } + curl_easy_setopt(curl, CURLOPT_USERAGENT, OAUTH_USER_AGENT); +#ifdef OAUTH_CURL_TIMEOUT + curl_easy_setopt(curl, CURLOPT_TIMEOUT, OAUTH_CURL_TIMEOUT); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); +#endif + GLOBAL_CURL_ENVIROMENT_OPTIONS; + res = curl_easy_perform(curl); + curl_slist_free_all(slist); + if (res) { + return NULL; + } + + curl_easy_cleanup(curl); + return (chunk.data); +} + +/** + * cURL http get function. + * the returned string (if not NULL) needs to be freed by the caller + * + * @param u url to retrieve + * @param q optional query parameters + * @param customheader specify custom HTTP header (or NULL for none) + * @return returned HTTP + */ +char *oauth_curl_get (const char *u, const char *q, const char *customheader) { + CURL *curl; + CURLcode res; + struct curl_slist *slist=NULL; + char *t1=NULL; + struct MemoryStruct chunk; + + if (q) { + t1=(char*)xmalloc(sizeof(char)*(strlen(u)+strlen(q)+2)); + strcpy(t1,u); strcat(t1,"?"); strcat(t1,q); + } + + chunk.data=NULL; + chunk.size = 0; + + curl = curl_easy_init(); + if(!curl) return NULL; + curl_easy_setopt(curl, CURLOPT_URL, q?t1:u); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + if (customheader) { + slist = curl_slist_append(slist, customheader); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + } +#if 0 // TODO - support request methods.. + if (0) + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "HEAD"); + else if (0) + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); +#endif + curl_easy_setopt(curl, CURLOPT_USERAGENT, OAUTH_USER_AGENT); +#ifdef OAUTH_CURL_TIMEOUT + curl_easy_setopt(curl, CURLOPT_TIMEOUT, OAUTH_CURL_TIMEOUT); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); +#endif + GLOBAL_CURL_ENVIROMENT_OPTIONS; + res = curl_easy_perform(curl); + curl_slist_free_all(slist); + if (q) free(t1); + curl_easy_cleanup(curl); + + if (res) { + return NULL; + } + return (chunk.data); +} + +/** + * cURL http post raw data from file. + * the returned string needs to be freed by the caller + * + * @param u url to retrieve + * @param fn filename of the file to post along + * @param len length of the file in bytes. set to '0' for autodetection + * @param customheader specify custom HTTP header (or NULL for default) + * @return returned HTTP or NULL on error + */ +char *oauth_curl_post_file (const char *u, const char *fn, size_t len, const char *customheader) { + CURL *curl; + CURLcode res; + struct curl_slist *slist=NULL; + struct MemoryStruct chunk; + FILE *f; + + chunk.data=NULL; + chunk.size=0; + + if (customheader) + slist = curl_slist_append(slist, customheader); + else + slist = curl_slist_append(slist, "Content-Type: image/jpeg;"); + + if (!len) { + struct stat statbuf; + if (stat(fn, &statbuf) == -1) return(NULL); + len = statbuf.st_size; + } + + f = fopen(fn,"r"); + if (!f) return NULL; + + curl = curl_easy_init(); + if(!curl) return NULL; + curl_easy_setopt(curl, CURLOPT_URL, u); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + curl_easy_setopt(curl, CURLOPT_READDATA, f); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl, CURLOPT_USERAGENT, OAUTH_USER_AGENT); +#ifdef OAUTH_CURL_TIMEOUT + curl_easy_setopt(curl, CURLOPT_TIMEOUT, OAUTH_CURL_TIMEOUT); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); +#endif + GLOBAL_CURL_ENVIROMENT_OPTIONS; + res = curl_easy_perform(curl); + curl_slist_free_all(slist); + if (res) { + // error + return NULL; + } + fclose(f); + + curl_easy_cleanup(curl); + return (chunk.data); +} + +/** + * http send raw data, with callback. + * the returned string needs to be freed by the caller + * + * more documentation in oauth.h + * + * @param u url to retrieve + * @param data data to post along + * @param len length of the file in bytes. set to '0' for autodetection + * @param customheader specify custom HTTP header (or NULL for default) + * @param callback specify the callback function + * @param callback_data specify data to pass to the callback function + * @return returned HTTP reply or NULL on error + */ +char *oauth_curl_send_data_with_callback (const char *u, const char *data, size_t len, const char *customheader, void (*callback)(void*,int,size_t,size_t), void *callback_data, const char *httpMethod) { + CURL *curl; + CURLcode res; + struct curl_slist *slist=NULL; + struct MemoryStruct chunk; + struct MemoryStruct rdnfo; + + chunk.data=NULL; + chunk.size=0; + chunk.start_size=0; + chunk.callback=callback; + chunk.callback_data=callback_data; + rdnfo.data=(char *)data; + rdnfo.size=len; + rdnfo.start_size=len; + rdnfo.callback=callback; + rdnfo.callback_data=callback_data; + + if (customheader) + slist = curl_slist_append(slist, customheader); + else + slist = curl_slist_append(slist, "Content-Type: image/jpeg;"); + + curl = curl_easy_init(); + if(!curl) return NULL; + curl_easy_setopt(curl, CURLOPT_URL, u); + curl_easy_setopt(curl, CURLOPT_POST, 1); + if (httpMethod) curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, httpMethod); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + curl_easy_setopt(curl, CURLOPT_READDATA, (void *)&rdnfo); + if (callback) + curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadMemoryCallbackAndCall); + else + curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadMemoryCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + if (callback) + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallbackAndCall); + else + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl, CURLOPT_USERAGENT, OAUTH_USER_AGENT); +#ifdef OAUTH_CURL_TIMEOUT + curl_easy_setopt(curl, CURLOPT_TIMEOUT, OAUTH_CURL_TIMEOUT); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); +#endif + GLOBAL_CURL_ENVIROMENT_OPTIONS; + res = curl_easy_perform(curl); + curl_slist_free_all(slist); + if (res) { + // error + return NULL; + } + + curl_easy_cleanup(curl); + return (chunk.data); +} + +/** + * http post raw data. + * the returned string needs to be freed by the caller + * + * more documentation in oauth.h + * + * @param u url to retrieve + * @param data data to post along + * @param len length of the file in bytes. set to '0' for autodetection + * @param customheader specify custom HTTP header (or NULL for default) + * @return returned HTTP reply or NULL on error + */ +char *oauth_curl_post_data(const char *u, const char *data, size_t len, const char *customheader) { + return oauth_curl_send_data_with_callback(u, data, len, customheader, NULL, NULL, NULL); +} + +char *oauth_curl_send_data (const char *u, const char *data, size_t len, const char *customheader, const char *httpMethod) { + return oauth_curl_send_data_with_callback(u, data, len, customheader, NULL, NULL, httpMethod); +} + +char *oauth_curl_post_data_with_callback (const char *u, const char *data, size_t len, const char *customheader, void (*callback)(void*,int,size_t,size_t), void *callback_data) { + return oauth_curl_send_data_with_callback(u, data, len, customheader, callback, callback_data, NULL); +} + +#endif // libcURL. + + +#ifdef HAVE_SHELL_CURL /* HTTP requests via command-line curl */ + +// command line presets and ENV variable name +#define _OAUTH_ENV_HTTPCMD "OAUTH_HTTP_CMD" +#define _OAUTH_ENV_HTTPGET "OAUTH_HTTP_GET_CMD" + +#ifdef OAUTH_CURL_TIMEOUT + +#define cpxstr(s) cpstr(s) +#define cpstr(s) #s + +#ifndef _OAUTH_DEF_HTTPCMD +# define _OAUTH_DEF_HTTPCMD "curl -sA '"OAUTH_USER_AGENT"' -m "cpxstr(OAUTH_CURL_TIMEOUT)" -d '%p' '%u' " +//alternative: "wget -q -U 'liboauth-agent/0.1' --post-data='%p' '%u' " +#endif + +#ifndef _OAUTH_DEF_HTTPGET +# define _OAUTH_DEF_HTTPGET "curl -sA '"OAUTH_USER_AGENT"' -m "cpxstr(OAUTH_CURL_TIMEOUT)" '%u' " +//alternative: "wget -q -U 'liboauth-agent/0.1' '%u' " +#endif + +#else // no timeout + +#ifndef _OAUTH_DEF_HTTPCMD +# define _OAUTH_DEF_HTTPCMD "curl -sA '"OAUTH_USER_AGENT"' -d '%p' '%u' " +//alternative: "wget -q -U 'liboauth-agent/0.1' --post-data='%p' '%u' " +#endif + +#ifndef _OAUTH_DEF_HTTPGET +# define _OAUTH_DEF_HTTPGET "curl -sA '"OAUTH_USER_AGENT"' '%u' " +//alternative: "wget -q -U 'liboauth-agent/0.1' '%u' " +#endif + +#endif + +#include <stdio.h> + +/** + * escape URL for use in String Quotes (aka shell single quotes). + * the returned string needs to be free()d by the calling function + * + * WARNING: this function only escapes single-quotes (') + * + * + * RFC2396 defines the following + * reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," + * besides alphanum the following are allowed as unreserved: + * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + * + * checking `echo '-_.!~*()'` it seems we + * just need to escape the tick (') itself from "'" to "'\''" + * + * In C shell, the "!" character may need a backslash before it. + * It depends on the characters next to it. If it is surrounded by spaces, + * you don't need to use a backslash. + * (here: we'd always need to escape it for c shell) + * @todo: escape '!' for c-shell curl/wget commandlines + * + * @param cmd URI string or parameter to be escaped + * @return escaped parameter + */ +char *oauth_escape_shell (const char *cmd) { + char *esc = xstrdup(cmd); + char *tmp = esc; + int idx; + while ((tmp=strchr(tmp,'\''))) { + idx = tmp-esc; + esc=(char*)xrealloc(esc,(strlen(esc)+5)*sizeof(char)); + memmove(esc+idx+4,esc+idx+1, strlen(esc+idx)); + esc[idx+1]='\\'; esc[idx+2]='\''; esc[idx+3]='\''; + tmp=esc+(idx+4); + } + +// TODO escape '!' if CSHELL ?! + + return esc; +} + +/** + * execute command via shell and return it's output. + * This is used to call 'curl' or 'wget'. + * the command is uses <em>as is</em> and needs to be propery escaped. + * + * @param cmd the commandline to execute + * @return stdout string that needs to be freed or NULL if there's no output + */ +char *oauth_exec_shell (const char *cmd) { +#ifdef DEBUG_OAUTH + printf("DEBUG: executing: %s\n",cmd); +#endif + FILE *in = popen (cmd, "r"); + size_t len = 0; + size_t alloc = 0; + char *data = NULL; + int rcv = 1; + while (in && rcv > 0 && !feof(in)) { + alloc +=1024; + data = (char*)xrealloc(data, alloc * sizeof(char)); + rcv = fread(data + (alloc-1024), sizeof(char), 1024, in); + len += rcv; + } + pclose(in); +#ifdef DEBUG_OAUTH + printf("DEBUG: read %i bytes\n",len); +#endif + data[len]=0; +#ifdef DEBUG_OAUTH + if (data) printf("DEBUG: return: %s\n",data); + else printf("DEBUG: NULL data\n"); +#endif + return (data); +} + +/** + * send POST via a command line HTTP client, wait for it to finish + * and return the content of the reply. requires a command-line HTTP client + * + * see \ref oauth_http_post + * + * @param u url to query + * @param p postargs to send along with the HTTP request. + * @return In case of an error NULL is returned; otherwise a pointer to the + * replied content from HTTP server. latter needs to be freed by caller. + */ +char *oauth_exec_post (const char *u, const char *p) { + char cmd[BUFSIZ]; + char *t1,*t2; + char *cmdtpl = getenv(_OAUTH_ENV_HTTPCMD); + if (!cmdtpl) cmdtpl = xstrdup (_OAUTH_DEF_HTTPCMD); + else cmdtpl = xstrdup (cmdtpl); // clone getenv() string. + + // add URL and post param - error if no '%p' or '%u' present in definition + t1=strstr(cmdtpl, "%p"); + t2=strstr(cmdtpl, "%u"); + if (!t1 || !t2) { + fprintf(stderr, "\nliboauth: invalid HTTP command. set the '%s' environment variable.\n\n",_OAUTH_ENV_HTTPCMD); + return(NULL); + } + // TODO: check if there are exactly two '%' in cmdtpl + *(++t1)= 's'; *(++t2)= 's'; + if (t1>t2) { + t1=oauth_escape_shell(u); + t2=oauth_escape_shell(p); + } else { + t1=oauth_escape_shell(p); + t2=oauth_escape_shell(u); + } + snprintf(cmd, BUFSIZ, cmdtpl, t1, t2); + free(cmdtpl); + free(t1); free(t2); + return oauth_exec_shell(cmd); +} + +/** + * send GET via a command line HTTP client + * and return the content of the reply.. + * requires a command-line HTTP client. + * + * Note: u and q are just concatenated with a '?' in between unless q is NULL. in which case only u will be used. + * + * see \ref oauth_http_get + * + * @param u base url to get + * @param q query string to send along with the HTTP request. + * @return In case of an error NULL is returned; otherwise a pointer to the + * replied content from HTTP server. latter needs to be freed by caller. + */ +char *oauth_exec_get (const char *u, const char *q) { + char cmd[BUFSIZ]; + char *cmdtpl, *t1, *e1; + + if (!u) return (NULL); + + cmdtpl = getenv(_OAUTH_ENV_HTTPGET); + if (!cmdtpl) cmdtpl = xstrdup (_OAUTH_DEF_HTTPGET); + else cmdtpl = xstrdup (cmdtpl); // clone getenv() string. + + // add URL and post param - error if no '%p' or '%u' present in definition + t1=strstr(cmdtpl, "%u"); + if (!t1) { + fprintf(stderr, "\nliboauth: invalid HTTP command. set the '%s' environment variable.\n\n",_OAUTH_ENV_HTTPGET); + return(NULL); + } + *(++t1)= 's'; + + e1 = oauth_escape_shell(u); + if (q) { + char *e2; + e2 = oauth_escape_shell(q); + t1=(char*)xmalloc(sizeof(char)*(strlen(e1)+strlen(e2)+2)); + strcpy(t1,e1); strcat(t1,"?"); strcat(t1,e2); + free(e2); + } + snprintf(cmd, BUFSIZ, cmdtpl, q?t1:e1); + free(cmdtpl); + free(e1); + if (q) free(t1); + return oauth_exec_shell(cmd); +} +#endif // command-line curl. + +/* wrapper functions */ + +/** + * do a HTTP GET request, wait for it to finish + * and return the content of the reply. + * (requires libcurl or a command-line HTTP client) + * + * more documentation in oauth.h + * + * @param u base url to get + * @param q query string to send along with the HTTP request or NULL. + * @return In case of an error NULL is returned; otherwise a pointer to the + * replied content from HTTP server. latter needs to be freed by caller. + */ +char *oauth_http_get (const char *u, const char *q) { +#ifdef HAVE_CURL + return oauth_curl_get(u,q,NULL); +#elif defined(HAVE_SHELL_CURL) + return oauth_exec_get(u,q); +#else + return NULL; +#endif +} + +/** + * do a HTTP GET request, wait for it to finish + * and return the content of the reply. + * (requires libcurl) + * + * @param u base url to get + * @param q query string to send along with the HTTP request or NULL. + * @param customheader specify custom HTTP header (or NULL for none) + * @return In case of an error NULL is returned; otherwise a pointer to the + * replied content from HTTP server. latter needs to be freed by caller. + */ +char *oauth_http_get2 (const char *u, const char *q, const char *customheader) { +#ifdef HAVE_CURL + return oauth_curl_get(u,q,customheader); +#else + return NULL; +#endif +} + +/** + * do a HTTP POST request, wait for it to finish + * and return the content of the reply. + * (requires libcurl or a command-line HTTP client) + * + * more documentation in oauth.h + * + * @param u url to query + * @param p postargs to send along with the HTTP request. + * @return In case of an error NULL is returned; otherwise a pointer to the + * replied content from HTTP server. latter needs to be freed by caller. + */ +char *oauth_http_post (const char *u, const char *p) { +#ifdef HAVE_CURL + return oauth_curl_post(u,p,NULL); +#elif defined(HAVE_SHELL_CURL) + return oauth_exec_post(u,p); +#else + return NULL; +#endif +} + + +/** + * do a HTTP POST request, wait for it to finish + * and return the content of the reply. + * (requires libcurl) + * + * more documentation in oauth.h + * + * @param u url to query + * @param p postargs to send along with the HTTP request. + * @param customheader specify custom HTTP header (or NULL for none) + * @return In case of an error NULL is returned; otherwise a pointer to the + * replied content from HTTP server. latter needs to be freed by caller. + */ +char *oauth_http_post2 (const char *u, const char *p, const char *customheader) { +#ifdef HAVE_CURL + return oauth_curl_post(u,p,customheader); +#else + return NULL; +#endif +} + +/** + * http post raw data from file. + * the returned string needs to be freed by the caller + * + * more documentation in oauth.h + * + * @param u url to retrieve + * @param fn filename of the file to post along + * @param len length of the file in bytes. set to '0' for autodetection + * @param customheader specify custom HTTP header (or NULL for default) + * @return returned HTTP reply or NULL on error + */ +char *oauth_post_file (const char *u, const char *fn, const size_t len, const char *customheader){ +#ifdef HAVE_CURL + return oauth_curl_post_file (u, fn, len, customheader); +#elif defined(HAVE_SHELL_CURL) + fprintf(stderr, "\nliboauth: oauth_post_file requires libcurl. libcurl is not available.\n\n"); + return NULL; +#else + return NULL; +#endif +} + +/** + * http post raw data. + * the returned string needs to be freed by the caller + * + * more documentation in oauth.h + * + * @param u url to retrieve + * @param data data to post along + * @param len length of the file in bytes. set to '0' for autodetection + * @param customheader specify custom HTTP header (or NULL for default) + * @return returned HTTP reply or NULL on error + */ +char *oauth_post_data (const char *u, const char *data, size_t len, const char *customheader) { +#ifdef HAVE_CURL + return oauth_curl_post_data (u, data, len, customheader); +#elif defined(HAVE_SHELL_CURL) + fprintf(stderr, "\nliboauth: oauth_post_file requires libcurl. libcurl is not available.\n\n"); + return NULL; +#else + return (NULL); +#endif +} + +char *oauth_send_data (const char *u, const char *data, size_t len, const char *customheader, const char *httpMethod) { +#ifdef HAVE_CURL + return oauth_curl_send_data (u, data, len, customheader, httpMethod); +#elif defined(HAVE_SHELL_CURL) + fprintf(stderr, "\nliboauth: oauth_send_file requires libcurl. libcurl is not available.\n\n"); + return NULL; +#else + return (NULL); +#endif +} + +char *oauth_post_data_with_callback (const char *u, const char *data, size_t len, const char *customheader, void (*callback)(void*,int,size_t,size_t), void *callback_data) { +#ifdef HAVE_CURL + return oauth_curl_post_data_with_callback(u, data, len, customheader, callback, callback_data); +#elif defined(HAVE_SHELL_CURL) + fprintf(stderr, "\nliboauth: oauth_post_data_with_callback requires libcurl.\n\n"); + return NULL; +#else + return (NULL); +#endif +} +/* vi:set ts=8 sts=2 sw=2: */ diff --git a/protocols/Twitter/oauth/sha1.c b/protocols/Twitter/oauth/sha1.c new file mode 100644 index 0000000000..c3189008ac --- /dev/null +++ b/protocols/Twitter/oauth/sha1.c @@ -0,0 +1,317 @@ +/* This code is public-domain - it is based on libcrypt + * placed in the public domain by Wei Dai and other contributors. + */ +// gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test + +#include <stdint.h> +#include <string.h> + +/* header */ + +#define HASH_LENGTH 20 +#define BLOCK_LENGTH 64 + +union _buffer { + uint8_t b[BLOCK_LENGTH]; + uint32_t w[BLOCK_LENGTH/4]; +}; + +union _state { + uint8_t b[HASH_LENGTH]; + uint32_t w[HASH_LENGTH/4]; +}; + +typedef struct sha1nfo { + union _buffer buffer; + uint8_t bufferOffset; + union _state state; + uint32_t byteCount; + uint8_t keyBuffer[BLOCK_LENGTH]; + uint8_t innerHash[HASH_LENGTH]; +} sha1nfo; + +/* public API - prototypes - TODO: doxygen*/ + +/** + */ +void sha1_init(sha1nfo *s); +/** + */ +void sha1_writebyte(sha1nfo *s, uint8_t data); +/** + */ +void sha1_write(sha1nfo *s, const char *data, size_t len); +/** + */ +uint8_t* sha1_result(sha1nfo *s); +/** + */ +void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); +/** + */ +uint8_t* sha1_resultHmac(sha1nfo *s); + + +/* code */ +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +const uint8_t sha1InitState[] = { + 0x01,0x23,0x45,0x67, // H0 + 0x89,0xab,0xcd,0xef, // H1 + 0xfe,0xdc,0xba,0x98, // H2 + 0x76,0x54,0x32,0x10, // H3 + 0xf0,0xe1,0xd2,0xc3 // H4 +}; + +void sha1_init(sha1nfo *s) { + memcpy(s->state.b,sha1InitState,HASH_LENGTH); + s->byteCount = 0; + s->bufferOffset = 0; +} + +uint32_t sha1_rol32(uint32_t number, uint8_t bits) { + return ((number << bits) | (number >> (32-bits))); +} + +void sha1_hashBlock(sha1nfo *s) { + uint8_t i; + uint32_t a,b,c,d,e,t; + + a=s->state.w[0]; + b=s->state.w[1]; + c=s->state.w[2]; + d=s->state.w[3]; + e=s->state.w[4]; + for (i=0; i<80; i++) { + if (i>=16) { + t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15]; + s->buffer.w[i&15] = sha1_rol32(t,1); + } + if (i<20) { + t = (d ^ (b & (c ^ d))) + SHA1_K0; + } else if (i<40) { + t = (b ^ c ^ d) + SHA1_K20; + } else if (i<60) { + t = ((b & c) | (d & (b | c))) + SHA1_K40; + } else { + t = (b ^ c ^ d) + SHA1_K60; + } + t+=sha1_rol32(a,5) + e + s->buffer.w[i&15]; + e=d; + d=c; + c=sha1_rol32(b,30); + b=a; + a=t; + } + s->state.w[0] += a; + s->state.w[1] += b; + s->state.w[2] += c; + s->state.w[3] += d; + s->state.w[4] += e; +} + +void sha1_addUncounted(sha1nfo *s, uint8_t data) { + s->buffer.b[s->bufferOffset ^ 3] = data; + s->bufferOffset++; + if (s->bufferOffset == BLOCK_LENGTH) { + sha1_hashBlock(s); + s->bufferOffset = 0; + } +} + +void sha1_writebyte(sha1nfo *s, uint8_t data) { + ++s->byteCount; + sha1_addUncounted(s, data); +} + +void sha1_write(sha1nfo *s, const char *data, size_t len) { + for (;len--;) sha1_writebyte(s, (uint8_t) *data++); +} + +void sha1_pad(sha1nfo *s) { + // Implement SHA-1 padding (fips180-2 ยง5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + sha1_addUncounted(s, 0x80); + while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); + + // Append length in the last 8 bytes + sha1_addUncounted(s, 0); // We're only using 32 bit lengths + sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths + sha1_addUncounted(s, 0); // So zero pad the top bits + sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 + sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as + sha1_addUncounted(s, s->byteCount >> 13); // byte. + sha1_addUncounted(s, s->byteCount >> 5); + sha1_addUncounted(s, s->byteCount << 3); +} + +uint8_t* sha1_result(sha1nfo *s) { + int i; + // Pad to complete the last block + sha1_pad(s); + + // Swap byte order back + for (i=0; i<5; i++) { + uint32_t a,b; + a=s->state.w[i]; + b=a<<24; + b|=(a<<8) & 0x00ff0000; + b|=(a>>8) & 0x0000ff00; + b|=a>>24; + s->state.w[i]=b; + } + + // Return pointer to hash (20 characters) + return s->state.b; +} + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c + +void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { + uint8_t i; + memset(s->keyBuffer, 0, BLOCK_LENGTH); + if (keyLength > BLOCK_LENGTH) { + // Hash long keys + sha1_init(s); + for (;keyLength--;) sha1_writebyte(s, *key++); + memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); + } else { + // Block length keys are used as is + memcpy(s->keyBuffer, key, keyLength); + } + // Start inner hash + sha1_init(s); + for (i=0; i<BLOCK_LENGTH; i++) { + sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD); + } +} + +uint8_t* sha1_resultHmac(sha1nfo *s) { + uint8_t i; + // Complete inner hash + memcpy(s->innerHash,sha1_result(s),HASH_LENGTH); + // Calculate outer hash + sha1_init(s); + for (i=0; i<BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD); + for (i=0; i<HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]); + return sha1_result(s); +} + +/* self-test */ + +#if SHA1TEST +#include <stdio.h> + +uint8_t hmacKey1[]={ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f +}; +uint8_t hmacKey2[]={ + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, + 0x40,0x41,0x42,0x43 +}; +uint8_t hmacKey3[]={ + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, + 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, + 0xb0,0xb1,0xb2,0xb3 +}; +uint8_t hmacKey4[]={ + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, + 0xa0 +}; + +void printHash(uint8_t* hash) { + int i; + for (i=0; i<20; i++) { + printf("%02x", hash[i]); + } + printf("\n"); +} + + +int main (int argc, char **argv) { + uint32_t a; + sha1nfo s; + + // SHA tests + printf("Test: FIPS 180-2 C.1 and RFC3174 7.3 TEST1\n"); + printf("Expect:a9993e364706816aba3e25717850c26c9cd0d89d\n"); + printf("Result:"); + sha1_init(&s); + sha1_write(&s, "abc", 3); + printHash(sha1_result(&s)); + printf("\n\n"); + + printf("Test: FIPS 180-2 C.2 and RFC3174 7.3 TEST2\n"); + printf("Expect:84983e441c3bd26ebaae4aa1f95129e5e54670f1\n"); + printf("Result:"); + sha1_init(&s); + sha1_write(&s, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56); + printHash(sha1_result(&s)); + printf("\n\n"); + + printf("Test: RFC3174 7.3 TEST4\n"); + printf("Expect:dea356a2cddd90c7a7ecedc5ebb563934f460452\n"); + printf("Result:"); + sha1_init(&s); + for (a=0; a<80; a++) sha1_write(&s, "01234567", 8); + printHash(sha1_result(&s)); + printf("\n\n"); + + // HMAC tests + printf("Test: FIPS 198a A.1\n"); + printf("Expect:4f4ca3d5d68ba7cc0a1208c9c61e9c5da0403c0a\n"); + printf("Result:"); + sha1_initHmac(&s, hmacKey1, 64); + sha1_write(&s, "Sample #1",9); + printHash(sha1_resultHmac(&s)); + printf("\n\n"); + + printf("Test: FIPS 198a A.2\n"); + printf("Expect:0922d3405faa3d194f82a45830737d5cc6c75d24\n"); + printf("Result:"); + sha1_initHmac(&s, hmacKey2, 20); + sha1_write(&s, "Sample #2", 9); + printHash(sha1_resultHmac(&s)); + printf("\n\n"); + + printf("Test: FIPS 198a A.3\n"); + printf("Expect:bcf41eab8bb2d802f3d05caf7cb092ecf8d1a3aa\n"); + printf("Result:"); + sha1_initHmac(&s, hmacKey3,100); + sha1_write(&s, "Sample #3", 9); + printHash(sha1_resultHmac(&s)); + printf("\n\n"); + + printf("Test: FIPS 198a A.4\n"); + printf("Expect:9ea886efe268dbecce420c7524df32e0751a2a26\n"); + printf("Result:"); + sha1_initHmac(&s, hmacKey4,49); + sha1_write(&s, "Sample #4", 9); + printHash(sha1_resultHmac(&s)); + printf("\n\n"); + + // Long tests + printf("Test: FIPS 180-2 C.3 and RFC3174 7.3 TEST3\n"); + printf("Expect:34aa973cd4c4daa4f61eeb2bdbad27316534016f\n"); + printf("Result:"); + sha1_init(&s); + for (a=0; a<1000000; a++) sha1_writebyte(&s, 'a'); + printHash(sha1_result(&s)); + + return 0; +} +#endif /* self-test */ diff --git a/protocols/Twitter/oauth/xmalloc.c b/protocols/Twitter/oauth/xmalloc.c new file mode 100644 index 0000000000..f831e13895 --- /dev/null +++ b/protocols/Twitter/oauth/xmalloc.c @@ -0,0 +1,60 @@ +/* xmalloc.c -- memory allocation including 'out of memory' checks + * + * Copyright 2010 Robin Gareus <robin@gareus.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include <stdio.h> +#include <sys/types.h> +#include <string.h> +#include <stdlib.h> + +static void *xmalloc_fatal(size_t size) { + if (size==0) return NULL; + fprintf(stderr, "Out of memory."); + exit(1); +} + +void *xmalloc (size_t size) { + void *ptr = malloc (size); + if (ptr == NULL) return xmalloc_fatal(size); + return ptr; +} + +void *xcalloc (size_t nmemb, size_t size) { + void *ptr = calloc (nmemb, size); + if (ptr == NULL) return xmalloc_fatal(nmemb*size); + return ptr; +} + +void *xrealloc (void *ptr, size_t size) { + void *p = realloc (ptr, size); + if (p == NULL) return xmalloc_fatal(size); + return p; +} + +char *xstrdup (const char *s) { + void *ptr = xmalloc(strlen(s)+1); + strcpy (ptr, s); + return (char*) ptr; +} + +// vi: sts=2 sw=2 ts=2 diff --git a/protocols/Twitter/oauth/xmalloc.h b/protocols/Twitter/oauth/xmalloc.h new file mode 100644 index 0000000000..7ec9b61fdd --- /dev/null +++ b/protocols/Twitter/oauth/xmalloc.h @@ -0,0 +1,10 @@ +#ifndef _OAUTH_XMALLOC_H +#define _OAUTH_XMALLOC_H 1 + +/* Prototypes for functions defined in xmalloc.c */ +void *xmalloc (size_t size); +void *xcalloc (size_t nmemb, size_t size); +void *xrealloc (void *ptr, size_t size); +char *xstrdup (const char *s); + +#endif |