Update OAuth instructions on README.Debian.
[debian/bti.git] / config.c
1 /*
2  * Copyright (C) 2008-2011 Greg Kroah-Hartman <greg@kroah.com>
3  * Copyright (C) 2009 Bart Trojanowski <bart@jukie.net>
4  * Copyright (C) 2009-2010 Amir Mohammad Saied <amirsaied@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  */
15
16 #define _GNU_SOURCE
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 <sys/types.h>
30 #include <sys/wait.h>
31 #include <curl/curl.h>
32 #include <libxml/xmlmemory.h>
33 #include <libxml/parser.h>
34 #include <libxml/tree.h>
35 #include <pcre.h>
36 #include <termios.h>
37 #include <dlfcn.h>
38 #include <oauth.h>
39 #include "bti.h"
40
41 typedef int (*config_function_callback)(struct session *session, char *value);
42
43 struct config_function {
44         const char *key;
45         config_function_callback callback;
46 };
47
48 /*
49  * get_key function
50  *
51  * Read a line from the config file and assign it a key and a value.
52  *
53  * This logic taken almost identically from taken from udev's rule file parsing
54  * logic in the file udev-rules.c, written by Kay Sievers and licensed under
55  * the GPLv2+.  I hate writing parsers, so it makes sense to borrow working
56  * logic from those smarter than I...
57  */
58 static int get_key(struct session *session, char *line, char **key, char **value)
59 {
60         char *linepos;
61         char *temp;
62         char terminator;
63
64         linepos = line;
65         if (linepos == NULL || linepos[0] == '\0')
66                 return -1;
67
68         /* skip whitespace */
69         while (isspace(linepos[0]) || linepos[0] == ',')
70                 linepos++;
71         if (linepos[0] == '\0')
72                 return -1;
73
74         *key = linepos;
75
76         for (;;) {
77                 linepos++;
78                 if (linepos[0] == '\0')
79                         return -1;
80                 if (isspace(linepos[0]))
81                         break;
82                 if (linepos[0] == '=')
83                         break;
84         }
85
86         /* remember the end of the key */
87         temp = linepos;
88
89         /* skip whitespace after key */
90         while (isspace(linepos[0]))
91                 linepos++;
92         if (linepos[0] == '\0')
93                 return -1;
94
95         /* make sure this is a = operation */
96         /*
97          * udev likes to check for += and == and lots of other complex
98          * assignments that we don't care about.
99          */
100         if (linepos[0] == '=')
101                 linepos++;
102         else
103                 return -1;
104
105         /* terminate key */
106         temp[0] = '\0';
107
108         /* skip whitespace after opearator */
109         while (isspace(linepos[0]))
110                 linepos++;
111         if (linepos[0] == '\0')
112                 return -1;
113
114         /*
115          * if the value is quoted, then terminate on a ", otherwise space is
116          * the terminator.
117          * */
118         if (linepos[0] == '"') {
119                 terminator = '"';
120                 linepos++;
121         } else
122                 terminator = ' ';
123
124         /* get the value */
125         *value = linepos;
126
127         /* terminate */
128         temp = strchr(linepos, terminator);
129         if (temp) {
130                 temp[0] = '\0';
131                 temp++;
132         } else {
133                 /*
134                  * perhaps we just hit the end of the line, so there would not
135                  * be a terminator, so just use the whole rest of the string as
136                  * the value.
137                  */
138         }
139         /* printf("%s = %s\n", *key, *value); */
140         return 0;
141 }
142
143 static int session_string(char **field, char *value)
144 {
145         char *string;
146
147         string = strdup(value);
148         if (string) {
149                 if (*field)
150                         free(*field);
151                 *field = string;
152                 return 0;
153         }
154         return -1;
155 }
156
157 static int session_bool(int *field, char *value)
158 {
159         if ((strncasecmp(value, "true", 4) == 0) ||
160             strncasecmp(value, "yes", 3) == 0)
161                 *field = 1;
162         return 0;
163 }
164
165 static int account_callback(struct session *session, char *value)
166 {
167         return session_string(&session->account, value);
168 }
169
170 static int password_callback(struct session *session, char *value)
171 {
172         return session_string(&session->password, value);
173 }
174
175 static int proxy_callback(struct session *session, char *value)
176 {
177         return session_string(&session->proxy, value);
178 }
179
180 static int user_callback(struct session *session, char *value)
181 {
182         return session_string(&session->user, value);
183 }
184
185 static int consumer_key_callback(struct session *session, char *value)
186 {
187         return session_string(&session->consumer_key, value);
188 }
189
190 static int consumer_secret_callback(struct session *session, char *value)
191 {
192         return session_string(&session->consumer_secret, value);
193 }
194
195 static int access_token_key_callback(struct session *session, char *value)
196 {
197         return session_string(&session->access_token_key, value);
198 }
199
200 static int access_token_secret_callback(struct session *session, char *value)
201 {
202         return session_string(&session->access_token_secret, value);
203 }
204
205 static int logfile_callback(struct session *session, char *value)
206 {
207         return session_string(&session->logfile, value);
208 }
209
210 static int replyto_callback(struct session *session, char *value)
211 {
212         return session_string(&session->replyto, value);
213 }
214
215 static int retweet_callback(struct session *session, char *value)
216 {
217         return session_string(&session->retweet, value);
218 }
219
220 static int host_callback(struct session *session, char *value)
221 {
222         if (strcasecmp(value, "twitter") == 0) {
223                 session->host = HOST_TWITTER;
224                 session->hosturl = strdup(twitter_host);
225                 session->hostname = strdup(twitter_name);
226         } else if (strcasecmp(value, "identica") == 0) {
227                 session->host = HOST_IDENTICA;
228                 session->hosturl = strdup(identica_host);
229                 session->hostname = strdup(identica_name);
230         } else {
231                 session->host = HOST_CUSTOM;
232                 session->hosturl = strdup(value);
233                 session->hostname = strdup(value);
234         }
235         return 0;
236 }
237
238 static int action_callback(struct session *session, char *value)
239 {
240         if (strcasecmp(value, "update") == 0)
241                 session->action = ACTION_UPDATE;
242         else if (strcasecmp(value, "friends") == 0)
243                 session->action = ACTION_FRIENDS;
244         else if (strcasecmp(value, "user") == 0)
245                 session->action = ACTION_USER;
246         else if (strcasecmp(value, "replies") == 0)
247                 session->action = ACTION_REPLIES;
248         else if (strcasecmp(value, "public") == 0)
249                 session->action = ACTION_PUBLIC;
250         else if (strcasecmp(value, "group") == 0)
251                 session->action = ACTION_GROUP;
252         else
253                 session->action = ACTION_UNKNOWN;
254         return 0;
255 }
256
257 static int verbose_callback(struct session *session, char *value)
258 {
259         return session_bool(&session->verbose, value);
260 }
261
262 static int shrink_urls_callback(struct session *session, char *value)
263 {
264         return session_bool(&session->shrink_urls, value);
265 }
266
267 /*
268  * List of all of the config file options.
269  *
270  * To add a new option, just add a string for the key name, and the callback
271  * function that will be called with the value read from the config file.
272  *
273  * Make sure the table is NULL terminated, otherwise bad things will happen.
274  */
275 static struct config_function config_table[] = {
276         { "account", account_callback },
277         { "password", password_callback },
278         { "proxy", proxy_callback },
279         { "user", user_callback },
280         { "consumer_key", consumer_key_callback },
281         { "consumer_secret", consumer_secret_callback },
282         { "access_token_key", access_token_key_callback },
283         { "access_token_secret", access_token_secret_callback },
284         { "logfile", logfile_callback },
285         { "replyto", replyto_callback },
286         { "retweet", retweet_callback },
287         { "host", host_callback },
288         { "action", action_callback },
289         { "verbose", verbose_callback },
290         { "shrink-urls", shrink_urls_callback },
291         { NULL, NULL }
292 };
293
294 static void process_line(struct session *session, char *key, char *value)
295 {
296         struct config_function *item;
297         int result;
298
299         if (key == NULL || value == NULL)
300                 return;
301
302         item = &config_table[0];
303         for (;;) {
304                 if (item->key == NULL || item->callback == NULL)
305                         break;
306
307                 if (strncasecmp(item->key, key, strlen(item->key)) == 0) {
308                         /*
309                          * printf("calling %p, for key = '%s' and value = * '%s'\n",
310                          *        item->callback, key, value);
311                          */
312                         result = item->callback(session, value);
313                         if (!result)
314                                 return;
315                 }
316                 item++;
317         }
318 }
319
320 void bti_parse_configfile(struct session *session)
321 {
322         FILE *config_file;
323         char *line = NULL;
324         char *key = NULL;
325         char *value = NULL;
326         char *hashmarker;
327         size_t len = 0;
328         ssize_t n;
329         char *c;
330
331         config_file = fopen(session->configfile, "r");
332
333         /* No error if file does not exist or is unreadable.  */
334         if (config_file == NULL)
335                 return;
336
337         do {
338                 n = getline(&line, &len, config_file);
339                 if (n < 0)
340                         break;
341                 if (line[n - 1] == '\n')
342                         line[n - 1] = '\0';
343
344                 /*
345                  * '#' is comment markers, like bash style but it is a valid
346                  * character in some fields, so only treat it as a comment
347                  * marker if it occurs at the beginning of the line, or after
348                  * whitespace
349                  */
350                 hashmarker = strchr(line, '#');
351                 if (line == hashmarker)
352                         line[0] = '\0';
353                 else {
354                         while (hashmarker != NULL) {
355                                 --hashmarker;
356                                 if (isblank(hashmarker[0])) {
357                                         hashmarker[0] = '\0';
358                                         break;
359                                 } else {
360                                         /*
361                                          * false positive; '#' occured
362                                          * within a string
363                                          */
364                                         hashmarker = strchr(hashmarker+2, '#');
365                                 }
366                         }
367                 }
368                 c = line;
369                 while (isspace(*c))
370                         c++;
371                 /* Ignore blank lines.  */
372                 if (c[0] == '\0')
373                         continue;
374
375                 /* parse the line into a key and value pair */
376                 get_key(session, c, &key, &value);
377
378                 process_line(session, key, value);
379         } while (!feof(config_file));
380
381         /* Free buffer and close file.  */
382         free(line);
383         fclose(config_file);
384 }
385