]> ToastFreeware Gitweb - gregoa/bti.git/blob - bti.c
428e4bc2685fafedf15f9ee40b12740b6acb9e9a
[gregoa/bti.git] / bti.c
1 /*
2  * Copyright (C) 2008-2010 Greg Kroah-Hartman <greg@kroah.com>
3  * Copyright (C) 2009 Bart Trojanowski <bart@jukie.net>
4  * Copyright (C) 2009-2010 Amir Mohammad Saied <amirsaied@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19
20 #define _GNU_SOURCE
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stddef.h>
25 #include <string.h>
26 #include <getopt.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <time.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <sys/wait.h>
35 #include <curl/curl.h>
36 #include <libxml/xmlmemory.h>
37 #include <libxml/parser.h>
38 #include <libxml/tree.h>
39 #include <pcre.h>
40 #include <termios.h>
41 #include <dlfcn.h>
42 #include <oauth.h>
43
44
45 #define zalloc(size)    calloc(size, 1)
46
47 #define dbg(format, arg...)                                             \
48         do {                                                            \
49                 if (debug)                                              \
50                         fprintf(stdout, "bti: %s: " format , __func__ , \
51                                 ## arg);                                \
52         } while (0)
53
54
55 static int debug;
56
57 enum host {
58         HOST_TWITTER  = 0,
59         HOST_IDENTICA = 1,
60         HOST_CUSTOM   = 2
61 };
62
63 enum action {
64         ACTION_UPDATE  = 0,
65         ACTION_FRIENDS = 1,
66         ACTION_USER    = 2,
67         ACTION_REPLIES = 4,
68         ACTION_PUBLIC  = 8,
69         ACTION_GROUP   = 16,
70         ACTION_RETWEET = 32,
71         ACTION_UNKNOWN = 64
72 };
73
74 struct session {
75         char *password;
76         char *account;
77         char *consumer_key;
78         char *consumer_secret;
79         char *access_token_key;
80         char *access_token_secret;
81         char *tweet;
82         char *proxy;
83         char *time;
84         char *homedir;
85         char *logfile;
86         char *user;
87         char *group;
88         char *hosturl;
89         char *hostname;
90         char *configfile;
91         char *replyto;
92         char *retweet;
93         int bash;
94         int background;
95         int interactive;
96         int shrink_urls;
97         int dry_run;
98         int page;
99         int no_oauth;
100         int guest;
101         int verbose;
102         enum host host;
103         enum action action;
104         void *readline_handle;
105         char *(*readline)(const char *);
106 };
107
108 struct bti_curl_buffer {
109         char *data;
110         struct session *session;
111         enum action action;
112         int length;
113 };
114
115 static void display_help(void)
116 {
117         fprintf(stdout, "bti - send tweet to twitter or identi.ca\n"
118                 "Version: %s\n"
119                 "Usage:\n"
120                 "  bti [options]\n"
121                 "options are:\n"
122                 "  --account accountname\n"
123                 "  --password password\n"
124                 "  --action action\n"
125                 "    ('update', 'friends', 'public', 'replies', or 'user')\n"
126                 "  --user screenname\n"
127                 "  --group groupname\n"
128                 "  --proxy PROXY:PORT\n"
129                 "  --host HOST\n"
130                 "  --logfile logfile\n"
131                 "  --config configfile\n"
132                 "  --replyto ID\n"
133                 "  --retweet ID\n"
134                 "  --shrink-urls\n"
135                 "  --page PAGENUMBER\n"
136                 "  --bash\n"
137                 "  --background\n"
138                 "  --debug\n"
139                 "  --verbose\n"
140                 "  --dry-run\n"
141                 "  --version\n"
142                 "  --help\n", VERSION);
143 }
144
145 static void display_version(void)
146 {
147         fprintf(stdout, "bti - version %s\n", VERSION);
148 }
149
150 static char *get_string(const char *name)
151 {
152         char *temp;
153         char *string;
154
155         string = zalloc(1000);
156         if (!string)
157                 exit(1);
158         if (name != NULL)
159                 fprintf(stdout, "%s", name);
160         if (!fgets(string, 999, stdin))
161                 return NULL;
162         temp = strchr(string, '\n');
163         if (temp)
164                 *temp = '\0';
165         return string;
166 }
167
168 /*
169  * Try to get a handle to a readline function from a variety of different
170  * libraries.  If nothing is present on the system, then fall back to an
171  * internal one.
172  *
173  * Logic originally based off of code in the e2fsutils package in the
174  * lib/ss/get_readline.c file, which is licensed under the MIT license.
175  *
176  * This keeps us from having to relicense the bti codebase if readline
177  * ever changes its license, as there is no link-time dependancy.
178  * It is a run-time thing only, and we handle any readline-like library
179  * in the same manner, making bti not be a derivative work of any
180  * other program.
181  */
182 static void session_readline_init(struct session *session)
183 {
184         /* Libraries we will try to use for readline/editline functionality */
185         const char *libpath = "libreadline.so.6:libreadline.so.5:"
186                                 "libreadline.so.4:libreadline.so:libedit.so.2:"
187                                 "libedit.so:libeditline.so.0:libeditline.so";
188         void *handle = NULL;
189         char *tmp, *cp, *next;
190         int (*bind_key)(int, void *);
191         void (*insert)(void);
192
193         /* default to internal function if we can't or won't find anything */
194         session->readline = get_string;
195         if (!isatty(0))
196                 return;
197         session->interactive = 1;
198
199         tmp = malloc(strlen(libpath)+1);
200         if (!tmp)
201                 return;
202         strcpy(tmp, libpath);
203         for (cp = tmp; cp; cp = next) {
204                 next = strchr(cp, ':');
205                 if (next)
206                         *next++ = 0;
207                 if (*cp == 0)
208                         continue;
209                 handle = dlopen(cp, RTLD_NOW);
210                 if (handle) {
211                         dbg("Using %s for readline library\n", cp);
212                         break;
213                 }
214         }
215         free(tmp);
216         if (!handle) {
217                 dbg("No readline library found.\n");
218                 return;
219         }
220
221         session->readline_handle = handle;
222         session->readline = (char *(*)(const char *))dlsym(handle, "readline");
223         if (session->readline == NULL) {
224                 /* something odd happened, default back to internal stuff */
225                 session->readline_handle = NULL;
226                 session->readline = get_string;
227                 return;
228         }
229
230         /*
231          * If we found a library, turn off filename expansion
232          * as that makes no sense from within bti.
233          */
234         bind_key = (int (*)(int, void *))dlsym(handle, "rl_bind_key");
235         insert = (void (*)(void))dlsym(handle, "rl_insert");
236         if (bind_key && insert)
237                 bind_key('\t', insert);
238 }
239
240 static void session_readline_cleanup(struct session *session)
241 {
242         if (session->readline_handle)
243                 dlclose(session->readline_handle);
244 }
245
246 static struct session *session_alloc(void)
247 {
248         struct session *session;
249
250         session = zalloc(sizeof(*session));
251         if (!session)
252                 return NULL;
253         return session;
254 }
255
256 static void session_free(struct session *session)
257 {
258         if (!session)
259                 return;
260         free(session->retweet);
261         free(session->replyto);
262         free(session->password);
263         free(session->account);
264         free(session->consumer_key);
265         free(session->consumer_secret);
266         free(session->access_token_key);
267         free(session->access_token_secret);
268         free(session->tweet);
269         free(session->proxy);
270         free(session->time);
271         free(session->homedir);
272         free(session->user);
273         free(session->group);
274         free(session->hosturl);
275         free(session->hostname);
276         free(session->configfile);
277         free(session);
278 }
279
280 static struct bti_curl_buffer *bti_curl_buffer_alloc(enum action action)
281 {
282         struct bti_curl_buffer *buffer;
283
284         buffer = zalloc(sizeof(*buffer));
285         if (!buffer)
286                 return NULL;
287
288         /* start out with a data buffer of 1 byte to
289          * make the buffer fill logic simpler */
290         buffer->data = zalloc(1);
291         if (!buffer->data) {
292                 free(buffer);
293                 return NULL;
294         }
295         buffer->length = 0;
296         buffer->action = action;
297         return buffer;
298 }
299
300 static void bti_curl_buffer_free(struct bti_curl_buffer *buffer)
301 {
302         if (!buffer)
303                 return;
304         free(buffer->data);
305         free(buffer);
306 }
307
308 static const char twitter_host[]  = "http://api.twitter.com/1/statuses";
309 static const char identica_host[] = "https://identi.ca/api/statuses";
310 static const char twitter_name[]  = "twitter";
311 static const char identica_name[] = "identi.ca";
312
313 static const char twitter_request_token_uri[]  = "http://twitter.com/oauth/request_token";
314 static const char twitter_access_token_uri[]   = "http://twitter.com/oauth/access_token";
315 static const char twitter_authorize_uri[]      = "http://twitter.com/oauth/authorize?oauth_token=";
316 static const char identica_request_token_uri[] = "http://identi.ca/api/oauth/request_token?oauth_callback=oob";
317 static const char identica_access_token_uri[]  = "http://identi.ca/api/oauth/access_token";
318 static const char identica_authorize_uri[]     = "http://identi.ca/api/oauth/authorize?oauth_token=";
319
320 static const char user_uri[]     = "/user_timeline/";
321 static const char update_uri[]   = "/update.xml";
322 static const char public_uri[]   = "/public_timeline.xml";
323 static const char friends_uri[]  = "/friends_timeline.xml";
324 static const char mentions_uri[] = "/mentions.xml";
325 static const char replies_uri[]  = "/replies.xml";
326 static const char retweet_uri[]  = "/retweet/";
327 static const char group_uri[]    = "/../statusnet/groups/timeline/";
328
329 static CURL *curl_init(void)
330 {
331         CURL *curl;
332
333         curl = curl_easy_init();
334         if (!curl) {
335                 fprintf(stderr, "Can not init CURL!\n");
336                 return NULL;
337         }
338         /* some ssl sanity checks on the connection we are making */
339         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
340         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
341         return curl;
342 }
343
344 static void parse_statuses(struct session *session,
345                            xmlDocPtr doc, xmlNodePtr current)
346 {
347         xmlChar *text = NULL;
348         xmlChar *user = NULL;
349         xmlChar *created = NULL;
350         xmlChar *id = NULL;
351         xmlNodePtr userinfo;
352
353         current = current->xmlChildrenNode;
354         while (current != NULL) {
355                 if (current->type == XML_ELEMENT_NODE) {
356                         if (!xmlStrcmp(current->name, (const xmlChar *)"created_at"))
357                                 created = xmlNodeListGetString(doc, current->xmlChildrenNode, 1);
358                         if (!xmlStrcmp(current->name, (const xmlChar *)"text"))
359                                 text = xmlNodeListGetString(doc, current->xmlChildrenNode, 1);
360                         if (!xmlStrcmp(current->name, (const xmlChar *)"id"))
361                                 id = xmlNodeListGetString(doc, current->xmlChildrenNode, 1);
362                         if (!xmlStrcmp(current->name, (const xmlChar *)"user")) {
363                                 userinfo = current->xmlChildrenNode;
364                                 while (userinfo != NULL) {
365                                         if ((!xmlStrcmp(userinfo->name, (const xmlChar *)"screen_name"))) {
366                                                 if (user)
367                                                         xmlFree(user);
368                                                 user = xmlNodeListGetString(doc, userinfo->xmlChildrenNode, 1);
369                                         }
370                                         userinfo = userinfo->next;
371                                 }
372                         }
373
374                         if (user && text && created && id) {
375                                 if (session->verbose)
376                                         printf("[%s] {%s} (%.16s) %s\n",
377                                                 user, id, created, text);
378                                 else
379                                         printf("[%s] %s\n",
380                                                 user, text);
381                                 xmlFree(user);
382                                 xmlFree(text);
383                                 xmlFree(created);
384                                 xmlFree(id);
385                                 user = NULL;
386                                 text = NULL;
387                                 created = NULL;
388                                 id = NULL;
389                         }
390                 }
391                 current = current->next;
392         }
393
394         return;
395 }
396
397 static void parse_timeline(char *document, struct session *session)
398 {
399         xmlDocPtr doc;
400         xmlNodePtr current;
401
402         doc = xmlReadMemory(document, strlen(document), "timeline.xml",
403                             NULL, XML_PARSE_NOERROR);
404         if (doc == NULL)
405                 return;
406
407         current = xmlDocGetRootElement(doc);
408         if (current == NULL) {
409                 fprintf(stderr, "empty document\n");
410                 xmlFreeDoc(doc);
411                 return;
412         }
413
414         if (xmlStrcmp(current->name, (const xmlChar *) "statuses")) {
415                 fprintf(stderr, "unexpected document type\n");
416                 xmlFreeDoc(doc);
417                 return;
418         }
419
420         current = current->xmlChildrenNode;
421         while (current != NULL) {
422                 if ((!xmlStrcmp(current->name, (const xmlChar *)"status")))
423                         parse_statuses(session, doc, current);
424                 current = current->next;
425         }
426         xmlFreeDoc(doc);
427
428         return;
429 }
430
431 static size_t curl_callback(void *buffer, size_t size, size_t nmemb,
432                             void *userp)
433 {
434         struct bti_curl_buffer *curl_buf = userp;
435         size_t buffer_size = size * nmemb;
436         char *temp;
437
438         if ((!buffer) || (!buffer_size) || (!curl_buf))
439                 return -EINVAL;
440
441         /* add to the data we already have */
442         temp = zalloc(curl_buf->length + buffer_size + 1);
443         if (!temp)
444                 return -ENOMEM;
445
446         memcpy(temp, curl_buf->data, curl_buf->length);
447         free(curl_buf->data);
448         curl_buf->data = temp;
449         memcpy(&curl_buf->data[curl_buf->length], (char *)buffer, buffer_size);
450         curl_buf->length += buffer_size;
451         if (curl_buf->action)
452                 parse_timeline(curl_buf->data, curl_buf->session);
453
454         dbg("%s\n", curl_buf->data);
455
456         return buffer_size;
457 }
458
459 static int parse_osp_reply(const char *reply, char **token, char **secret)
460 {
461         int rc;
462         int retval = 1;
463         char **rv = NULL;
464         rc = oauth_split_url_parameters(reply, &rv);
465         qsort(rv, rc, sizeof(char *), oauth_cmpstringp);
466         if (rc == 2 || rc == 4) {
467                 if (!strncmp(rv[0], "oauth_token=", 11) &&
468                     !strncmp(rv[1], "oauth_token_secret=", 18)) {
469                         if (token)
470                                 *token = strdup(&(rv[0][12]));
471                         if (secret)
472                                 *secret = strdup(&(rv[1][19]));
473
474                         retval = 0;
475                 }
476         } else if (rc == 3) {
477                 if (!strncmp(rv[1], "oauth_token=", 11) &&
478                     !strncmp(rv[2], "oauth_token_secret=", 18)) {
479                         if (token)
480                                 *token = strdup(&(rv[1][12]));
481                         if (secret)
482                                 *secret = strdup(&(rv[2][19]));
483
484                         retval = 0;
485                 }
486         }
487
488         dbg("token: %s\n", *token);
489         dbg("secret: %s\n", *secret);
490
491         if (rv)
492                 free(rv);
493
494         return retval;
495 }
496
497 static int request_access_token(struct session *session)
498 {
499         char *post_params = NULL;
500         char *request_url = NULL;
501         char *reply       = NULL;
502         char *at_key      = NULL;
503         char *at_secret   = NULL;
504         char *verifier    = NULL;
505         char at_uri[90];
506
507         if (!session)
508                 return -EINVAL;
509
510         if (session->host == HOST_TWITTER)
511                 request_url = oauth_sign_url2(
512                                 twitter_request_token_uri, NULL,
513                                 OA_HMAC, NULL, session->consumer_key,
514                                 session->consumer_secret, NULL, NULL);
515         else if (session->host == HOST_IDENTICA)
516                 request_url = oauth_sign_url2(
517                                 identica_request_token_uri, NULL,
518                                 OA_HMAC, NULL, session->consumer_key,
519                                 session->consumer_secret, NULL, NULL);
520         reply = oauth_http_get(request_url, post_params);
521
522         if (request_url)
523                 free(request_url);
524
525         if (post_params)
526                 free(post_params);
527
528         if (!reply)
529                 return 1;
530
531         if (parse_osp_reply(reply, &at_key, &at_secret))
532                 return 1;
533
534         free(reply);
535
536         fprintf(stdout,
537                 "Please open the following link in your browser, and "
538                 "allow 'bti' to access your account. Then paste "
539                 "back the provided PIN in here.\n");
540         if (session->host == HOST_TWITTER) {
541                 fprintf(stdout, "%s%s\nPIN: ", twitter_authorize_uri, at_key);
542                 verifier = session->readline(NULL);
543                 sprintf(at_uri, "%s?oauth_verifier=%s",
544                         twitter_access_token_uri, verifier);
545         } else if (session->host == HOST_IDENTICA) {
546                 fprintf(stdout, "%s%s\nPIN: ", identica_authorize_uri, at_key);
547                 verifier = session->readline(NULL);
548                 sprintf(at_uri, "%s?oauth_verifier=%s",
549                         identica_access_token_uri, verifier);
550         }
551         request_url = oauth_sign_url2(at_uri, NULL, OA_HMAC, NULL,
552                                       session->consumer_key,
553                                       session->consumer_secret,
554                                       at_key, at_secret);
555         reply = oauth_http_get(request_url, post_params);
556
557         if (!reply)
558                 return 1;
559
560         if (parse_osp_reply(reply, &at_key, &at_secret))
561                 return 1;
562
563         free(reply);
564
565         fprintf(stdout,
566                 "Please put these two lines in your bti "
567                 "configuration file (~/.bti):\n"
568                 "access_token_key=%s\n"
569                 "access_token_secret=%s\n",
570                 at_key, at_secret);
571
572         return 0;
573 }
574
575 static int send_request(struct session *session)
576 {
577         char endpoint[500];
578         char user_password[500];
579         char data[500];
580         struct bti_curl_buffer *curl_buf;
581         CURL *curl = NULL;
582         CURLcode res;
583         struct curl_httppost *formpost = NULL;
584         struct curl_httppost *lastptr = NULL;
585         struct curl_slist *slist = NULL;
586         char *req_url = NULL;
587         char *reply = NULL;
588         char *postarg = NULL;
589         char *escaped_tweet = NULL;
590         int is_post = 0;
591
592         if (!session)
593                 return -EINVAL;
594
595         if (!session->hosturl)
596                 session->hosturl = strdup(twitter_host);
597
598         if (session->no_oauth || session->guest) {
599                 curl_buf = bti_curl_buffer_alloc(session->action);
600                 if (!curl_buf)
601                         return -ENOMEM;
602                 curl_buf->session = session;
603
604                 curl = curl_init();
605                 if (!curl)
606                         return -EINVAL;
607
608                 if (!session->hosturl)
609                         session->hosturl = strdup(twitter_host);
610
611                 switch (session->action) {
612                 case ACTION_UPDATE:
613                         snprintf(user_password, sizeof(user_password), "%s:%s",
614                                  session->account, session->password);
615                         snprintf(data, sizeof(data), "status=\"%s\"",
616                                  session->tweet);
617                         curl_formadd(&formpost, &lastptr,
618                                      CURLFORM_COPYNAME, "status",
619                                      CURLFORM_COPYCONTENTS, session->tweet,
620                                      CURLFORM_END);
621
622                         curl_formadd(&formpost, &lastptr,
623                                      CURLFORM_COPYNAME, "source",
624                                      CURLFORM_COPYCONTENTS, "bti",
625                                      CURLFORM_END);
626
627                         if (session->replyto)
628                                 curl_formadd(&formpost, &lastptr,
629                                              CURLFORM_COPYNAME, "in_reply_to_status_id",
630                                              CURLFORM_COPYCONTENTS, session->replyto,
631                                              CURLFORM_END);
632
633                         curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
634                         slist = curl_slist_append(slist, "Expect:");
635                         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
636
637                         sprintf(endpoint, "%s%s", session->hosturl, update_uri);
638                         curl_easy_setopt(curl, CURLOPT_URL, endpoint);
639                         curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
640                         break;
641
642                 case ACTION_FRIENDS:
643                         snprintf(user_password, sizeof(user_password), "%s:%s",
644                                  session->account, session->password);
645                         sprintf(endpoint, "%s%s?page=%d", session->hosturl,
646                                         friends_uri, session->page);
647                         curl_easy_setopt(curl, CURLOPT_URL, endpoint);
648                         curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
649                         break;
650
651                 case ACTION_USER:
652                         sprintf(endpoint, "%s%s%s.xml?page=%d", session->hosturl,
653                                 user_uri, session->user, session->page);
654                         curl_easy_setopt(curl, CURLOPT_URL, endpoint);
655                         break;
656
657                 case ACTION_REPLIES:
658                         snprintf(user_password, sizeof(user_password), "%s:%s",
659                                  session->account, session->password);
660                         sprintf(endpoint, "%s%s?page=%d", session->hosturl,
661                                 replies_uri, session->page);
662                         curl_easy_setopt(curl, CURLOPT_URL, endpoint);
663                         curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
664                         break;
665
666                 case ACTION_PUBLIC:
667                         sprintf(endpoint, "%s%s?page=%d", session->hosturl,
668                                 public_uri, session->page);
669                         curl_easy_setopt(curl, CURLOPT_URL, endpoint);
670                         break;
671
672                 case ACTION_GROUP:
673                         sprintf(endpoint, "%s%s%s.xml?page=%d",
674                                 session->hosturl, group_uri, session->group,
675                                 session->page);
676                         curl_easy_setopt(curl, CURLOPT_URL, endpoint);
677                         break;
678
679                 default:
680                         break;
681                 }
682
683                 if (session->proxy)
684                         curl_easy_setopt(curl, CURLOPT_PROXY, session->proxy);
685
686                 if (debug)
687                         curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
688
689                 dbg("user_password = %s\n", user_password);
690                 dbg("data = %s\n", data);
691                 dbg("proxy = %s\n", session->proxy);
692
693                 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback);
694                 curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf);
695                 if (!session->dry_run) {
696                         res = curl_easy_perform(curl);
697                         if (res && !session->background) {
698                                 fprintf(stderr, "error(%d) trying to perform "
699                                                 "operation\n", res);
700                                 return -EINVAL;
701                         }
702                 }
703
704                 curl_easy_cleanup(curl);
705                 if (session->action == ACTION_UPDATE)
706                         curl_formfree(formpost);
707                 bti_curl_buffer_free(curl_buf);
708         } else {
709                 switch (session->action) {
710                 case ACTION_UPDATE:
711                         escaped_tweet = oauth_url_escape(session->tweet);
712                         if (session->replyto) {
713                                 sprintf(endpoint,
714                                         "%s%s?status=%s&in_reply_to_status_id=%s",
715                                         session->hosturl, update_uri,
716                                         escaped_tweet, session->replyto);
717                         } else {
718                                 sprintf(endpoint, "%s%s?status=%s",
719                                         session->hosturl, update_uri,
720                                         escaped_tweet);
721                         }
722
723                         is_post = 1;
724                         break;
725                 case ACTION_USER:
726                         sprintf(endpoint, "%s%s%s.xml?page=%d",
727                                 session->hosturl, user_uri, session->user,
728                                 session->page);
729                         break;
730                 case ACTION_REPLIES:
731                         sprintf(endpoint, "%s%s?page=%d", session->hosturl,
732                                 mentions_uri, session->page);
733                         break;
734                 case ACTION_PUBLIC:
735                         sprintf(endpoint, "%s%s?page=%d", session->hosturl,
736                                 public_uri, session->page);
737                         break;
738                 case ACTION_GROUP:
739                         sprintf(endpoint, "%s%s%s.xml?page=%d",
740                                 session->hosturl, group_uri, session->group,
741                                 session->page);
742                         break;
743                 case ACTION_FRIENDS:
744                         sprintf(endpoint, "%s%s?page=%d", session->hosturl,
745                                 friends_uri, session->page);
746                         break;
747                 case ACTION_RETWEET:
748                         sprintf(endpoint, "%s%s%s.xml", session->hosturl,
749                                 retweet_uri, session->retweet);
750                         is_post = 1;
751                         break;
752                 default:
753                         break;
754                 }
755
756                 dbg("%s\n", endpoint);
757                 if (!session->dry_run) {
758                         if (is_post) {
759                                 req_url = oauth_sign_url2(endpoint, &postarg, OA_HMAC,
760                                                           NULL, session->consumer_key,
761                                                           session->consumer_secret,
762                                                           session->access_token_key,
763                                                           session->access_token_secret);
764                                 reply = oauth_http_post(req_url, postarg);
765                         } else {
766                                 req_url = oauth_sign_url2(endpoint, NULL, OA_HMAC, NULL,
767                                                           session->consumer_key,
768                                                           session->consumer_secret,
769                                                           session->access_token_key,
770                                                           session->access_token_secret);
771                                 reply = oauth_http_get(req_url, postarg);
772                         }
773
774                         dbg("%s\n", req_url);
775                         dbg("%s\n", reply);
776                         if (req_url)
777                                 free(req_url);
778                 }
779
780                 if ((session->action != ACTION_UPDATE) &&
781                                 (session->action != ACTION_RETWEET))
782                         parse_timeline(reply, session);
783         }
784         return 0;
785 }
786
787 static void parse_configfile(struct session *session)
788 {
789         FILE *config_file;
790         char *line = NULL;
791         size_t len = 0;
792         char *account = NULL;
793         char *password = NULL;
794         char *consumer_key = NULL;
795         char *consumer_secret = NULL;
796         char *access_token_key = NULL;
797         char *access_token_secret = NULL;
798         char *host = NULL;
799         char *proxy = NULL;
800         char *logfile = NULL;
801         char *action = NULL;
802         char *user = NULL;
803         char *replyto = NULL;
804         char *retweet = NULL;
805         int shrink_urls = 0;
806
807         config_file = fopen(session->configfile, "r");
808
809         /* No error if file does not exist or is unreadable.  */
810         if (config_file == NULL)
811                 return;
812
813         do {
814                 ssize_t n = getline(&line, &len, config_file);
815                 if (n < 0)
816                         break;
817                 if (line[n - 1] == '\n')
818                         line[n - 1] = '\0';
819                 /* Parse file.  Format is the usual value pairs:
820                    account=name
821                    passwort=value
822                    # is a comment character
823                 */
824                 *strchrnul(line, '#') = '\0';
825                 char *c = line;
826                 while (isspace(*c))
827                         c++;
828                 /* Ignore blank lines.  */
829                 if (c[0] == '\0')
830                         continue;
831
832                 if (!strncasecmp(c, "account", 7) && (c[7] == '=')) {
833                         c += 8;
834                         if (c[0] != '\0')
835                                 account = strdup(c);
836                 } else if (!strncasecmp(c, "password", 8) &&
837                            (c[8] == '=')) {
838                         c += 9;
839                         if (c[0] != '\0')
840                                 password = strdup(c);
841                 } else if (!strncasecmp(c, "consumer_key", 12) &&
842                            (c[12] == '=')) {
843                         c += 13;
844                         if (c[0] != '\0')
845                                 consumer_key = strdup(c);
846                 } else if (!strncasecmp(c, "consumer_secret", 15) &&
847                            (c[15] == '=')) {
848                         c += 16;
849                         if (c[0] != '\0')
850                                 consumer_secret = strdup(c);
851                 } else if (!strncasecmp(c, "access_token_key", 16) &&
852                            (c[16] == '=')) {
853                         c += 17;
854                         if (c[0] != '\0')
855                                 access_token_key = strdup(c);
856                 } else if (!strncasecmp(c, "access_token_secret", 19) &&
857                            (c[19] == '=')) {
858                         c += 20;
859                         if (c[0] != '\0')
860                                 access_token_secret = strdup(c);
861                 } else if (!strncasecmp(c, "host", 4) &&
862                            (c[4] == '=')) {
863                         c += 5;
864                         if (c[0] != '\0')
865                                 host = strdup(c);
866                 } else if (!strncasecmp(c, "proxy", 5) &&
867                            (c[5] == '=')) {
868                         c += 6;
869                         if (c[0] != '\0')
870                                 proxy = strdup(c);
871                 } else if (!strncasecmp(c, "logfile", 7) &&
872                            (c[7] == '=')) {
873                         c += 8;
874                         if (c[0] != '\0')
875                                 logfile = strdup(c);
876                 } else if (!strncasecmp(c, "replyto", 7) &&
877                            (c[7] == '=')) {
878                         c += 8;
879                         if (c[0] != '\0')
880                                 replyto = strdup(c);
881                 } else if (!strncasecmp(c, "action", 6) &&
882                            (c[6] == '=')) {
883                         c += 7;
884                         if (c[0] != '\0')
885                                 action = strdup(c);
886                 } else if (!strncasecmp(c, "user", 4) &&
887                                 (c[4] == '=')) {
888                         c += 5;
889                         if (c[0] != '\0')
890                                 user = strdup(c);
891                 } else if (!strncasecmp(c, "shrink-urls", 11) &&
892                                 (c[11] == '=')) {
893                         c += 12;
894                         if (!strncasecmp(c, "true", 4) ||
895                                         !strncasecmp(c, "yes", 3))
896                                 shrink_urls = 1;
897                 } else if (!strncasecmp(c, "verbose", 7) &&
898                                 (c[7] == '=')) {
899                         c += 8;
900                         if (!strncasecmp(c, "true", 4) ||
901                                         !strncasecmp(c, "yes", 3))
902                                 session->verbose = 1;
903                 } else if (!strncasecmp(c,"retweet", 7) &&
904                                 (c[7] == '=')) {
905                         c += 8;
906                         if (c[0] != '\0')
907                                 retweet = strdup(c);
908                 }
909         } while (!feof(config_file));
910
911         if (password)
912                 session->password = password;
913         if (account)
914                 session->account = account;
915         if (consumer_key)
916                 session->consumer_key = consumer_key;
917         if (consumer_secret)
918                 session->consumer_secret = consumer_secret;
919         if (access_token_key)
920                 session->access_token_key = access_token_key;
921         if (access_token_secret)
922                 session->access_token_secret = access_token_secret;
923         if (host) {
924                 if (strcasecmp(host, "twitter") == 0) {
925                         session->host = HOST_TWITTER;
926                         session->hosturl = strdup(twitter_host);
927                         session->hostname = strdup(twitter_name);
928                 } else if (strcasecmp(host, "identica") == 0) {
929                         session->host = HOST_IDENTICA;
930                         session->hosturl = strdup(identica_host);
931                         session->hostname = strdup(identica_name);
932                 } else {
933                         session->host = HOST_CUSTOM;
934                         session->hosturl = strdup(host);
935                         session->hostname = strdup(host);
936                 }
937                 free(host);
938         }
939         if (proxy) {
940                 if (session->proxy)
941                         free(session->proxy);
942                 session->proxy = proxy;
943         }
944         if (logfile)
945                 session->logfile = logfile;
946         if (replyto)
947                 session->replyto = replyto;
948         if (retweet)
949                 session->retweet = retweet;
950         if (action) {
951                 if (strcasecmp(action, "update") == 0)
952                         session->action = ACTION_UPDATE;
953                 else if (strcasecmp(action, "friends") == 0)
954                         session->action = ACTION_FRIENDS;
955                 else if (strcasecmp(action, "user") == 0)
956                         session->action = ACTION_USER;
957                 else if (strcasecmp(action, "replies") == 0)
958                         session->action = ACTION_REPLIES;
959                 else if (strcasecmp(action, "public") == 0)
960                         session->action = ACTION_PUBLIC;
961                 else if (strcasecmp(action, "group") == 0)
962                         session->action = ACTION_GROUP;
963                 else
964                         session->action = ACTION_UNKNOWN;
965                 free(action);
966         }
967         if (user)
968                 session->user = user;
969         session->shrink_urls = shrink_urls;
970
971         /* Free buffer and close file.  */
972         free(line);
973         fclose(config_file);
974 }
975
976 static void log_session(struct session *session, int retval)
977 {
978         FILE *log_file;
979         char *filename;
980
981         /* Only log something if we have a log file set */
982         if (!session->logfile)
983                 return;
984
985         filename = alloca(strlen(session->homedir) +
986                           strlen(session->logfile) + 3);
987
988         sprintf(filename, "%s/%s", session->homedir, session->logfile);
989
990         log_file = fopen(filename, "a+");
991         if (log_file == NULL)
992                 return;
993
994         switch (session->action) {
995         case ACTION_UPDATE:
996                 if (retval)
997                         fprintf(log_file, "%s: host=%s tweet failed\n",
998                                 session->time, session->hostname);
999                 else
1000                         fprintf(log_file, "%s: host=%s tweet=%s\n",
1001                                 session->time, session->hostname,
1002                                 session->tweet);
1003                 break;
1004         case ACTION_FRIENDS:
1005                 fprintf(log_file, "%s: host=%s retrieving friends timeline\n",
1006                         session->time, session->hostname);
1007                 break;
1008         case ACTION_USER:
1009                 fprintf(log_file, "%s: host=%s retrieving %s's timeline\n",
1010                         session->time, session->hostname, session->user);
1011                 break;
1012         case ACTION_REPLIES:
1013                 fprintf(log_file, "%s: host=%s retrieving replies\n",
1014                         session->time, session->hostname);
1015                 break;
1016         case ACTION_PUBLIC:
1017                 fprintf(log_file, "%s: host=%s retrieving public timeline\n",
1018                         session->time, session->hostname);
1019                 break;
1020         case ACTION_GROUP:
1021                 fprintf(log_file, "%s: host=%s retrieving group timeline\n",
1022                         session->time, session->hostname);
1023                 break;
1024         default:
1025                 break;
1026         }
1027
1028         fclose(log_file);
1029 }
1030
1031 static char *get_string_from_stdin(void)
1032 {
1033         char *temp;
1034         char *string;
1035
1036         string = zalloc(1000);
1037         if (!string)
1038                 return NULL;
1039
1040         if (!fgets(string, 999, stdin))
1041                 return NULL;
1042         temp = strchr(string, '\n');
1043         if (temp)
1044                 *temp = '\0';
1045         return string;
1046 }
1047
1048 static void read_password(char *buf, size_t len, char *host)
1049 {
1050         char pwd[80];
1051         int retval;
1052         struct termios old;
1053         struct termios tp;
1054
1055         tcgetattr(0, &tp);
1056         old = tp;
1057
1058         tp.c_lflag &= (~ECHO);
1059         tcsetattr(0, TCSANOW, &tp);
1060
1061         fprintf(stdout, "Enter password for %s: ", host);
1062         fflush(stdout);
1063         tcflow(0, TCOOFF);
1064         retval = scanf("%79s", pwd);
1065         tcflow(0, TCOON);
1066         fprintf(stdout, "\n");
1067
1068         tcsetattr(0, TCSANOW, &old);
1069
1070         strncpy(buf, pwd, len);
1071         buf[len-1] = '\0';
1072 }
1073
1074 static int find_urls(const char *tweet, int **pranges)
1075 {
1076         /*
1077          * magic obtained from
1078          * http://www.geekpedia.com/KB65_How-to-validate-an-URL-using-RegEx-in-Csharp.html
1079          */
1080         static const char *re_magic =
1081                 "(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)/{1,3}"
1082                 "[0-9a-zA-Z;/~?:@&=+$\\.\\-_'()%]+)"
1083                 "(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?";
1084         pcre *re;
1085         const char *errptr;
1086         int erroffset;
1087         int ovector[10] = {0,};
1088         const size_t ovsize = sizeof(ovector)/sizeof(*ovector);
1089         int startoffset, tweetlen;
1090         int i, rc;
1091         int rbound = 10;
1092         int rcount = 0;
1093         int *ranges = malloc(sizeof(int) * rbound);
1094
1095         re = pcre_compile(re_magic,
1096                         PCRE_NO_AUTO_CAPTURE,
1097                         &errptr, &erroffset, NULL);
1098         if (!re) {
1099                 fprintf(stderr, "pcre_compile @%u: %s\n", erroffset, errptr);
1100                 exit(1);
1101         }
1102
1103         tweetlen = strlen(tweet);
1104         for (startoffset = 0; startoffset < tweetlen; ) {
1105
1106                 rc = pcre_exec(re, NULL, tweet, strlen(tweet), startoffset, 0,
1107                                 ovector, ovsize);
1108                 if (rc == PCRE_ERROR_NOMATCH)
1109                         break;
1110
1111                 if (rc < 0) {
1112                         fprintf(stderr, "pcre_exec @%u: %s\n",
1113                                 erroffset, errptr);
1114                         exit(1);
1115                 }
1116
1117                 for (i = 0; i < rc; i += 2) {
1118                         if ((rcount+2) == rbound) {
1119                                 rbound *= 2;
1120                                 ranges = realloc(ranges, sizeof(int) * rbound);
1121                         }
1122
1123                         ranges[rcount++] = ovector[i];
1124                         ranges[rcount++] = ovector[i+1];
1125                 }
1126
1127                 startoffset = ovector[1];
1128         }
1129
1130         pcre_free(re);
1131
1132         *pranges = ranges;
1133         return rcount;
1134 }
1135
1136 /**
1137  * bidirectional popen() call
1138  *
1139  * @param rwepipe - int array of size three
1140  * @param exe - program to run
1141  * @param argv - argument list
1142  * @return pid or -1 on error
1143  *
1144  * The caller passes in an array of three integers (rwepipe), on successful
1145  * execution it can then write to element 0 (stdin of exe), and read from
1146  * element 1 (stdout) and 2 (stderr).
1147  */
1148 static int popenRWE(int *rwepipe, const char *exe, const char *const argv[])
1149 {
1150         int in[2];
1151         int out[2];
1152         int err[2];
1153         int pid;
1154         int rc;
1155
1156         rc = pipe(in);
1157         if (rc < 0)
1158                 goto error_in;
1159
1160         rc = pipe(out);
1161         if (rc < 0)
1162                 goto error_out;
1163
1164         rc = pipe(err);
1165         if (rc < 0)
1166                 goto error_err;
1167
1168         pid = fork();
1169         if (pid > 0) {
1170                 /* parent */
1171                 close(in[0]);
1172                 close(out[1]);
1173                 close(err[1]);
1174                 rwepipe[0] = in[1];
1175                 rwepipe[1] = out[0];
1176                 rwepipe[2] = err[0];
1177                 return pid;
1178         } else if (pid == 0) {
1179                 /* child */
1180                 close(in[1]);
1181                 close(out[0]);
1182                 close(err[0]);
1183                 close(0);
1184                 rc = dup(in[0]);
1185                 close(1);
1186                 rc = dup(out[1]);
1187                 close(2);
1188                 rc = dup(err[1]);
1189
1190                 execvp(exe, (char **)argv);
1191                 exit(1);
1192         } else
1193                 goto error_fork;
1194
1195         return pid;
1196
1197 error_fork:
1198         close(err[0]);
1199         close(err[1]);
1200 error_err:
1201         close(out[0]);
1202         close(out[1]);
1203 error_out:
1204         close(in[0]);
1205         close(in[1]);
1206 error_in:
1207         return -1;
1208 }
1209
1210 static int pcloseRWE(int pid, int *rwepipe)
1211 {
1212         int rc, status;
1213         close(rwepipe[0]);
1214         close(rwepipe[1]);
1215         close(rwepipe[2]);
1216         rc = waitpid(pid, &status, 0);
1217         return status;
1218 }
1219
1220 static char *shrink_one_url(int *rwepipe, char *big)
1221 {
1222         int biglen = strlen(big);
1223         char *small;
1224         int smalllen;
1225         int rc;
1226
1227         rc = dprintf(rwepipe[0], "%s\n", big);
1228         if (rc < 0)
1229                 return big;
1230
1231         smalllen = biglen + 128;
1232         small = malloc(smalllen);
1233         if (!small)
1234                 return big;
1235
1236         rc = read(rwepipe[1], small, smalllen);
1237         if (rc < 0 || rc > biglen)
1238                 goto error_free_small;
1239
1240         if (strncmp(small, "http://", 7))
1241                 goto error_free_small;
1242
1243         smalllen = rc;
1244         while (smalllen && isspace(small[smalllen-1]))
1245                         small[--smalllen] = 0;
1246
1247         free(big);
1248         return small;
1249
1250 error_free_small:
1251         free(small);
1252         return big;
1253 }
1254
1255 static char *shrink_urls(char *text)
1256 {
1257         int *ranges;
1258         int rcount;
1259         int i;
1260         int inofs = 0;
1261         int outofs = 0;
1262         const char *const shrink_args[] = {
1263                 "bti-shrink-urls",
1264                 NULL
1265         };
1266         int shrink_pid;
1267         int shrink_pipe[3];
1268         int inlen = strlen(text);
1269
1270         dbg("before len=%u\n", inlen);
1271
1272         shrink_pid = popenRWE(shrink_pipe, shrink_args[0], shrink_args);
1273         if (shrink_pid < 0)
1274                 return text;
1275
1276         rcount = find_urls(text, &ranges);
1277         if (!rcount)
1278                 return text;
1279
1280         for (i = 0; i < rcount; i += 2) {
1281                 int url_start = ranges[i];
1282                 int url_end = ranges[i+1];
1283                 int long_url_len = url_end - url_start;
1284                 char *url = strndup(text + url_start, long_url_len);
1285                 int short_url_len;
1286                 int not_url_len = url_start - inofs;
1287
1288                 dbg("long  url[%u]: %s\n", long_url_len, url);
1289                 url = shrink_one_url(shrink_pipe, url);
1290                 short_url_len = url ? strlen(url) : 0;
1291                 dbg("short url[%u]: %s\n", short_url_len, url);
1292
1293                 if (!url || short_url_len >= long_url_len) {
1294                         /* The short url ended up being too long
1295                          * or unavailable */
1296                         if (inofs) {
1297                                 strncpy(text + outofs, text + inofs,
1298                                                 not_url_len + long_url_len);
1299                         }
1300                         inofs += not_url_len + long_url_len;
1301                         outofs += not_url_len + long_url_len;
1302
1303                 } else {
1304                         /* copy the unmodified block */
1305                         strncpy(text + outofs, text + inofs, not_url_len);
1306                         inofs += not_url_len;
1307                         outofs += not_url_len;
1308
1309                         /* copy the new url */
1310                         strncpy(text + outofs, url, short_url_len);
1311                         inofs += long_url_len;
1312                         outofs += short_url_len;
1313                 }
1314
1315                 free(url);
1316         }
1317
1318         /* copy the last block after the last match */
1319         if (inofs) {
1320                 int tail = inlen - inofs;
1321                 if (tail) {
1322                         strncpy(text + outofs, text + inofs, tail);
1323                         outofs += tail;
1324                 }
1325         }
1326
1327         free(ranges);
1328
1329         (void)pcloseRWE(shrink_pid, shrink_pipe);
1330
1331         text[outofs] = 0;
1332         dbg("after len=%u\n", outofs);
1333         return text;
1334 }
1335
1336 int main(int argc, char *argv[], char *envp[])
1337 {
1338         static const struct option options[] = {
1339                 { "debug", 0, NULL, 'd' },
1340                 { "verbose", 0, NULL, 'V' },
1341                 { "account", 1, NULL, 'a' },
1342                 { "password", 1, NULL, 'p' },
1343                 { "host", 1, NULL, 'H' },
1344                 { "proxy", 1, NULL, 'P' },
1345                 { "action", 1, NULL, 'A' },
1346                 { "user", 1, NULL, 'u' },
1347                 { "group", 1, NULL, 'G' },
1348                 { "logfile", 1, NULL, 'L' },
1349                 { "shrink-urls", 0, NULL, 's' },
1350                 { "help", 0, NULL, 'h' },
1351                 { "bash", 0, NULL, 'b' },
1352                 { "background", 0, NULL, 'B' },
1353                 { "dry-run", 0, NULL, 'n' },
1354                 { "page", 1, NULL, 'g' },
1355                 { "version", 0, NULL, 'v' },
1356                 { "config", 1, NULL, 'c' },
1357                 { "replyto", 1, NULL, 'r' },
1358                 { "retweet", 1, NULL, 'w' },
1359                 { }
1360         };
1361         struct session *session;
1362         pid_t child;
1363         char *tweet;
1364         char *retweet;
1365         static char password[80];
1366         int retval = 0;
1367         int option;
1368         char *http_proxy;
1369         time_t t;
1370         int page_nr;
1371
1372         debug = 0;
1373
1374         session = session_alloc();
1375         if (!session) {
1376                 fprintf(stderr, "no more memory...\n");
1377                 return -1;
1378         }
1379
1380         /* get the current time so that we can log it later */
1381         time(&t);
1382         session->time = strdup(ctime(&t));
1383         session->time[strlen(session->time)-1] = 0x00;
1384
1385         /* Get the home directory so we can try to find a config file */
1386         session->homedir = strdup(getenv("HOME"));
1387
1388         /* set up a default config file location (traditionally ~/.bti) */
1389         session->configfile = zalloc(strlen(session->homedir) + 7);
1390         sprintf(session->configfile, "%s/.bti", session->homedir);
1391
1392         /* Set environment variables first, before reading command line options
1393          * or config file values. */
1394         http_proxy = getenv("http_proxy");
1395         if (http_proxy) {
1396                 if (session->proxy)
1397                         free(session->proxy);
1398                 session->proxy = strdup(http_proxy);
1399                 dbg("http_proxy = %s\n", session->proxy);
1400         }
1401
1402         parse_configfile(session);
1403
1404         while (1) {
1405                 option = getopt_long_only(argc, argv,
1406                                           "dp:P:H:a:A:u:c:hg:G:sr:nVv",
1407                                           options, NULL);
1408                 if (option == -1)
1409                         break;
1410                 switch (option) {
1411                 case 'd':
1412                         debug = 1;
1413                         break;
1414                 case 'V':
1415                         session->verbose = 1;
1416                         break;
1417                 case 'a':
1418                         if (session->account)
1419                                 free(session->account);
1420                         session->account = strdup(optarg);
1421                         dbg("account = %s\n", session->account);
1422                         break;
1423                 case 'g':
1424                         page_nr = atoi(optarg);
1425                         dbg("page = %d\n", page_nr);
1426                         session->page = page_nr;
1427                         break;
1428                 case 'r':
1429                         session->replyto = strdup(optarg);
1430                         dbg("in_reply_to_status_id = %s\n", session->replyto);
1431                         break;
1432                 case 'w':
1433                         session->retweet = strdup(optarg);
1434                         dbg("Retweet ID = %s\n", session->retweet);
1435                         break;
1436                 case 'p':
1437                         if (session->password)
1438                                 free(session->password);
1439                         session->password = strdup(optarg);
1440                         dbg("password = %s\n", session->password);
1441                         break;
1442                 case 'P':
1443                         if (session->proxy)
1444                                 free(session->proxy);
1445                         session->proxy = strdup(optarg);
1446                         dbg("proxy = %s\n", session->proxy);
1447                         break;
1448                 case 'A':
1449                         if (strcasecmp(optarg, "update") == 0)
1450                                 session->action = ACTION_UPDATE;
1451                         else if (strcasecmp(optarg, "friends") == 0)
1452                                 session->action = ACTION_FRIENDS;
1453                         else if (strcasecmp(optarg, "user") == 0)
1454                                 session->action = ACTION_USER;
1455                         else if (strcasecmp(optarg, "replies") == 0)
1456                                 session->action = ACTION_REPLIES;
1457                         else if (strcasecmp(optarg, "public") == 0)
1458                                 session->action = ACTION_PUBLIC;
1459                         else if (strcasecmp(optarg, "group") == 0)
1460                                 session->action = ACTION_GROUP;
1461                         else if (strcasecmp(optarg,"retweet") == 0)
1462                                 session->action = ACTION_RETWEET;
1463                         else
1464                                 session->action = ACTION_UNKNOWN;
1465                         dbg("action = %d\n", session->action);
1466                         break;
1467                 case 'u':
1468                         if (session->user)
1469                                 free(session->user);
1470                         session->user = strdup(optarg);
1471                         dbg("user = %s\n", session->user);
1472                         break;
1473
1474                 case 'G':
1475                         if (session->group)
1476                                 free(session->group);
1477                         session->group = strdup(optarg);
1478                         dbg("group = %s\n", session->group);
1479                         break;
1480                 case 'L':
1481                         if (session->logfile)
1482                                 free(session->logfile);
1483                         session->logfile = strdup(optarg);
1484                         dbg("logfile = %s\n", session->logfile);
1485                         break;
1486                 case 's':
1487                         session->shrink_urls = 1;
1488                         break;
1489                 case 'H':
1490                         if (session->hosturl)
1491                                 free(session->hosturl);
1492                         if (session->hostname)
1493                                 free(session->hostname);
1494                         if (strcasecmp(optarg, "twitter") == 0) {
1495                                 session->host = HOST_TWITTER;
1496                                 session->hosturl = strdup(twitter_host);
1497                                 session->hostname = strdup(twitter_name);
1498                         } else if (strcasecmp(optarg, "identica") == 0) {
1499                                 session->host = HOST_IDENTICA;
1500                                 session->hosturl = strdup(identica_host);
1501                                 session->hostname = strdup(identica_name);
1502                         } else {
1503                                 session->host = HOST_CUSTOM;
1504                                 session->hosturl = strdup(optarg);
1505                                 session->hostname = strdup(optarg);
1506                         }
1507                         dbg("host = %d\n", session->host);
1508                         break;
1509                 case 'b':
1510                         session->bash = 1;
1511                         /* fall-through intended */
1512                 case 'B':
1513                         session->background = 1;
1514                         break;
1515                 case 'c':
1516                         if (session->configfile)
1517                                 free(session->configfile);
1518                         session->configfile = strdup(optarg);
1519                         dbg("configfile = %s\n", session->configfile);
1520
1521                         /*
1522                          * read the config file now.  Yes, this could override
1523                          * previously set options from the command line, but
1524                          * the user asked for it...
1525                          */
1526                         parse_configfile(session);
1527                         break;
1528                 case 'h':
1529                         display_help();
1530                         goto exit;
1531                 case 'n':
1532                         session->dry_run = 1;
1533                         break;
1534                 case 'v':
1535                         display_version();
1536                         goto exit;
1537                 default:
1538                         display_help();
1539                         goto exit;
1540                 }
1541         }
1542
1543         session_readline_init(session);
1544         /*
1545          * Show the version to make it easier to determine what
1546          * is going on here
1547          */
1548         if (debug)
1549                 display_version();
1550
1551         if (session->host == HOST_TWITTER) {
1552                 if (!session->consumer_key || !session->consumer_secret) {
1553                         if (session->action == ACTION_USER ||
1554                                         session->action == ACTION_PUBLIC) {
1555                                 /* Some actions may still work without authentication */
1556                                 session->guest = 1;
1557                         } else {
1558                                 fprintf(stderr,
1559                                                 "Twitter no longer supports HTTP basic authentication.\n"
1560                                                 "Both consumer key, and consumer secret are required"
1561                                                 " for bti in order to behave as an OAuth consumer.\n");
1562                                 goto exit;
1563                         }
1564                 }
1565                 if (session->action == ACTION_GROUP) {
1566                         fprintf(stderr, "Groups only work in Identi.ca.\n");
1567                         goto exit;
1568                 }
1569         } else {
1570                 if (!session->consumer_key || !session->consumer_secret)
1571                         session->no_oauth = 1;
1572         }
1573
1574         if (session->no_oauth) {
1575                 if (!session->account) {
1576                         fprintf(stdout, "Enter account for %s: ",
1577                                 session->hostname);
1578                         session->account = session->readline(NULL);
1579                 }
1580                 if (!session->password) {
1581                         read_password(password, sizeof(password),
1582                                       session->hostname);
1583                         session->password = strdup(password);
1584                 }
1585         } else if (!session->guest) {
1586                 if (!session->access_token_key ||
1587                     !session->access_token_secret) {
1588                         request_access_token(session);
1589                         goto exit;
1590                 }
1591         }
1592
1593         if (session->action == ACTION_UNKNOWN) {
1594                 fprintf(stderr, "Unknown action, valid actions are:\n"
1595                         "'update', 'friends', 'public', 'replies', 'group' or 'user'.\n");
1596                 goto exit;
1597         }
1598
1599         if (session->action == ACTION_GROUP && !session->group) {
1600                 fprintf(stdout, "Enter group name: ");
1601                 session->group = session->readline(NULL);
1602         }
1603
1604         if (session->action == ACTION_RETWEET) {
1605                 fprintf(stdout, "Status ID to retweet: ");
1606                 retweet = get_string_from_stdin();
1607
1608                 if (!retweet || strlen(retweet) == 0) {
1609                         dbg("no retweet?\n");
1610                         return -1;
1611                 }
1612
1613                 session->retweet = zalloc(strlen(retweet) + 10);
1614                 sprintf(session->retweet,"%s", retweet);
1615                 free(retweet);
1616                 dbg("retweet ID = %s\n", session->retweet);
1617         }
1618
1619         if (session->action == ACTION_UPDATE) {
1620                 if (session->background || !session->interactive)
1621                         tweet = get_string_from_stdin();
1622                 else
1623                         tweet = session->readline("tweet: ");
1624                 if (!tweet || strlen(tweet) == 0) {
1625                         dbg("no tweet?\n");
1626                         return -1;
1627                 }
1628
1629                 if (session->shrink_urls)
1630                         tweet = shrink_urls(tweet);
1631
1632                 session->tweet = zalloc(strlen(tweet) + 10);
1633                 if (session->bash)
1634                         sprintf(session->tweet, "%c %s",
1635                                 getuid() ? '$' : '#', tweet);
1636                 else
1637                         sprintf(session->tweet, "%s", tweet);
1638
1639                 free(tweet);
1640                 dbg("tweet = %s\n", session->tweet);
1641         }
1642
1643         if (session->page == 0)
1644                 session->page = 1;
1645         dbg("config file = %s\n", session->configfile);
1646         dbg("host = %d\n", session->host);
1647         dbg("action = %d\n", session->action);
1648
1649         /* fork ourself so that the main shell can get on
1650          * with it's life as we try to connect and handle everything
1651          */
1652         if (session->background) {
1653                 child = fork();
1654                 if (child) {
1655                         dbg("child is %d\n", child);
1656                         exit(0);
1657                 }
1658         }
1659
1660         retval = send_request(session);
1661         if (retval && !session->background)
1662                 fprintf(stderr, "operation failed\n");
1663
1664         log_session(session, retval);
1665 exit:
1666         session_readline_cleanup(session);
1667         session_free(session);
1668         return retval;;
1669 }