/[debian]/bti/trunk/bti.c
ViewVC logotype

Contents of /bti/trunk/bti.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.26