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