keystore: add plaintext module
[vlc.git] / modules / keystore / plaintext.c
1 /*****************************************************************************
2  * plaintext.c: Insecure keystore
3  *****************************************************************************
4  * Copyright © 2015-2016 VLC authors, VideoLAN and VideoLabs
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <stdarg.h>
26 #include <stdio.h>
27 #ifdef HAVE_FLOCK
28 #include <sys/file.h>
29 #endif
30
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_fs.h>
34 #include <vlc_memory.h>
35 #include <vlc_keystore.h>
36 #include <vlc_strings.h>
37
38 #include <assert.h>
39
40 static int Open(vlc_object_t *);
41 static void Close(vlc_object_t *);
42
43 vlc_module_begin()
44     set_shortname(N_("plaintext keystore (insecure)"))
45     set_description(N_("secrets are stored in plaintext without any encryption"))
46     set_category(CAT_ADVANCED)
47     set_subcategory(SUBCAT_ADVANCED_MISC)
48     add_string("keystore-plaintext-file", NULL, NULL, NULL, false )
49     set_capability("keystore", 0)
50     set_callbacks(Open, Close)
51 vlc_module_end ()
52
53 struct list
54 {
55     vlc_keystore_entry *p_entries;
56     unsigned            i_count;
57     unsigned            i_max;
58 };
59
60 struct vlc_keystore_sys
61 {
62     char *      psz_file;
63     FILE *      p_file;
64     int         i_fd;
65     struct list list;
66     bool        b_error;
67 };
68
69 static struct
70 {
71     vlc_mutex_t         lock;
72     unsigned int        i_ref_count;
73     vlc_keystore_sys *  p_sys;
74 } instance = {
75     .lock = VLC_STATIC_MUTEX,
76     .i_ref_count = 0,
77     .p_sys = NULL
78 };
79
80 static const char *const ppsz_keys[] = {
81     "protocol",
82     "user",
83     "server",
84     "path",
85     "port",
86     "realm",
87     "authtype",
88 };
89 static_assert(sizeof(ppsz_keys)/sizeof(*ppsz_keys) == KEY_MAX, "key mismatch");
90
91 static int
92 str2key(const char *psz_key)
93 {
94     for (unsigned int i = 0; i < KEY_MAX; ++i)
95     {
96         if (strcmp(ppsz_keys[i], psz_key) == 0)
97             return i;
98     }
99     return -1;
100 }
101
102 static void
103 list_free(struct list *p_list)
104 {
105     vlc_keystore_release_entries(p_list->p_entries, p_list->i_count);
106     p_list->p_entries = NULL;
107     p_list->i_count = 0;
108     p_list->i_max = 0;
109 }
110
111 static int
112 values_copy(const char * ppsz_dst[KEY_MAX], const char *const ppsz_src[KEY_MAX])
113 {
114     for (unsigned int i = 0; i < KEY_MAX; ++i)
115     {
116         if (ppsz_src[i])
117         {
118             ppsz_dst[i] = strdup(ppsz_src[i]);
119             if (!ppsz_dst[i])
120                 return VLC_EGENERIC;
121         }
122     }
123     return VLC_SUCCESS;
124 }
125
126 static int
127 str_write(FILE *p_file, const char *psz_str)
128 {
129     size_t i_len = strlen(psz_str);
130     return fwrite(psz_str, sizeof(char), i_len, p_file) == i_len ? VLC_SUCCESS
131                                                                  : VLC_EGENERIC;
132 }
133
134 static int
135 values_write(FILE *p_file, const char *const ppsz_values[KEY_MAX])
136 {
137     for (unsigned int i = 0; i < KEY_MAX; ++i)
138     {
139         if (!ppsz_values[i])
140             continue;
141         if (str_write(p_file, ppsz_keys[i]))
142             return VLC_EGENERIC;
143         if (str_write(p_file, ":"))
144             return VLC_EGENERIC;
145         char *psz_b64 = vlc_b64_encode(ppsz_values[i]);
146         if (!psz_b64 || str_write(p_file, psz_b64))
147         {
148             free(psz_b64);
149             return VLC_EGENERIC;
150         }
151         free(psz_b64);
152         for (unsigned int j = i + 1; j < KEY_MAX; ++j)
153         {
154             if (ppsz_values[j])
155             {
156                 if (str_write(p_file, ","))
157                     return VLC_EGENERIC;
158                 break;
159             }
160         }
161     }
162
163     return VLC_SUCCESS;
164 }
165
166 static vlc_keystore_entry *
167 list_new_entry(struct list *p_list)
168 {
169     if (p_list->i_count + 1 > p_list->i_max)
170     {
171         p_list->i_max += 10;
172         vlc_keystore_entry *p_entries = realloc(p_list->p_entries, p_list->i_max
173                                                 * sizeof(*p_list->p_entries));
174         if (!p_entries)
175         {
176             list_free(p_list);
177             return NULL;
178         }
179         p_list->p_entries = p_entries;
180     }
181     vlc_keystore_entry *p_entry = &p_list->p_entries[p_list->i_count];
182     p_entry->p_secret = NULL;
183     VLC_KEYSTORE_VALUES_INIT(p_entry->ppsz_values);
184     p_list->i_count++;
185
186     return p_entry;
187 }
188
189 static vlc_keystore_entry *
190 list_get_entry(struct list *p_list, const char *const ppsz_values[KEY_MAX],
191               unsigned *p_start_index)
192 {
193     for (unsigned int i = p_start_index ? *p_start_index : 0;
194          i < p_list->i_count; ++i)
195     {
196         vlc_keystore_entry *p_entry = &p_list->p_entries[i];
197         if (!p_entry->p_secret)
198             continue;
199
200         bool b_match = true;
201         for (unsigned int j = 0; j < KEY_MAX; ++j)
202         {
203             const char *psz_value1 = ppsz_values[j];
204             const char *psz_value2 = p_entry->ppsz_values[j];
205
206             if (!psz_value1)
207                 continue;
208             if (!psz_value2 || strcmp(psz_value1, psz_value2))
209                 b_match = false;
210         }
211         if (b_match)
212         {
213             if (p_start_index)
214                 *p_start_index = i + 1;
215             return p_entry;
216         }
217     }
218     return NULL;
219 }
220
221 static int
222 truncate0(int i_fd)
223 {
224 #ifndef _WIN32
225     return ftruncate(i_fd, 0) == 0 ? VLC_SUCCESS : VLC_EGENERIC;
226 #else
227     return _chsize(i_fd, 0) == 0 ? VLC_SUCCESS : VLC_EGENERIC;
228 #endif
229 }
230
231 /* a line is "{key1:VALUE1_B64,key2:VALUE2_B64}:PASSWORD_B64" */
232 static int
233 list_save(vlc_keystore_sys *p_sys, struct list *p_list)
234 {
235     int i_ret = VLC_EGENERIC;
236     rewind(p_sys->p_file);
237
238     if (truncate0(p_sys->i_fd))
239         goto end;
240
241     for (unsigned int i = 0; i < p_list->i_count; ++i)
242     {
243         vlc_keystore_entry *p_entry = &p_list->p_entries[i];
244         if (!p_entry->p_secret)
245             continue;
246
247         if (str_write(p_sys->p_file, "{"))
248             goto end;
249         if (values_write(p_sys->p_file,
250                          (const char *const *) p_entry->ppsz_values))
251             goto end;
252         if (str_write(p_sys->p_file, "}:"))
253             goto end;
254         char *psz_b64 = vlc_b64_encode_binary(p_entry->p_secret,
255                                               p_entry->i_secret_len);
256         if (!psz_b64 || str_write(p_sys->p_file, psz_b64))
257         {
258             free(psz_b64);
259             goto end;
260         }
261         free(psz_b64);
262         if (i < p_list->i_count - 1 && str_write(p_sys->p_file, "\n"))
263             goto end;
264     }
265     i_ret = VLC_SUCCESS;
266 end:
267
268     if (i_ret != VLC_SUCCESS)
269     {
270         p_sys->b_error = true;
271         list_free(p_list);
272     }
273     return i_ret;
274 }
275
276 static int
277 list_read(vlc_keystore_sys *p_sys, struct list *p_list)
278 {
279     char *psz_line = NULL;
280     size_t i_line_len = 0;
281     ssize_t i_read;
282     bool b_valid = false;
283
284     while ((i_read = getline(&psz_line, &i_line_len, p_sys->p_file)) != -1)
285     {
286         char *p = psz_line;
287         if (*(p++) != '{')
288             goto end;
289
290         vlc_keystore_entry *p_entry = list_new_entry(p_list);
291         if (!p_entry)
292             goto end;
293
294         bool b_end = false;
295         while (*p != '\0' && !b_end)
296         {
297             int i_key;
298             char *p_key, *p_value;
299             size_t i_len;
300
301             /* read key */
302             i_len = strcspn(p, ":");
303             if (!i_len || p[i_len] == '\0')
304                 goto end;
305
306             p[i_len] = '\0';
307             p_key = p;
308             i_key = str2key(p_key);
309             if (i_key == -1 || i_key >= KEY_MAX)
310                 goto end;
311             p += i_len + 1;
312
313             /* read value */
314             i_len = strcspn(p, ",}");
315             if (!i_len || p[i_len] == '\0')
316                 goto end;
317
318             if (p[i_len] == '}')
319                 b_end = true;
320
321             p[i_len] = '\0';
322             p_value = vlc_b64_decode(p); /* BASE 64 */
323             if (!p_value)
324                 goto end;
325             p += i_len + 1;
326
327             p_entry->ppsz_values[i_key] = p_value;
328         }
329         /* read passwd */
330         if (*p == '\0' || *p != ':')
331             goto end;
332
333         p_entry->i_secret_len = vlc_b64_decode_binary(&p_entry->p_secret, p + 1);
334         if (!p_entry->p_secret)
335             goto end;
336     }
337
338     b_valid = true;
339
340 end:
341     free(psz_line);
342     if (!b_valid)
343     {
344         p_sys->b_error = true;
345         list_free(p_list);
346     }
347     return VLC_SUCCESS;
348 }
349
350 static int
351 Store(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX],
352       const uint8_t *p_secret, size_t i_secret_len, const char *psz_label)
353 {
354     vlc_mutex_lock(&instance.lock);
355
356     (void) psz_label;
357     vlc_keystore_sys *p_sys = p_keystore->p_sys;
358     assert(p_sys == instance.p_sys);
359     struct list *p_list = &p_sys->list;
360     vlc_keystore_entry *p_entry = list_get_entry(p_list, ppsz_values, NULL);
361
362     if (p_entry)
363     {
364         free(p_entry->p_secret);
365         p_entry->p_secret = NULL;
366         for (unsigned int i = 0; i < KEY_MAX; ++i)
367         {
368             free(p_entry->ppsz_values[i]);
369             p_entry->ppsz_values[i] = NULL;
370         }
371     }
372     else
373     {
374         p_entry = list_new_entry(p_list);
375         if (!p_entry)
376             goto error;
377     }
378     if (values_copy((const char **)p_entry->ppsz_values, ppsz_values))
379         goto error;
380
381     if (vlc_keystore_entry_set_secret(p_entry, p_secret, i_secret_len))
382         goto error;
383
384     int i_ret = list_save(p_sys, &p_sys->list);
385     vlc_mutex_unlock(&instance.lock);
386     return i_ret;
387
388 error:
389     vlc_mutex_unlock(&instance.lock);
390     return VLC_EGENERIC;
391 }
392
393 static unsigned int
394 Find(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX],
395      vlc_keystore_entry **pp_entries)
396 {
397     vlc_mutex_lock(&instance.lock);
398
399     vlc_keystore_sys *p_sys = p_keystore->p_sys;
400     assert(p_sys == instance.p_sys);
401     struct list *p_list = &p_sys->list;
402     struct list out_list = { 0 };
403     vlc_keystore_entry *p_entry;
404     unsigned i_index = 0;
405
406     while ((p_entry = list_get_entry(p_list, ppsz_values, &i_index)))
407     {
408         vlc_keystore_entry *p_out_entry = list_new_entry(&out_list);
409
410         if (!p_out_entry || values_copy((const char **)p_out_entry->ppsz_values,
411                                         (const char *const*)p_entry->ppsz_values))
412         {
413             list_free(&out_list);
414             break;
415         }
416
417         if (vlc_keystore_entry_set_secret(p_out_entry, p_entry->p_secret,
418                                           p_entry->i_secret_len))
419         {
420             list_free(&out_list);
421             break;
422         }
423     }
424
425     *pp_entries = out_list.p_entries;
426
427     vlc_mutex_unlock(&instance.lock);
428     return out_list.i_count;
429 }
430
431 static unsigned int
432 Remove(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX])
433 {
434     vlc_mutex_lock(&instance.lock);
435
436     vlc_keystore_sys *p_sys = p_keystore->p_sys;
437     assert(p_sys == instance.p_sys);
438     struct list *p_list = &p_sys->list;
439     vlc_keystore_entry *p_entry;
440     unsigned i_index = 0, i_count = 0;
441
442     while ((p_entry = list_get_entry(p_list, ppsz_values, &i_index)))
443     {
444         free(p_entry->p_secret);
445         p_entry->p_secret = NULL;
446         i_count++;
447     }
448
449     if (list_save(p_sys, &p_sys->list) != VLC_SUCCESS)
450     {
451         vlc_mutex_unlock(&instance.lock);
452         return 0;
453     }
454
455     vlc_mutex_unlock(&instance.lock);
456     return i_count;
457 }
458
459 static void
460 CleanUp(vlc_keystore_sys *p_sys)
461 {
462     if (p_sys->p_file)
463     {
464         if (p_sys->b_error)
465         {
466             if (truncate0(p_sys->i_fd))
467                 vlc_unlink(p_sys->psz_file);
468         }
469
470 #ifdef HAVE_FLOCK
471         flock(p_sys->i_fd, LOCK_UN);
472 #endif
473         fclose(p_sys->p_file);
474     }
475
476     list_free(&p_sys->list);
477     free(p_sys->psz_file);
478     free(p_sys);
479 }
480
481 static int
482 Open(vlc_object_t *p_this)
483 {
484     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
485     vlc_keystore_sys *p_sys;
486
487     vlc_mutex_lock(&instance.lock);
488
489     /* The p_sys context is shared and protected between all threads */
490     if (instance.i_ref_count == 0)
491     {
492         char *psz_file = var_InheritString(p_this, "keystore-plaintext-file");
493         if (!psz_file)
494             return VLC_EGENERIC;
495
496         p_sys = calloc(1, sizeof(vlc_keystore_sys));
497         if (!p_sys)
498         {
499             free(psz_file);
500             return VLC_EGENERIC;
501         }
502
503         p_sys->psz_file = psz_file;
504         p_sys->p_file = vlc_fopen(p_sys->psz_file, "a+");
505
506         if (!p_sys->p_file)
507         {
508             CleanUp(p_sys);
509             return VLC_EGENERIC;
510         }
511         p_sys->i_fd = fileno(p_sys->p_file);
512         if (p_sys->i_fd == -1)
513         {
514             CleanUp(p_sys);
515             return VLC_EGENERIC;
516         }
517
518         /* Fail if an other LibVLC process acquired the file lock.
519          * If HAVE_FLOCK is not defined, the running OS is most likely Windows
520          * and a lock was already acquired when the file was opened. */
521 #ifdef HAVE_FLOCK
522         if (flock(p_sys->i_fd, LOCK_EX|LOCK_NB) != 0)
523         {
524             CleanUp(p_sys);
525             return VLC_EGENERIC;
526         }
527 #endif
528
529         if (list_read(p_sys, &p_sys->list) != VLC_SUCCESS)
530         {
531             CleanUp(p_sys);
532             return VLC_EGENERIC;
533         }
534         instance.p_sys = p_sys;
535     }
536     else
537         p_sys = instance.p_sys;
538
539     instance.i_ref_count++;
540     p_keystore->p_sys = p_sys;
541
542     p_keystore->pf_store = Store;
543     p_keystore->pf_find = Find;
544     p_keystore->pf_remove = Remove;
545
546     vlc_mutex_unlock(&instance.lock);
547
548     return VLC_SUCCESS;
549 }
550
551 static void
552 Close(vlc_object_t *p_this)
553 {
554     (void) p_this;
555
556     vlc_mutex_lock(&instance.lock);
557     assert(((vlc_keystore *)p_this)->p_sys == instance.p_sys);
558     if (--instance.i_ref_count == 0)
559     {
560         CleanUp(instance.p_sys);
561         instance.p_sys = NULL;
562     }
563     vlc_mutex_unlock(&instance.lock);
564 }