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