#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <curl/curl.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
};
struct session {
+ char *password;
+ char *account;
char *consumer_key;
char *consumer_secret;
char *access_token_key;
int shrink_urls;
int dry_run;
int page;
+ int no_oauth;
enum host host;
enum action action;
void *readline_handle;
char *(*readline)(const char *);
};
+struct bti_curl_buffer {
+ char *data;
+ enum action action;
+ int length;
+};
+
static void display_help(void)
{
fprintf(stdout, "bti - send tweet to twitter or identi.ca\n");
fprintf(stdout, "Usage:\n");
fprintf(stdout, " bti [options]\n");
fprintf(stdout, "options are:\n");
+ fprintf(stdout, " --account accountname\n");
+ fprintf(stdout, " --password password\n");
fprintf(stdout, " --action action\n");
fprintf(stdout, " ('update', 'friends', 'public', 'replies', "
"'group' or 'user')\n");
{
if (!session)
return;
+ free(session->password);
+ free(session->account);
free(session->consumer_key);
free(session->consumer_secret);
free(session->access_token_key);
free(session);
}
+static struct bti_curl_buffer *bti_curl_buffer_alloc(enum action action)
+{
+ struct bti_curl_buffer *buffer;
+
+ buffer = zalloc(sizeof(*buffer));
+ if (!buffer)
+ return NULL;
+
+ /* start out with a data buffer of 1 byte to
+ * make the buffer fill logic simpler */
+ buffer->data = zalloc(1);
+ if (!buffer->data) {
+ free(buffer);
+ return NULL;
+ }
+ buffer->length = 0;
+ buffer->action = action;
+ return buffer;
+}
+
+static void bti_curl_buffer_free(struct bti_curl_buffer *buffer)
+{
+ if (!buffer)
+ return;
+ free(buffer->data);
+ free(buffer);
+}
+
static const char *twitter_host = "http://api.twitter.com/1/statuses";
static const char *identica_host = "https://identi.ca/api/statuses";
static const char *twitter_name = "twitter";
static const char *public_uri = "/public_timeline.xml";
static const char *friends_uri = "/friends_timeline.xml";
static const char *mentions_uri = "/mentions.xml";
-//static const char *group_uri = "/../laconica/groups/timeline/";
+static const char *replies_uri = "/replies.xml";
+static const char *group_uri = "/../statusnet/groups/timeline/";
+
+static CURL *curl_init(void)
+{
+ CURL *curl;
+
+ curl = curl_easy_init();
+ if (!curl) {
+ fprintf(stderr, "Can not init CURL!\n");
+ return NULL;
+ }
+ /* some ssl sanity checks on the connection we are making */
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+ return curl;
+}
static void parse_statuses(xmlDocPtr doc, xmlNodePtr current)
{
return;
}
+static size_t curl_callback(void *buffer, size_t size, size_t nmemb,
+ void *userp)
+{
+ struct bti_curl_buffer *curl_buf = userp;
+ size_t buffer_size = size * nmemb;
+ char *temp;
+
+ if ((!buffer) || (!buffer_size) || (!curl_buf))
+ return -EINVAL;
+
+ /* add to the data we already have */
+ temp = zalloc(curl_buf->length + buffer_size + 1);
+ if (!temp)
+ return -ENOMEM;
+
+ memcpy(temp, curl_buf->data, curl_buf->length);
+ free(curl_buf->data);
+ curl_buf->data = temp;
+ memcpy(&curl_buf->data[curl_buf->length], (char *)buffer, buffer_size);
+ curl_buf->length += buffer_size;
+ if (curl_buf->action)
+ parse_timeline(curl_buf->data);
+
+ dbg("%s\n", curl_buf->data);
+
+ return buffer_size;
+}
+
static int parse_osp_reply(const char *reply, char **token, char **secret)
{
int rc;
}
}
+ dbg("token: %s\n", *token);
+ dbg("secret: %s\n", *secret);
+
if (rv)
free(rv);
static int send_request(struct session *session)
{
char endpoint[500];
+ char user_password[500];
+ char data[500];
+ struct bti_curl_buffer *curl_buf;
+ CURL *curl = NULL;
+ CURLcode res;
+ struct curl_httppost *formpost = NULL;
+ struct curl_httppost *lastptr = NULL;
+ struct curl_slist *slist = NULL;
char *req_url = NULL;
char *reply = NULL;
char *postarg = NULL;
if (!session->hosturl)
session->hosturl = strdup(twitter_host);
- switch (session->action) {
- case ACTION_UPDATE:
- sprintf(endpoint, "%s%s?status=%s", session->hosturl, update_uri, session->tweet);
- is_post = 1;
- break;
- case ACTION_USER:
- sprintf(endpoint, "%s%s", session->hosturl, user_uri);
- break;
- case ACTION_REPLIES:
- sprintf(endpoint, "%s%s", session->hosturl, mentions_uri);
- break;
- case ACTION_PUBLIC:
- sprintf(endpoint, "%s%s", session->hosturl, public_uri);
- break;
- case ACTION_GROUP:
- case ACTION_FRIENDS:
- sprintf(endpoint, "%s%s", session->hosturl, friends_uri);
- break;
- default:
- break;
- }
-
- if (is_post) {
- req_url = oauth_sign_url2(
- endpoint, &postarg, OA_HMAC, NULL,
- session->consumer_key, session->consumer_secret,
- session->access_token_key, session->access_token_secret
- );
- reply = oauth_http_post(req_url, postarg);
+ if (session->no_oauth) {
+ curl_buf = bti_curl_buffer_alloc(session->action);
+ if (!curl_buf)
+ return -ENOMEM;
+
+ curl = curl_init();
+ if (!curl)
+ return -EINVAL;
+
+ if (!session->hosturl)
+ session->hosturl = strdup(twitter_host);
+
+ switch (session->action) {
+ case ACTION_UPDATE:
+ snprintf(user_password, sizeof(user_password), "%s:%s",
+ session->account, session->password);
+ snprintf(data, sizeof(data), "status=\"%s\"", session->tweet);
+ curl_formadd(&formpost, &lastptr,
+ CURLFORM_COPYNAME, "status",
+ CURLFORM_COPYCONTENTS, session->tweet,
+ CURLFORM_END);
+
+ curl_formadd(&formpost, &lastptr,
+ CURLFORM_COPYNAME, "source",
+ CURLFORM_COPYCONTENTS, "bti",
+ CURLFORM_END);
+
+ curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
+ slist = curl_slist_append(slist, "Expect:");
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+
+ sprintf(endpoint, "%s%s", session->hosturl, update_uri);
+ curl_easy_setopt(curl, CURLOPT_URL, endpoint);
+ curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
+
+ break;
+ case ACTION_FRIENDS:
+ snprintf(user_password, sizeof(user_password), "%s:%s",
+ session->account, session->password);
+ sprintf(endpoint, "%s%s?page=%d", session->hosturl,
+ friends_uri, session->page);
+ curl_easy_setopt(curl, CURLOPT_URL, endpoint);
+ curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
+
+ break;
+ case ACTION_USER:
+ sprintf(endpoint, "%s%s%s.xml?page=%d", session->hosturl,
+ user_uri, session->user, session->page);
+ curl_easy_setopt(curl, CURLOPT_URL, endpoint);
+
+ break;
+ case ACTION_REPLIES:
+ snprintf(user_password, sizeof(user_password), "%s:%s",
+ session->account, session->password);
+ sprintf(endpoint, "%s%s?page=%d", session->hosturl, replies_uri,
+ session->page);
+ curl_easy_setopt(curl, CURLOPT_URL, endpoint);
+ curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
+
+ break;
+ case ACTION_PUBLIC:
+ sprintf(endpoint, "%s%s?page=%d", session->hosturl, public_uri,
+ session->page);
+ curl_easy_setopt(curl, CURLOPT_URL, endpoint);
+
+ break;
+ case ACTION_GROUP:
+ sprintf(endpoint, "%s%s%s.xml?page=%d", session->hosturl,
+ group_uri, session->group, session->page);
+ curl_easy_setopt(curl, CURLOPT_URL, endpoint);
+
+ break;
+ default:
+ break;
+ }
+
+ if (session->proxy)
+ curl_easy_setopt(curl, CURLOPT_PROXY, session->proxy);
+
+ if (debug)
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+
+ dbg("user_password = %s\n", user_password);
+ dbg("data = %s\n", data);
+ dbg("proxy = %s\n", session->proxy);
+
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf);
+ if (!session->dry_run) {
+ res = curl_easy_perform(curl);
+ if (res && !session->bash) {
+ fprintf(stderr, "error(%d) trying to perform "
+ "operation\n", res);
+ return -EINVAL;
+ }
+ }
+
+ curl_easy_cleanup(curl);
+ if (session->action == ACTION_UPDATE)
+ curl_formfree(formpost);
+ bti_curl_buffer_free(curl_buf);
} else {
- req_url = oauth_sign_url2(
- endpoint, NULL, OA_HMAC, NULL,
- session->consumer_key, session->consumer_secret,
- session->access_token_key, session->access_token_secret
- );
- reply = oauth_http_get(req_url, postarg);
- }
+ switch (session->action) {
+ case ACTION_UPDATE:
+ sprintf(endpoint,
+ "%s%s?status=%s",
+ session->hosturl, update_uri, session->tweet);
+ is_post = 1;
+ break;
+ case ACTION_USER:
+ sprintf(endpoint, "%s%s%s.xml?page=%d",
+ session->hosturl, user_uri,
+ session->user, session->page);
+ break;
+ case ACTION_REPLIES:
+ sprintf(endpoint, "%s%s?page=%d",
+ session->hosturl, mentions_uri, session->page);
+ break;
+ case ACTION_PUBLIC:
+ sprintf(endpoint, "%s%s?page=%d",
+ session->hosturl, public_uri, session->page);
+ break;
+ case ACTION_GROUP:
+ sprintf(endpoint, "%s%s%s.xml?page=%d",
+ session->hosturl, group_uri,
+ session->group, session->page);
+ break;
+ case ACTION_FRIENDS:
+ sprintf(endpoint, "%s%s?page=%d",
+ session->hosturl, friends_uri, session->page);
+ break;
+ default:
+ break;
+ }
- if (req_url)
- free(req_url);
+ if (is_post) {
+ req_url = oauth_sign_url2(
+ endpoint, &postarg, OA_HMAC, NULL,
+ session->consumer_key, session->consumer_secret,
+ session->access_token_key, session->access_token_secret
+ );
+ reply = oauth_http_post(req_url, postarg);
+ } else {
+ req_url = oauth_sign_url2(
+ endpoint, NULL, OA_HMAC, NULL,
+ session->consumer_key, session->consumer_secret,
+ session->access_token_key, session->access_token_secret
+ );
+ reply = oauth_http_get(req_url, postarg);
+ }
- parse_timeline(reply);
+ dbg("%s\n", req_url);
+ dbg("%s\n", reply);
+ if (req_url)
+ free(req_url);
+ if (session->action != ACTION_UPDATE)
+ parse_timeline(reply);
+ }
return 0;
}
FILE *config_file;
char *line = NULL;
size_t len = 0;
+ char *account = NULL;
+ char *password = NULL;
char *consumer_key = NULL;
char *consumer_secret = NULL;
char *access_token_key = NULL;
if (c[0] == '\0')
continue;
- if (!strncasecmp(c, "consumer_key", 12) &&
+ if (!strncasecmp(c, "account", 7) && (c[7] == '=')) {
+ c += 8;
+ if (c[0] != '\0')
+ account = strdup(c);
+ } else if (!strncasecmp(c, "password", 8) &&
+ (c[8] == '=')) {
+ c += 9;
+ if (c[0] != '\0')
+ password = strdup(c);
+ } else if (!strncasecmp(c, "consumer_key", 12) &&
(c[12] == '=')) {
c += 13;
if (c[0] != '\0')
}
} while (!feof(config_file));
+ if (password)
+ session->password = password;
+ if (account)
+ session->account = account;
if (consumer_key)
session->consumer_key = consumer_key;
if (consumer_secret)
return string;
}
+static void read_password(char *buf, size_t len, char *host)
+{
+ char pwd[80];
+ int retval;
+ struct termios old;
+ struct termios tp;
+
+ tcgetattr(0, &tp);
+ old = tp;
+
+ tp.c_lflag &= (~ECHO);
+ tcsetattr(0, TCSANOW, &tp);
+
+ fprintf(stdout, "Enter password for %s: ", host);
+ fflush(stdout);
+ tcflow(0, TCOOFF);
+ retval = scanf("%79s", pwd);
+ tcflow(0, TCOON);
+ fprintf(stdout, "\n");
+
+ tcsetattr(0, TCSANOW, &old);
+
+ strncpy(buf, pwd, len);
+ buf[len-1] = '\0';
+}
+
static int find_urls(const char *tweet, int **pranges)
{
/*
struct session *session;
pid_t child;
char *tweet;
+ static char password[80];
int retval = 0;
int option;
char *http_proxy;
if (debug)
display_version();
- if (!session->consumer_key || !session->consumer_secret) {
- fprintf(stderr, "Both consumer key, and consumer secret are required.\n");
- goto exit;
+ if (session->host == HOST_TWITTER) {
+ if (!session->consumer_key || !session->consumer_secret) {
+ fprintf(stderr, "Twitter no longer supuports HTTP basic authentication.\n");
+ fprintf(stderr, "Both consumer key, and consumer secret are required");
+ fprintf(stderr, " for bti in order to behave as an OAuth consumer.\n");
+ goto exit;
+ }
+ if (session->action == ACTION_GROUP) {
+ fprintf(stderr, "Groups only work in Identi.ca.\n");
+ goto exit;
+ }
+ } else {
+ if (!session->consumer_key || !session->consumer_secret) {
+ session->no_oauth = 1;
+ }
}
- if (!session->access_token_key || !session->access_token_secret) {
- request_access_token(session);
- goto exit;
+ if (session->no_oauth) {
+ if (!session->account) {
+ fprintf(stdout, "Enter account for %s: ", session->hostname);
+ session->account = session->readline(NULL);
+ }
+ if (!session->password) {
+ read_password(password, sizeof(password), session->hostname);
+ session->password = strdup(password);
+ }
+ } else {
+ if (!session->access_token_key || !session->access_token_secret) {
+ request_access_token(session);
+ goto exit;
+ }
}
if (session->action == ACTION_UNKNOWN) {
goto exit;
}
- if (session->host == HOST_TWITTER && session->action == ACTION_GROUP) {
- fprintf(stderr, "Groups only work in Identi.ca.\n");
- goto exit;
- }
-
if (session->action == ACTION_GROUP && !session->group) {
fprintf(stdout, "Enter group name: ");
session->group = session->readline(NULL);