disable log support until I can add some configurations for it
[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 "bti_version.h"
32
33
34 #define zalloc(size)    calloc(size, 1)
35
36 #define dbg(format, arg...)                                             \
37         do {                                                            \
38                 if (debug)                                              \
39                         printf("%s: " format , __func__ , ## arg );     \
40         } while (0)
41
42
43 static int debug = 0;
44
45 enum host {
46         HOST_TWITTER = 0,
47         HOST_IDENTICA = 1,
48 };
49
50 struct session {
51         char *password;
52         char *account;
53         char *tweet;
54         char *proxy;
55         char *time;
56         char *homedir;
57         int bash;
58         enum host host;
59 };
60
61 struct bti_curl_buffer {
62         char *data;
63         int length;
64 };
65
66 static void display_help(void)
67 {
68         fprintf(stdout, "bti - send tweet to twitter\n");
69         fprintf(stdout, "Version: " BTI_VERSION "\n");
70         fprintf(stdout, "Usage:\n");
71         fprintf(stdout, "  bti [options]\n");
72         fprintf(stdout, "options are:\n");
73         fprintf(stdout, "  --account accountname\n");
74         fprintf(stdout, "  --password password\n");
75         fprintf(stdout, "  --proxy PROXY:PORT\n");
76         fprintf(stdout, "  --host HOST\n");
77         fprintf(stdout, "  --bash\n");
78         fprintf(stdout, "  --debug\n");
79         fprintf(stdout, "  --version\n");
80         fprintf(stdout, "  --help\n");
81 }
82
83 static void display_version(void)
84 {
85         fprintf(stdout, "bti - version %s\n", BTI_VERSION);
86 }
87
88 static char *get_string_from_stdin(void)
89 {
90         static char *string = (char *)NULL;
91         if (string) {
92                 free(string);
93                 string = (char *)NULL;
94         }
95
96         string = readline("tweet: ");
97
98         return string;
99 }
100
101 static struct session *session_alloc(void)
102 {
103         struct session *session;
104
105         session = zalloc(sizeof(*session));
106         if (!session)
107                 return NULL;
108         return session;
109 }
110
111 static void session_free(struct session *session)
112 {
113         if (!session)
114                 return;
115         free(session->password);
116         free(session->account);
117         free(session->tweet);
118         free(session->proxy);
119         free(session->time);
120         free(session->homedir);
121         free(session);
122 }
123
124 static struct bti_curl_buffer *bti_curl_buffer_alloc(void)
125 {
126         struct bti_curl_buffer *buffer;
127
128         buffer = zalloc(sizeof(*buffer));
129         if (!buffer)
130                 return NULL;
131
132         /* start out with a data buffer of 1 byte to
133          * make the buffer fill logic simpler */
134         buffer->data = zalloc(1);
135         if (!buffer->data) {
136                 free(buffer);
137                 return NULL;
138         }
139         buffer->length = 0;
140         return buffer;
141 }
142
143 static void bti_curl_buffer_free(struct bti_curl_buffer *buffer)
144 {
145         if (!buffer)
146                 return;
147         free(buffer->data);
148         free(buffer);
149 }
150
151 static const char *twitter_url = "https://twitter.com/statuses/update.xml";
152 static const char *identica_url = "http://identi.ca/api/statuses/update.xml";
153
154 static CURL *curl_init(void)
155 {
156         CURL *curl;
157
158         curl = curl_easy_init();
159         if (!curl) {
160                 fprintf(stderr, "Can not init CURL!\n");
161                 return NULL;
162         }
163         /* some ssl sanity checks on the connection we are making */
164         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
165         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
166         return curl;
167 }
168
169 size_t curl_callback(void *buffer, size_t size, size_t nmemb, void *userp)
170 {
171         struct bti_curl_buffer *curl_buf = userp;
172         size_t buffer_size = size * nmemb;
173         char *temp;
174
175         if ((!buffer) || (!buffer_size) || (!curl_buf))
176                 return -EINVAL;
177
178         /* add to the data we already have */
179         temp = zalloc(curl_buf->length + buffer_size + 1);
180         if (!temp)
181                 return -ENOMEM;
182
183         memcpy(temp, curl_buf->data, curl_buf->length);
184         free(curl_buf->data);
185         curl_buf->data = temp;
186         memcpy(&curl_buf->data[curl_buf->length], (char *)buffer, buffer_size);
187         curl_buf->length += buffer_size;
188
189         dbg("%s\n", curl_buf->data);
190
191         return buffer_size;
192 }
193
194 static int send_tweet(struct session *session)
195 {
196         char user_password[500];
197         char data[500];
198         struct bti_curl_buffer *curl_buf;
199         CURL *curl = NULL;
200         CURLcode res;
201         struct curl_httppost *formpost = NULL;
202         struct curl_httppost *lastptr = NULL;
203         struct curl_slist *slist = NULL;
204
205         if (!session)
206                 return -EINVAL;
207
208         curl_buf = bti_curl_buffer_alloc();
209         if (!curl_buf)
210                 return -ENOMEM;
211
212         snprintf(user_password, sizeof(user_password), "%s:%s",
213                  session->account, session->password);
214         snprintf(data, sizeof(data), "status=\"%s\"", session->tweet);
215
216         curl = curl_init();
217         if (!curl)
218                 return -EINVAL;
219
220         curl_formadd(&formpost, &lastptr,
221                      CURLFORM_COPYNAME, "status",
222                      CURLFORM_COPYCONTENTS, session->tweet,
223                      CURLFORM_END);
224
225         curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
226
227         switch (session->host) {
228         case HOST_TWITTER:
229                 curl_easy_setopt(curl, CURLOPT_URL, twitter_url);
230                 /*
231                  * twitter doesn't like the "Expect: 100-continue" header
232                  * anymore, so turn it off.
233                  */
234                 slist = curl_slist_append(slist, "Expect:");
235                 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
236                 break;
237         case HOST_IDENTICA:
238                 curl_easy_setopt(curl, CURLOPT_URL, identica_url);
239                 break;
240         }
241
242         if (session->proxy)
243                 curl_easy_setopt(curl, CURLOPT_PROXY, session->proxy);
244
245         if (debug)
246                 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
247         curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
248
249         dbg("user_password = %s\n", user_password);
250         dbg("data = %s\n", data);
251         dbg("proxy = %s\n", session->proxy);
252
253         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback);
254         curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf);
255         res = curl_easy_perform(curl);
256         if (res && !session->bash) {
257                 fprintf(stderr, "error(%d) trying to send tweet\n", res);
258                 return -EINVAL;
259         }
260
261         curl_easy_cleanup(curl);
262         curl_formfree(formpost);
263         bti_curl_buffer_free(curl_buf);
264         return 0;
265 }
266
267 static void parse_configfile(struct session *session)
268 {
269         FILE *config_file;
270         char *line = NULL;
271         size_t len = 0;
272         char *account = NULL;
273         char *password = NULL;
274         char *host = NULL;
275         char *proxy = NULL;
276         char *file;
277
278         /* config file is ~/.bti  */
279         file = alloca(strlen(session->homedir) + 7);
280
281         sprintf(file, "%s/.bti", session->homedir);
282
283         config_file = fopen(file, "r");
284
285         /* No error if file does not exist or is unreadable.  */
286         if (config_file == NULL)
287                 return;
288
289         do {
290                 ssize_t n = getline(&line, &len, config_file);
291                 if (n < 0)
292                         break;
293                 if (line[n - 1] == '\n')
294                         line[n - 1] = '\0';
295                 /* Parse file.  Format is the usual value pairs:
296                    account=name
297                    passwort=value
298                    # is a comment character
299                 */
300                 *strchrnul(line, '#') = '\0';
301                 char *c = line;
302                 while (isspace(*c))
303                         c++;
304                 /* Ignore blank lines.  */
305                 if (c[0] == '\0')
306                         continue;
307
308                 if (!strncasecmp(c, "account", 7) && (c[7] == '=')) {
309                         c += 8;
310                         if (c[0] != '\0')
311                                 account = strdup(c);
312                 } else if (!strncasecmp(c, "password", 8) &&
313                            (c[8] == '=')) {
314                         c += 9;
315                         if (c[0] != '\0')
316                                 password = strdup(c);
317                 } else if (!strncasecmp(c, "host", 4) &&
318                            (c[4] == '=')) {
319                         c += 5;
320                         if (c[0] != '\0')
321                                 host = strdup(c);
322                 } else if (!strncasecmp(c, "proxy", 5) &&
323                            (c[5] == '=')) {
324                         c += 6;
325                         if (c[0] != '\0')
326                                 proxy = strdup(c);
327                 }
328         } while (!feof(config_file));
329
330         if (password)
331                 session->password = password;
332         if (account)
333                 session->account = account;
334         if (host) {
335                 if (strcasecmp(host, "twitter") == 0)
336                         session->host = HOST_TWITTER;
337                 if (strcasecmp(host, "identica") == 0)
338                         session->host = HOST_IDENTICA;
339                 free(host);
340         }
341         if (proxy) {
342                 if (session->proxy)
343                         free(session->proxy);
344                 session->proxy = proxy;
345         }
346
347         /* Free buffer and close file.  */
348         free(line);
349         fclose(config_file);
350 }
351
352 static void log_session(struct session *session, int retval)
353 {
354         FILE *log_file;
355         char *filename;
356         char *host;
357
358         /* logfile is ~/.bti.log  */
359         filename = alloca(strlen(session->homedir) + 10);
360
361         sprintf(filename, "%s/.bti.log", session->homedir);
362
363         log_file = fopen(filename, "a+");
364         if (log_file == NULL)
365                 return;
366         switch (session->host) {
367         case HOST_TWITTER:
368                 host = "twitter";
369                 break;
370         case HOST_IDENTICA:
371                 host = "identi.ca";
372                 break;
373         default:
374                 host = "unknown";
375                 break;
376         }
377
378         if (retval)
379                 fprintf(log_file, "%s: host=%s tweet failed\n", session->time, host);
380         else
381                 fprintf(log_file, "%s: host=%s tweet=%s\n", session->time, host, session->tweet);
382
383         fclose(log_file);
384 }
385
386 int main(int argc, char *argv[], char *envp[])
387 {
388         static const struct option options[] = {
389                 { "debug", 0, NULL, 'd' },
390                 { "account", 1, NULL, 'a' },
391                 { "password", 1, NULL, 'p' },
392                 { "host", 1, NULL, 'H' },
393                 { "proxy", 1, NULL, 'P' },
394                 { "help", 0, NULL, 'h' },
395                 { "bash", 0, NULL, 'b' },
396                 { "version", 0, NULL, 'v' },
397                 { }
398         };
399         struct session *session;
400         pid_t child;
401         char *tweet;
402         int retval = 0;
403         int option;
404         char *http_proxy;
405         time_t t;
406 #if 0
407         char *pwd = getenv("PWD");
408         char *dir;
409 #endif
410
411         rl_bind_key('\t', rl_insert);
412         session = session_alloc();
413         if (!session) {
414                 fprintf(stderr, "no more memory...\n");
415                 return -1;
416         }
417
418         /* get the current time so that we can log it later */
419         time(&t);
420         session->time = strdup(ctime(&t));
421         session->time[strlen(session->time)-1] = 0x00;
422
423         session->homedir = strdup(getenv("HOME"));
424
425         curl_global_init(CURL_GLOBAL_ALL);
426
427         /* Set environment variables first, before reading command line options
428          * or config file values. */
429         http_proxy = getenv("http_proxy");
430         if (http_proxy) {
431                 if (session->proxy)
432                         free(session->proxy);
433                 session->proxy = strdup(http_proxy);
434                 dbg("http_proxy = %s\n", session->proxy);
435         }
436
437         parse_configfile(session);
438
439         while (1) {
440                 option = getopt_long_only(argc, argv, "dqe:p:P:H:a:h",
441                                           options, NULL);
442                 if (option == -1)
443                         break;
444                 switch (option) {
445                 case 'd':
446                         debug = 1;
447                         break;
448                 case 'a':
449                         if (session->account)
450                                 free(session->account);
451                         session->account = strdup(optarg);
452                         dbg("account = %s\n", session->account);
453                         break;
454                 case 'p':
455                         if (session->password)
456                                 free(session->password);
457                         session->password = strdup(optarg);
458                         dbg("password = %s\n", session->password);
459                         break;
460                 case 'P':
461                         if (session->proxy)
462                                 free(session->proxy);
463                         session->proxy = strdup(optarg);
464                         dbg("proxy = %s\n", session->proxy);
465                         break;
466                 case 'H':
467                         if (strcasecmp(optarg, "twitter") == 0)
468                                 session->host = HOST_TWITTER;
469                         if (strcasecmp(optarg, "identica") == 0)
470                                 session->host = HOST_IDENTICA;
471                         dbg("host = %d\n", session->host);
472                         break;
473                 case 'b':
474                         session->bash= 1;
475                         break;
476                 case 'h':
477                         display_help();
478                         goto exit;
479                 case 'v':
480                         display_version();
481                         goto exit;
482                 default:
483                         display_help();
484                         goto exit;
485                 }
486         }
487
488         if (!session->account) {
489                 fprintf(stdout, "Enter twitter account: ");
490                 session->account = get_string_from_stdin();
491         }
492
493         if (!session->password) {
494                 fprintf(stdout, "Enter twitter password: ");
495                 session->password = get_string_from_stdin();
496         }
497 #if 0
498         /* get the current working directory basename */
499         if (strcmp(pwd, home) == 0)
500                 dir = "~";
501         else {
502                 dir = strrchr(pwd, '/');
503                 if (dir)
504                         dir++;
505                 else
506                         dir = "?";
507         }
508 #endif
509         tweet = get_string_from_stdin();
510         if (!tweet || strlen(tweet) == 0) {
511                 dbg("no tweet?\n");
512                 return -1;
513         }
514
515 //      session->tweet = zalloc(strlen(tweet) + strlen(dir) + 10);
516         session->tweet = zalloc(strlen(tweet) + 10);
517
518         /* if --bash is specified, add the "PWD $ " to
519          * the start of the tweet. */
520         if (session->bash)
521 //              sprintf(session->tweet, "%s $ %s", dir, tweet);
522                 sprintf(session->tweet, "$ %s", tweet);
523         else
524                 sprintf(session->tweet, "%s", tweet);
525         free(tweet);
526
527         dbg("account = %s\n", session->account);
528         dbg("password = %s\n", session->password);
529         dbg("tweet = %s\n", session->tweet);
530         dbg("host = %d\n", session->host);
531
532         /* fork ourself so that the main shell can get on
533          * with it's life as we try to connect and handle everything
534          */
535         if (session->bash) {
536                 child = fork();
537                         if (child) {
538                         dbg("child is %d\n", child);
539                         exit(0);
540                 }
541         }
542
543         retval = send_tweet(session);
544         if (retval && !session->bash)
545                 fprintf(stderr, "tweet failed\n");
546
547 //      log_session(session, retval);
548 exit:
549         session_free(session);
550         return retval;;
551 }