2 * Copyright (C) 2008 Greg Kroah-Hartman <greg@kroah.com>
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation version 2 of the License.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 #include <curl/curl.h>
30 #include <readline/readline.h>
31 #include "bti_version.h"
34 #define zalloc(size) calloc(size, 1)
36 #define dbg(format, arg...) \
39 printf("%s: " format , __func__ , ## arg ); \
62 struct bti_curl_buffer {
67 static void display_help(void)
69 fprintf(stdout, "bti - send tweet to twitter\n");
70 fprintf(stdout, "Version: " BTI_VERSION "\n");
71 fprintf(stdout, "Usage:\n");
72 fprintf(stdout, " bti [options]\n");
73 fprintf(stdout, "options are:\n");
74 fprintf(stdout, " --account accountname\n");
75 fprintf(stdout, " --password password\n");
76 fprintf(stdout, " --proxy PROXY:PORT\n");
77 fprintf(stdout, " --host HOST\n");
78 fprintf(stdout, " --logfile logfile\n");
79 fprintf(stdout, " --bash\n");
80 fprintf(stdout, " --debug\n");
81 fprintf(stdout, " --version\n");
82 fprintf(stdout, " --help\n");
85 static void display_version(void)
87 fprintf(stdout, "bti - version %s\n", BTI_VERSION);
90 static char *get_string_from_stdin(void)
92 static char *string = (char *)NULL;
95 string = (char *)NULL;
98 string = readline("tweet: ");
103 static struct session *session_alloc(void)
105 struct session *session;
107 session = zalloc(sizeof(*session));
113 static void session_free(struct session *session)
117 free(session->password);
118 free(session->account);
119 free(session->tweet);
120 free(session->proxy);
122 free(session->homedir);
126 static struct bti_curl_buffer *bti_curl_buffer_alloc(void)
128 struct bti_curl_buffer *buffer;
130 buffer = zalloc(sizeof(*buffer));
134 /* start out with a data buffer of 1 byte to
135 * make the buffer fill logic simpler */
136 buffer->data = zalloc(1);
145 static void bti_curl_buffer_free(struct bti_curl_buffer *buffer)
153 static const char *twitter_url = "https://twitter.com/statuses/update.xml";
154 static const char *identica_url = "http://identi.ca/api/statuses/update.xml";
156 static CURL *curl_init(void)
160 curl = curl_easy_init();
162 fprintf(stderr, "Can not init CURL!\n");
165 /* some ssl sanity checks on the connection we are making */
166 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
167 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
171 size_t curl_callback(void *buffer, size_t size, size_t nmemb, void *userp)
173 struct bti_curl_buffer *curl_buf = userp;
174 size_t buffer_size = size * nmemb;
177 if ((!buffer) || (!buffer_size) || (!curl_buf))
180 /* add to the data we already have */
181 temp = zalloc(curl_buf->length + buffer_size + 1);
185 memcpy(temp, curl_buf->data, curl_buf->length);
186 free(curl_buf->data);
187 curl_buf->data = temp;
188 memcpy(&curl_buf->data[curl_buf->length], (char *)buffer, buffer_size);
189 curl_buf->length += buffer_size;
191 dbg("%s\n", curl_buf->data);
196 static int send_tweet(struct session *session)
198 char user_password[500];
200 struct bti_curl_buffer *curl_buf;
203 struct curl_httppost *formpost = NULL;
204 struct curl_httppost *lastptr = NULL;
205 struct curl_slist *slist = NULL;
210 curl_buf = bti_curl_buffer_alloc();
214 snprintf(user_password, sizeof(user_password), "%s:%s",
215 session->account, session->password);
216 snprintf(data, sizeof(data), "status=\"%s\"", session->tweet);
222 curl_formadd(&formpost, &lastptr,
223 CURLFORM_COPYNAME, "status",
224 CURLFORM_COPYCONTENTS, session->tweet,
227 curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
229 switch (session->host) {
231 curl_easy_setopt(curl, CURLOPT_URL, twitter_url);
233 * twitter doesn't like the "Expect: 100-continue" header
234 * anymore, so turn it off.
236 slist = curl_slist_append(slist, "Expect:");
237 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
240 curl_easy_setopt(curl, CURLOPT_URL, identica_url);
245 curl_easy_setopt(curl, CURLOPT_PROXY, session->proxy);
248 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
249 curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
251 dbg("user_password = %s\n", user_password);
252 dbg("data = %s\n", data);
253 dbg("proxy = %s\n", session->proxy);
255 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback);
256 curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf);
257 res = curl_easy_perform(curl);
258 if (res && !session->bash) {
259 fprintf(stderr, "error(%d) trying to send tweet\n", res);
263 curl_easy_cleanup(curl);
264 curl_formfree(formpost);
265 bti_curl_buffer_free(curl_buf);
269 static void parse_configfile(struct session *session)
274 char *account = NULL;
275 char *password = NULL;
278 char *logfile = NULL;
281 /* config file is ~/.bti */
282 file = alloca(strlen(session->homedir) + 7);
284 sprintf(file, "%s/.bti", session->homedir);
286 config_file = fopen(file, "r");
288 /* No error if file does not exist or is unreadable. */
289 if (config_file == NULL)
293 ssize_t n = getline(&line, &len, config_file);
296 if (line[n - 1] == '\n')
298 /* Parse file. Format is the usual value pairs:
301 # is a comment character
303 *strchrnul(line, '#') = '\0';
307 /* Ignore blank lines. */
311 if (!strncasecmp(c, "account", 7) && (c[7] == '=')) {
315 } else if (!strncasecmp(c, "password", 8) &&
319 password = strdup(c);
320 } else if (!strncasecmp(c, "host", 4) &&
325 } else if (!strncasecmp(c, "proxy", 5) &&
330 } else if (!strncasecmp(c, "logfile", 7) &&
336 } while (!feof(config_file));
339 session->password = password;
341 session->account = account;
343 if (strcasecmp(host, "twitter") == 0)
344 session->host = HOST_TWITTER;
345 if (strcasecmp(host, "identica") == 0)
346 session->host = HOST_IDENTICA;
351 free(session->proxy);
352 session->proxy = proxy;
355 session->logfile = logfile;
357 /* Free buffer and close file. */
362 static void log_session(struct session *session, int retval)
368 /* Only log something if we have a log file set */
369 if (!session->logfile)
372 filename = alloca(strlen(session->homedir) +
373 strlen(session->logfile) + 3);
375 sprintf(filename, "%s/%s", session->homedir, session->logfile);
377 log_file = fopen(filename, "a+");
378 if (log_file == NULL)
380 switch (session->host) {
393 fprintf(log_file, "%s: host=%s tweet failed\n", session->time, host);
395 fprintf(log_file, "%s: host=%s tweet=%s\n", session->time, host, session->tweet);
400 int main(int argc, char *argv[], char *envp[])
402 static const struct option options[] = {
403 { "debug", 0, NULL, 'd' },
404 { "account", 1, NULL, 'a' },
405 { "password", 1, NULL, 'p' },
406 { "host", 1, NULL, 'H' },
407 { "proxy", 1, NULL, 'P' },
408 { "logfile", 1, NULL, 'L' },
409 { "help", 0, NULL, 'h' },
410 { "bash", 0, NULL, 'b' },
411 { "version", 0, NULL, 'v' },
414 struct session *session;
422 char *pwd = getenv("PWD");
426 rl_bind_key('\t', rl_insert);
427 session = session_alloc();
429 fprintf(stderr, "no more memory...\n");
433 /* get the current time so that we can log it later */
435 session->time = strdup(ctime(&t));
436 session->time[strlen(session->time)-1] = 0x00;
438 session->homedir = strdup(getenv("HOME"));
440 curl_global_init(CURL_GLOBAL_ALL);
442 /* Set environment variables first, before reading command line options
443 * or config file values. */
444 http_proxy = getenv("http_proxy");
447 free(session->proxy);
448 session->proxy = strdup(http_proxy);
449 dbg("http_proxy = %s\n", session->proxy);
452 parse_configfile(session);
455 option = getopt_long_only(argc, argv, "dqe:p:P:H:a:h",
464 if (session->account)
465 free(session->account);
466 session->account = strdup(optarg);
467 dbg("account = %s\n", session->account);
470 if (session->password)
471 free(session->password);
472 session->password = strdup(optarg);
473 dbg("password = %s\n", session->password);
477 free(session->proxy);
478 session->proxy = strdup(optarg);
479 dbg("proxy = %s\n", session->proxy);
482 if (session->logfile)
483 free(session->logfile);
484 session->logfile = strdup(optarg);
485 dbg("logfile = %s\n", session->logfile);
488 if (strcasecmp(optarg, "twitter") == 0)
489 session->host = HOST_TWITTER;
490 if (strcasecmp(optarg, "identica") == 0)
491 session->host = HOST_IDENTICA;
492 dbg("host = %d\n", session->host);
509 if (!session->account) {
510 fprintf(stdout, "Enter twitter account: ");
511 session->account = get_string_from_stdin();
514 if (!session->password) {
515 fprintf(stdout, "Enter twitter password: ");
516 session->password = get_string_from_stdin();
519 /* get the current working directory basename */
520 if (strcmp(pwd, home) == 0)
523 dir = strrchr(pwd, '/');
530 tweet = get_string_from_stdin();
531 if (!tweet || strlen(tweet) == 0) {
536 // session->tweet = zalloc(strlen(tweet) + strlen(dir) + 10);
537 session->tweet = zalloc(strlen(tweet) + 10);
539 /* if --bash is specified, add the "PWD $ " to
540 * the start of the tweet. */
542 // sprintf(session->tweet, "%s $ %s", dir, tweet);
543 sprintf(session->tweet, "$ %s", tweet);
545 sprintf(session->tweet, "%s", tweet);
548 dbg("account = %s\n", session->account);
549 dbg("password = %s\n", session->password);
550 dbg("tweet = %s\n", session->tweet);
551 dbg("host = %d\n", session->host);
553 /* fork ourself so that the main shell can get on
554 * with it's life as we try to connect and handle everything
559 dbg("child is %d\n", child);
564 retval = send_tweet(session);
565 if (retval && !session->bash)
566 fprintf(stderr, "tweet failed\n");
568 log_session(session, retval);
570 session_free(session);