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