/[debian]/bti/tags/014-1/bti.c
ViewVC logotype

Contents of /bti/tags/014-1/bti.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.26