/[debian]/bti/branches/upstream/current/bti.c
ViewVC logotype

Contents of /bti/branches/upstream/current/bti.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.26