New upstream release; from the RELEASE-NOTES:
[debian/bti.git] / bti.c
1 /*
2  * Copyright (C) 2008 Greg Kroah-Hartman <greg@kroah.com>
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the
6  * Free Software Foundation version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16  */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <stddef.h>
21 #include <string.h>
22 #include <getopt.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <time.h>
28 #include <sys/stat.h>
29 #include <curl/curl.h>
30 #include <readline/readline.h>
31 #include <libxml/xmlmemory.h>
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #include "bti_version.h"
35
36
37 #define zalloc(size)    calloc(size, 1)
38
39 #define dbg(format, arg...)                                             \
40         do {                                                            \
41                 if (debug)                                              \
42                         printf("%s: " format , __func__ , ## arg);      \
43         } while (0)
44
45
46 static int debug;
47
48 enum host {
49         HOST_TWITTER = 0,
50         HOST_IDENTICA = 1,
51 };
52
53 enum action {
54         ACTION_UPDATE = 0,
55         ACTION_FRIENDS = 1,
56         ACTION_USER = 2,
57         ACTION_REPLIES = 4,
58         ACTION_PUBLIC = 8,
59         ACTION_UNKNOWN = 16
60 };
61
62 struct session {
63         char *password;
64         char *account;
65         char *tweet;
66         char *proxy;
67         char *time;
68         char *homedir;
69         char *logfile;
70         char *user;
71         int bash;
72         enum host host;
73         enum action action;
74 };
75
76 struct bti_curl_buffer {
77         char *data;
78         enum action action;
79         int length;
80 };
81
82 static void display_help(void)
83 {
84         fprintf(stdout, "bti - send tweet to twitter or identi.ca\n");
85         fprintf(stdout, "Version: " BTI_VERSION "\n");
86         fprintf(stdout, "Usage:\n");
87         fprintf(stdout, "  bti [options]\n");
88         fprintf(stdout, "options are:\n");
89         fprintf(stdout, "  --account accountname\n");
90         fprintf(stdout, "  --password password\n");
91         fprintf(stdout, "  --action action\n");
92         fprintf(stdout, "    ('update', 'friends', 'public', 'replies' or 'user')\n");
93         fprintf(stdout, "  --user screenname\n");
94         fprintf(stdout, "  --proxy PROXY:PORT\n");
95         fprintf(stdout, "  --host HOST\n");
96         fprintf(stdout, "  --logfile logfile\n");
97         fprintf(stdout, "  --bash\n");
98         fprintf(stdout, "  --debug\n");
99         fprintf(stdout, "  --version\n");
100         fprintf(stdout, "  --help\n");
101 }
102
103 static void display_version(void)
104 {
105         fprintf(stdout, "bti - version %s\n", BTI_VERSION);
106 }
107
108 static struct session *session_alloc(void)
109 {
110         struct session *session;
111
112         session = zalloc(sizeof(*session));
113         if (!session)
114                 return NULL;
115         return session;
116 }
117
118 static void session_free(struct session *session)
119 {
120         if (!session)
121                 return;
122         free(session->password);
123         free(session->account);
124         free(session->tweet);
125         free(session->proxy);
126         free(session->time);
127         free(session->homedir);
128         free(session->user);
129         free(session);
130 }
131
132 static struct bti_curl_buffer *bti_curl_buffer_alloc(enum action action)
133 {
134         struct bti_curl_buffer *buffer;
135
136         buffer = zalloc(sizeof(*buffer));
137         if (!buffer)
138                 return NULL;
139
140         /* start out with a data buffer of 1 byte to
141          * make the buffer fill logic simpler */
142         buffer->data = zalloc(1);
143         if (!buffer->data) {
144                 free(buffer);
145                 return NULL;
146         }
147         buffer->length = 0;
148         buffer->action = action;
149         return buffer;
150 }
151
152 static void bti_curl_buffer_free(struct bti_curl_buffer *buffer)
153 {
154         if (!buffer)
155                 return;
156         free(buffer->data);
157         free(buffer);
158 }
159
160 static const char *twitter_user_url    = "http://twitter.com/statuses/user_timeline/";
161 static const char *twitter_update_url  = "https://twitter.com/statuses/update.xml";
162 static const char *twitter_public_url  = "http://twitter.com/statuses/public_timeline.xml";
163 static const char *twitter_friends_url = "https://twitter.com/statuses/friends_timeline.xml";
164 static const char *twitter_replies_url = "http://twitter.com/statuses/replies.xml";
165
166 static const char *identica_user_url    = "http://identi.ca/api/statuses/user_timeline/";
167 static const char *identica_update_url  = "http://identi.ca/api/statuses/update.xml";
168 static const char *identica_public_url  = "http://identi.ca/api/statuses/public_timeline.xml";
169 static const char *identica_friends_url = "http://identi.ca/api/statuses/friends_timeline.xml";
170 static const char *identica_replies_url = "http://identi.ca/api/statuses/replies.xml";
171
172 static CURL *curl_init(void)
173 {
174         CURL *curl;
175
176         curl = curl_easy_init();
177         if (!curl) {
178                 fprintf(stderr, "Can not init CURL!\n");
179                 return NULL;
180         }
181         /* some ssl sanity checks on the connection we are making */
182         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
183         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
184         return curl;
185 }
186
187 void parse_statuses(xmlDocPtr doc, xmlNodePtr current)
188 {
189         xmlChar *text = NULL;
190         xmlChar *user = NULL;
191         xmlNodePtr userinfo;
192
193         current = current->xmlChildrenNode;
194         while (current != NULL) {
195                 if (current->type == XML_ELEMENT_NODE) {
196                         if (!xmlStrcmp(current->name, (const xmlChar *)"text"))
197                                 text = xmlNodeListGetString(doc, current->xmlChildrenNode, 1);
198                         if (!xmlStrcmp(current->name, (const xmlChar *)"user")) {
199                                 userinfo = current->xmlChildrenNode;
200                                 while (userinfo != NULL) {
201                                         if ((!xmlStrcmp(userinfo->name, (const xmlChar *)"screen_name"))) {
202                                                 if (user)
203                                                         xmlFree(user);
204                                                 user = xmlNodeListGetString(doc, userinfo->xmlChildrenNode, 1);
205                                         }
206                                         userinfo = userinfo->next;
207                                 }
208                         }
209                         if (user && text) {
210                                 printf("[%s] %s\n", user, text);
211                                 xmlFree(user);
212                                 xmlFree(text);
213                                 user = NULL;
214                                 text = NULL;
215                         }
216                 }
217                 current = current->next;
218         }
219
220         return;
221 }
222
223 static void parse_timeline(char *document)
224 {
225         xmlDocPtr doc;
226         xmlNodePtr current;
227         doc = xmlReadMemory(document, strlen(document), "timeline.xml", NULL, XML_PARSE_NOERROR);
228
229         if (doc == NULL)
230                 return;
231
232         current = xmlDocGetRootElement(doc);
233         if (current == NULL) {
234                 fprintf(stderr, "empty document\n");
235                 xmlFreeDoc(doc);
236                 return;
237         }
238
239         if (xmlStrcmp(current->name, (const xmlChar *) "statuses")) {
240                 fprintf(stderr, "unexpected document type\n");
241                 xmlFreeDoc(doc);
242                 return;
243         }
244
245         current = current->xmlChildrenNode;
246         while (current != NULL) {
247                 if ((!xmlStrcmp(current->name, (const xmlChar *)"status")))
248                         parse_statuses(doc, current);
249                 current = current->next;
250         }
251         xmlFreeDoc(doc);
252
253         return;
254 }
255
256 size_t curl_callback(void *buffer, size_t size, size_t nmemb, void *userp)
257 {
258         struct bti_curl_buffer *curl_buf = userp;
259         size_t buffer_size = size * nmemb;
260         char *temp;
261
262         if ((!buffer) || (!buffer_size) || (!curl_buf))
263                 return -EINVAL;
264
265         /* add to the data we already have */
266         temp = zalloc(curl_buf->length + buffer_size + 1);
267         if (!temp)
268                 return -ENOMEM;
269
270         memcpy(temp, curl_buf->data, curl_buf->length);
271         free(curl_buf->data);
272         curl_buf->data = temp;
273         memcpy(&curl_buf->data[curl_buf->length], (char *)buffer, buffer_size);
274         curl_buf->length += buffer_size;
275         if (curl_buf->action)
276                 parse_timeline(curl_buf->data);
277
278         dbg("%s\n", curl_buf->data);
279
280         return buffer_size;
281 }
282
283 static int send_request(struct session *session)
284 {
285         char user_password[500];
286         char data[500];
287         /* is there usernames longer than 22 chars? */
288         char user_url[70];
289         struct bti_curl_buffer *curl_buf;
290         CURL *curl = NULL;
291         CURLcode res;
292         struct curl_httppost *formpost = NULL;
293         struct curl_httppost *lastptr = NULL;
294         struct curl_slist *slist = NULL;
295
296         if (!session)
297                 return -EINVAL;
298
299         curl_buf = bti_curl_buffer_alloc(session->action);
300         if (!curl_buf)
301                 return -ENOMEM;
302
303         curl = curl_init();
304         if (!curl)
305                 return -EINVAL;
306
307         switch (session->action) {
308         case ACTION_UPDATE:
309                 snprintf(user_password, sizeof(user_password), "%s:%s",
310                          session->account, session->password);
311                 snprintf(data, sizeof(data), "status=\"%s\"", session->tweet);
312                 curl_formadd(&formpost, &lastptr,
313                              CURLFORM_COPYNAME, "status",
314                              CURLFORM_COPYCONTENTS, session->tweet,
315                              CURLFORM_END);
316
317                 curl_formadd(&formpost, &lastptr,
318                              CURLFORM_COPYNAME, "source",
319                              CURLFORM_COPYCONTENTS, "bti",
320                              CURLFORM_END);
321
322                 curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
323                 slist = curl_slist_append(slist, "Expect:");
324                 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
325                 switch (session->host) {
326                 case HOST_TWITTER:
327                         curl_easy_setopt(curl, CURLOPT_URL, twitter_update_url);
328                         break;
329                 case HOST_IDENTICA:
330                         curl_easy_setopt(curl, CURLOPT_URL, identica_update_url);
331                         break;
332                 }
333                 curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
334
335                 break;
336         case ACTION_FRIENDS:
337                 snprintf(user_password, sizeof(user_password), "%s:%s",
338                          session->account, session->password);
339                 switch (session->host) {
340                 case HOST_TWITTER:
341                         curl_easy_setopt(curl, CURLOPT_URL, twitter_friends_url);
342                         break;
343                 case HOST_IDENTICA:
344                         curl_easy_setopt(curl, CURLOPT_URL, identica_friends_url);
345                         break;
346                 }
347                 curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
348
349                 break;
350         case ACTION_USER:
351                 switch (session->host) {
352                 case HOST_TWITTER:
353                         sprintf(user_url, "%s%s.xml", twitter_user_url, session->user);
354                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
355                         break;
356                 case HOST_IDENTICA:
357                         sprintf(user_url, "%s%s.xml", identica_user_url, session->user);
358                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
359                         break;
360                 }
361
362                 break;
363         case ACTION_REPLIES:
364                 snprintf(user_password, sizeof(user_password), "%s:%s",
365                          session->account, session->password);
366                 switch (session->host) {
367                 case HOST_TWITTER:
368                         curl_easy_setopt(curl, CURLOPT_URL, twitter_replies_url);
369                         break;
370                 case HOST_IDENTICA:
371                         curl_easy_setopt(curl, CURLOPT_URL, identica_replies_url);
372                         break;
373                 }
374                 curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
375
376                 break;
377         case ACTION_PUBLIC:
378                 switch (session->host) {
379                 case HOST_TWITTER:
380                         curl_easy_setopt(curl, CURLOPT_URL, twitter_public_url);
381                         break;
382                 case HOST_IDENTICA:
383                         curl_easy_setopt(curl, CURLOPT_URL, identica_public_url);
384                         break;
385                 }
386
387                 break;
388         default:
389                 break;
390         }
391
392         if (session->proxy)
393                 curl_easy_setopt(curl, CURLOPT_PROXY, session->proxy);
394
395         if (debug)
396                 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
397
398         dbg("user_password = %s\n", user_password);
399         dbg("data = %s\n", data);
400         dbg("proxy = %s\n", session->proxy);
401
402         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback);
403         curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf);
404         res = curl_easy_perform(curl);
405         if (res && !session->bash) {
406                 fprintf(stderr, "error(%d) trying to perform operation\n", res);
407                 return -EINVAL;
408         }
409
410         curl_easy_cleanup(curl);
411         if (session->action == ACTION_UPDATE)
412                 curl_formfree(formpost);
413         bti_curl_buffer_free(curl_buf);
414         return 0;
415 }
416
417 static void parse_configfile(struct session *session)
418 {
419         FILE *config_file;
420         char *line = NULL;
421         size_t len = 0;
422         char *account = NULL;
423         char *password = NULL;
424         char *host = NULL;
425         char *proxy = NULL;
426         char *logfile = NULL;
427         char *action = NULL;
428         char *user = NULL;
429         char *file;
430
431         /* config file is ~/.bti  */
432         file = alloca(strlen(session->homedir) + 7);
433
434         sprintf(file, "%s/.bti", session->homedir);
435
436         config_file = fopen(file, "r");
437
438         /* No error if file does not exist or is unreadable.  */
439         if (config_file == NULL)
440                 return;
441
442         do {
443                 ssize_t n = getline(&line, &len, config_file);
444                 if (n < 0)
445                         break;
446                 if (line[n - 1] == '\n')
447                         line[n - 1] = '\0';
448                 /* Parse file.  Format is the usual value pairs:
449                    account=name
450                    passwort=value
451                    # is a comment character
452                 */
453                 *strchrnul(line, '#') = '\0';
454                 char *c = line;
455                 while (isspace(*c))
456                         c++;
457                 /* Ignore blank lines.  */
458                 if (c[0] == '\0')
459                         continue;
460
461                 if (!strncasecmp(c, "account", 7) && (c[7] == '=')) {
462                         c += 8;
463                         if (c[0] != '\0')
464                                 account = strdup(c);
465                 } else if (!strncasecmp(c, "password", 8) &&
466                            (c[8] == '=')) {
467                         c += 9;
468                         if (c[0] != '\0')
469                                 password = strdup(c);
470                 } else if (!strncasecmp(c, "host", 4) &&
471                            (c[4] == '=')) {
472                         c += 5;
473                         if (c[0] != '\0')
474                                 host = strdup(c);
475                 } else if (!strncasecmp(c, "proxy", 5) &&
476                            (c[5] == '=')) {
477                         c += 6;
478                         if (c[0] != '\0')
479                                 proxy = strdup(c);
480                 } else if (!strncasecmp(c, "logfile", 7) &&
481                            (c[7] == '=')) {
482                         c += 8;
483                         if (c[0] != '\0')
484                                 logfile = strdup(c);
485                 } else if (!strncasecmp(c, "action", 6) &&
486                            (c[6] == '=')) {
487                         c += 7;
488                         if (c[0] != '\0')
489                                 action = strdup(c);
490                 } else if (!strncasecmp(c, "user", 4) &&
491                                 (c[4] == '=')) {
492                         c += 5;
493                         if (c[0] != '\0')
494                                 user = strdup(c);
495                 }
496         } while (!feof(config_file));
497
498         if (password)
499                 session->password = password;
500         if (account)
501                 session->account = account;
502         if (host) {
503                 if (strcasecmp(host, "twitter") == 0)
504                         session->host = HOST_TWITTER;
505                 if (strcasecmp(host, "identica") == 0)
506                         session->host = HOST_IDENTICA;
507                 free(host);
508         }
509         if (proxy) {
510                 if (session->proxy)
511                         free(session->proxy);
512                 session->proxy = proxy;
513         }
514         if (logfile)
515                 session->logfile = logfile;
516         if (action) {
517                 if (strcasecmp(action, "update") == 0)
518                         session->action = ACTION_UPDATE;
519                 else if (strcasecmp(action, "friends") == 0)
520                         session->action = ACTION_FRIENDS;
521                 else if (strcasecmp(action, "user") == 0)
522                         session->action = ACTION_USER;
523                 else if (strcasecmp(action, "replies") == 0)
524                         session->action = ACTION_REPLIES;
525                 else if (strcasecmp(action, "public") == 0)
526                         session->action = ACTION_PUBLIC;
527                 else
528                         session->action = ACTION_UNKNOWN;
529                 free(action);
530         }
531         if (user) {
532                 session->user = user;
533         }
534
535         /* Free buffer and close file.  */
536         free(line);
537         fclose(config_file);
538 }
539
540 static void log_session(struct session *session, int retval)
541 {
542         FILE *log_file;
543         char *filename;
544         char *host;
545
546         /* Only log something if we have a log file set */
547         if (!session->logfile)
548                 return;
549
550         filename = alloca(strlen(session->homedir) +
551                           strlen(session->logfile) + 3);
552
553         sprintf(filename, "%s/%s", session->homedir, session->logfile);
554
555         log_file = fopen(filename, "a+");
556         if (log_file == NULL)
557                 return;
558         switch (session->host) {
559         case HOST_TWITTER:
560                 host = "twitter";
561                 break;
562         case HOST_IDENTICA:
563                 host = "identi.ca";
564                 break;
565         default:
566                 host = "unknown";
567                 break;
568         }
569
570         switch (session->action) {
571         case ACTION_UPDATE:
572                 if (retval)
573                         fprintf(log_file, "%s: host=%s tweet failed\n",
574                                 session->time, host);
575                 else
576                         fprintf(log_file, "%s: host=%s tweet=%s\n",
577                                 session->time, host, session->tweet);
578                 break;
579         case ACTION_FRIENDS:
580                 fprintf(log_file, "%s: host=%s retrieving friends timeline\n",
581                         session->time, host);
582                 break;
583         case ACTION_USER:
584                 fprintf(log_file, "%s: host=%s retrieving %s's timeline\n",
585                         session->time, host, session->user);
586                 break;
587         case ACTION_REPLIES:
588                 fprintf(log_file, "%s: host=%s retrieving replies\n",
589                         session->time, host);
590                 break;
591         case ACTION_PUBLIC:
592                 fprintf(log_file, "%s: host=%s retrieving public timeline\n",
593                         session->time, host);
594                 break;
595         default:
596                 break;
597         }
598
599         fclose(log_file);
600 }
601
602 static char *get_string_from_stdin(void)
603 {
604         char *temp;
605         char *string;
606
607         string = zalloc(1000);
608         if (!string)
609                 return NULL;
610
611         if (!fgets(string, 999, stdin))
612                 return NULL;
613         temp = strchr(string, '\n');
614         *temp = '\0';
615         return string;
616 }
617
618 int main(int argc, char *argv[], char *envp[])
619 {
620         static const struct option options[] = {
621                 { "debug", 0, NULL, 'd' },
622                 { "account", 1, NULL, 'a' },
623                 { "password", 1, NULL, 'p' },
624                 { "host", 1, NULL, 'H' },
625                 { "proxy", 1, NULL, 'P' },
626                 { "action", 1, NULL, 'A' },
627                 { "user", 1, NULL, 'u' },
628                 { "logfile", 1, NULL, 'L' },
629                 { "help", 0, NULL, 'h' },
630                 { "bash", 0, NULL, 'b' },
631                 { "version", 0, NULL, 'v' },
632                 { }
633         };
634         struct session *session;
635         pid_t child;
636         char *tweet;
637         int retval = 0;
638         int option;
639         char *http_proxy;
640         time_t t;
641
642         debug = 0;
643         rl_bind_key('\t', rl_insert);
644
645         session = session_alloc();
646         if (!session) {
647                 fprintf(stderr, "no more memory...\n");
648                 return -1;
649         }
650
651         /* get the current time so that we can log it later */
652         time(&t);
653         session->time = strdup(ctime(&t));
654         session->time[strlen(session->time)-1] = 0x00;
655
656         session->homedir = strdup(getenv("HOME"));
657
658         curl_global_init(CURL_GLOBAL_ALL);
659
660         /* Set environment variables first, before reading command line options
661          * or config file values. */
662         http_proxy = getenv("http_proxy");
663         if (http_proxy) {
664                 if (session->proxy)
665                         free(session->proxy);
666                 session->proxy = strdup(http_proxy);
667                 dbg("http_proxy = %s\n", session->proxy);
668         }
669
670         parse_configfile(session);
671
672         while (1) {
673                 option = getopt_long_only(argc, argv, "dqe:p:P:H:a:A:u:h",
674                                           options, NULL);
675                 if (option == -1)
676                         break;
677                 switch (option) {
678                 case 'd':
679                         debug = 1;
680                         break;
681                 case 'a':
682                         if (session->account)
683                                 free(session->account);
684                         session->account = strdup(optarg);
685                         dbg("account = %s\n", session->account);
686                         break;
687                 case 'p':
688                         if (session->password)
689                                 free(session->password);
690                         session->password = strdup(optarg);
691                         dbg("password = %s\n", session->password);
692                         break;
693                 case 'P':
694                         if (session->proxy)
695                                 free(session->proxy);
696                         session->proxy = strdup(optarg);
697                         dbg("proxy = %s\n", session->proxy);
698                         break;
699                 case 'A':
700                         if (strcasecmp(optarg, "update") == 0)
701                                 session->action = ACTION_UPDATE;
702                         else if (strcasecmp(optarg, "friends") == 0)
703                                 session->action = ACTION_FRIENDS;
704                         else if (strcasecmp(optarg, "user") == 0)
705                                 session->action = ACTION_USER;
706                         else if (strcasecmp(optarg, "replies") == 0)
707                                 session->action = ACTION_REPLIES;
708                         else if (strcasecmp(optarg, "public") == 0)
709                                 session->action = ACTION_PUBLIC;
710                         else
711                                 session->action = ACTION_UNKNOWN;
712                         dbg("action = %d\n", session->action);
713                         break;
714                 case 'u':
715                         if (session->user)
716                                 free(session->user);
717                         session->user = strdup(optarg);
718                         dbg("user = %s\n", session->user);
719                         break;
720                 case 'L':
721                         if (session->logfile)
722                                 free(session->logfile);
723                         session->logfile = strdup(optarg);
724                         dbg("logfile = %s\n", session->logfile);
725                         break;
726                 case 'H':
727                         if (strcasecmp(optarg, "twitter") == 0)
728                                 session->host = HOST_TWITTER;
729                         if (strcasecmp(optarg, "identica") == 0)
730                                 session->host = HOST_IDENTICA;
731                         dbg("host = %d\n", session->host);
732                         break;
733                 case 'b':
734                         session->bash = 1;
735                         break;
736                 case 'h':
737                         display_help();
738                         goto exit;
739                 case 'v':
740                         display_version();
741                         goto exit;
742                 default:
743                         display_help();
744                         goto exit;
745                 }
746         }
747
748         if (session->action == ACTION_UNKNOWN) {
749                 fprintf(stderr, "Unknown action, valid actions are:\n");
750                 fprintf(stderr, "'update', 'friends', 'public', 'replies' or 'user'.\n");
751                 goto exit;
752         }
753
754         if (!session->account) {
755                 fprintf(stdout, "Enter twitter account: ");
756                 session->account = readline(NULL);
757         }
758
759         if (!session->password) {
760                 fprintf(stdout, "Enter twitter password: ");
761                 session->password = readline(NULL);
762         }
763
764         if (session->action == ACTION_UPDATE) {
765                 if (session->bash)
766                         tweet = get_string_from_stdin();
767                 else
768                         tweet = readline("tweet: ");
769                 if (!tweet || strlen(tweet) == 0) {
770                         dbg("no tweet?\n");
771                         return -1;
772                 }
773
774                 session->tweet = zalloc(strlen(tweet) + 10);
775                 if (session->bash)
776                         sprintf(session->tweet, "$ %s", tweet);
777                 else
778                         sprintf(session->tweet, "%s", tweet);
779
780                 free(tweet);
781                 dbg("tweet = %s\n", session->tweet);
782         }
783
784         if (!session->user)
785                 session->user = strdup(session->account);
786
787         dbg("account = %s\n", session->account);
788         dbg("password = %s\n", session->password);
789         dbg("host = %d\n", session->host);
790         dbg("action = %d\n", session->action);
791
792         /* fork ourself so that the main shell can get on
793          * with it's life as we try to connect and handle everything
794          */
795         if (session->bash) {
796                 child = fork();
797                 if (child) {
798                         dbg("child is %d\n", child);
799                         exit(0);
800                 }
801         }
802
803         retval = send_request(session);
804         if (retval && !session->bash)
805                 fprintf(stderr, "operation failed\n");
806
807         log_session(session, retval);
808 exit:
809         session_free(session);
810         return retval;;
811 }