1 /*****************************************************************************
2  * file.c: File and crypt 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 <stdio.h>
26 #include <sys/stat.h>
27 #ifdef HAVE_FLOCK
28 #include <sys/file.h>
29 #endif
30 #ifdef HAVE_FCNTL
31 #include <fcntl.h>
32 #endif
33 
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_fs.h>
37 #include <vlc_memory.h>
38 #include <vlc_keystore.h>
39 #include <vlc_strings.h>
40 
41 #include <assert.h>
42 
43 #include "file_crypt.h"
44 #include "list_util.h"
45 
46 static int Open(vlc_object_t *);
47 static void Close(vlc_object_t *);
48 #ifdef CRYPTFILE
49 static int OpenCrypt(vlc_object_t *);
50 static void CloseCrypt(vlc_object_t *);
51 #endif
52 
53 vlc_module_begin()
54     set_shortname(N_("File keystore (plaintext)"))
55     set_description(N_("Secrets are stored on a file without any encryption"))
56     set_category(CAT_ADVANCED)
57     set_subcategory(SUBCAT_ADVANCED_MISC)
58     set_callbacks(Open, Close)
59     add_savefile("keystore-file", NULL, NULL, NULL, true)
60         change_private()
61     set_capability("keystore", 0)
62     add_shortcut("file_plaintext")
63 #ifdef CRYPTFILE
64     add_submodule()
65         set_shortname(N_("Crypt keystore"))
66         set_description(N_("Secrets are stored encrypted on a file"))
67         set_category(CAT_ADVANCED)
68         set_subcategory(SUBCAT_ADVANCED_MISC)
69         set_callbacks(OpenCrypt, CloseCrypt)
70         set_capability("keystore", 1)
71         add_shortcut("file_crypt")
72 #endif
73 vlc_module_end ()
74 
75 struct vlc_keystore_sys
76 {
77     char *          psz_file;
78 #ifdef CRYPTFILE
79     bool            b_crypted;
80     struct crypt    crypt;
81 #endif
82 };
83 
84 static const char *const ppsz_keys[] = {
85     "protocol",
86     "user",
87     "server",
88     "path",
89     "port",
90     "realm",
91     "authtype",
92 };
93 static_assert(sizeof(ppsz_keys)/sizeof(*ppsz_keys) == KEY_MAX, "key mismatch");
94 
95 static int
str2key(const char * psz_key)96 str2key(const char *psz_key)
97 {
98     for (unsigned int i = 0; i < KEY_MAX; ++i)
99     {
100         if (strcmp(ppsz_keys[i], psz_key) == 0)
101             return i;
102     }
103     return -1;
104 }
105 
106 static int
values_write(FILE * p_file,const char * const ppsz_values[KEY_MAX])107 values_write(FILE *p_file, const char *const ppsz_values[KEY_MAX])
108 {
109     for (unsigned int i = 0; i < KEY_MAX; ++i)
110     {
111         if (!ppsz_values[i])
112             continue;
113         char *psz_b64 = vlc_b64_encode(ppsz_values[i]);
114         if (!psz_b64)
115             return VLC_EGENERIC;
116         const char *psz_end_sep = "";
117         for (unsigned int j = i + 1; j < KEY_MAX; ++j)
118         {
119             if (ppsz_values[j])
120             {
121                 psz_end_sep = ",";
122                 break;
123             }
124         }
125         if (fprintf(p_file, "%s:%s%s", ppsz_keys[i], psz_b64, psz_end_sep) < 0)
126         {
127             free(psz_b64);
128             return VLC_EGENERIC;
129         }
130         free(psz_b64);
131     }
132 
133     return VLC_SUCCESS;
134 }
135 
136 static int
truncate0(int i_fd)137 truncate0(int i_fd)
138 {
139 #ifndef _WIN32
140     return ftruncate(i_fd, 0) == 0 ? VLC_SUCCESS : VLC_EGENERIC;
141 #else
142     return _chsize(i_fd, 0) == 0 ? VLC_SUCCESS : VLC_EGENERIC;
143 #endif
144 }
145 
146 /* a line is "{key1:VALUE1_B64,key2:VALUE2_B64}:PASSWORD_B64" */
147 static int
file_save(vlc_keystore * p_keystore,FILE * p_file,int i_fd,struct ks_list * p_list)148 file_save(vlc_keystore *p_keystore, FILE *p_file, int i_fd, struct ks_list *p_list)
149 {
150     vlc_keystore_sys *p_sys = p_keystore->p_sys;
151     int i_ret = VLC_EGENERIC;
152 
153     rewind(p_file);
154     if (truncate0(i_fd))
155     {
156         vlc_unlink(p_sys->psz_file);
157         return i_ret;
158     }
159 
160     for (unsigned int i = 0; i < p_list->i_count; ++i)
161     {
162         vlc_keystore_entry *p_entry = &p_list->p_entries[i];
163         if (!p_entry->p_secret)
164             continue;
165 
166         if (fprintf(p_file, "{") < 0)
167             goto end;
168         if (values_write(p_file, (const char *const *) p_entry->ppsz_values))
169             goto end;
170         char *psz_b64 = vlc_b64_encode_binary(p_entry->p_secret,
171                                               p_entry->i_secret_len);
172         if (!psz_b64)
173             goto end;
174         if (fprintf(p_file, "}:%s\n", psz_b64) < 0)
175         {
176             free(psz_b64);
177             goto end;
178         }
179         free(psz_b64);
180     }
181     i_ret = VLC_SUCCESS;
182 end:
183 
184     if (i_ret != VLC_SUCCESS)
185     {
186         if (truncate0(i_fd))
187             vlc_unlink(p_sys->psz_file);
188     }
189     return i_ret;
190 }
191 
192 static int
file_read(vlc_keystore * p_keystore,FILE * p_file,int i_fd,struct ks_list * p_list)193 file_read(vlc_keystore *p_keystore, FILE *p_file, int i_fd, struct ks_list *p_list)
194 {
195     vlc_keystore_sys *p_sys = p_keystore->p_sys;
196     char *psz_line = NULL;
197     size_t i_line_len = 0;
198     ssize_t i_read;
199     bool b_valid = false;
200 
201     while ((i_read = getline(&psz_line, &i_line_len, p_file)) != -1)
202     {
203         char *p = psz_line;
204         if (*(p++) != '{')
205         {
206             getchar();
207             goto end;
208         }
209 
210         vlc_keystore_entry *p_entry = ks_list_new_entry(p_list);
211         if (!p_entry)
212             goto end;
213 
214         bool b_end = false;
215         while (*p != '\0' && !b_end)
216         {
217             int i_key;
218             char *p_key, *p_value;
219             size_t i_len;
220 
221             /* read key */
222             i_len = strcspn(p, ":");
223             if (!i_len || p[i_len] == '\0')
224                 goto end;
225 
226             p[i_len] = '\0';
227             p_key = p;
228             i_key = str2key(p_key);
229             if (i_key == -1 || i_key >= KEY_MAX)
230                 goto end;
231             p += i_len + 1;
232 
233             /* read value */
234             i_len = strcspn(p, ",}");
235             if (!i_len || p[i_len] == '\0')
236                 goto end;
237 
238             if (p[i_len] == '}')
239                 b_end = true;
240 
241             p[i_len] = '\0';
242             p_value = vlc_b64_decode(p); /* BASE 64 */
243             if (!p_value)
244                 goto end;
245             p += i_len + 1;
246 
247             p_entry->ppsz_values[i_key] = p_value;
248         }
249         /* read passwd */
250         if (*p == '\0' || *p != ':')
251             goto end;
252 
253         p_entry->i_secret_len = vlc_b64_decode_binary(&p_entry->p_secret, p + 1);
254         if (!p_entry->p_secret)
255             goto end;
256     }
257 
258     b_valid = true;
259 
260 end:
261     free(psz_line);
262     if (!b_valid)
263     {
264         if (truncate0(i_fd))
265             vlc_unlink(p_sys->psz_file);
266     }
267     return VLC_SUCCESS;
268 }
269 
270 #if (!defined(HAVE_FLOCK) && defined (HAVE_FCNTL) && defined (F_SETLKW))
271 static int
posix_lock_fd(int fd)272 posix_lock_fd(int fd)
273 {
274     struct flock lock;
275     int flags;
276 
277     if (fd == -1)
278         return -1;
279 
280     flags = fcntl(fd, F_GETFL);
281 
282     lock.l_start = 0;
283     lock.l_len = 0;
284     lock.l_whence = SEEK_SET;
285     lock.l_type = (flags & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK;
286 
287     return fcntl(fd, F_SETLKW, &lock);
288 }
289 #endif
290 
291 static int
file_open(const char * psz_file,const char * psz_mode,FILE ** pp_file)292 file_open(const char *psz_file, const char *psz_mode, FILE **pp_file)
293 {
294     FILE *p_file = vlc_fopen(psz_file, psz_mode);
295     if (p_file == NULL)
296         return -1;
297 
298     int i_fd = fileno(p_file);
299     if (i_fd == -1)
300     {
301         fclose(p_file);
302         return -1;
303     }
304 
305 #ifdef HAVE_FLOCK
306     if (flock(i_fd, LOCK_EX) != 0)
307     {
308         fclose(p_file);
309         return -1;
310     }
311 #elif defined (HAVE_FCNTL) && defined (F_SETLKW)
312     if (posix_lock_fd(i_fd) != 0)
313     {
314         fclose(p_file);
315         return -1;
316     }
317 #endif
318     *pp_file = p_file;
319     return i_fd;
320 }
321 
322 static void
file_close(FILE * p_file)323 file_close(FILE *p_file)
324 {
325     fclose(p_file);
326 }
327 
328 static int
Store(vlc_keystore * p_keystore,const char * const ppsz_values[KEY_MAX],const uint8_t * p_secret,size_t i_secret_len,const char * psz_label)329 Store(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX],
330       const uint8_t *p_secret, size_t i_secret_len, const char *psz_label)
331 {
332     (void) psz_label;
333     vlc_keystore_sys *p_sys = p_keystore->p_sys;
334     int i_ret = VLC_EGENERIC;
335     struct ks_list list = { 0 };
336     FILE *p_file;
337     int i_fd = file_open(p_sys->psz_file, "r+", &p_file);
338     if (i_fd == -1)
339         return i_ret;
340 
341     if (file_read(p_keystore, p_file, i_fd, &list) != VLC_SUCCESS)
342         goto end;
343 
344     vlc_keystore_entry *p_entry = ks_list_find_entry(&list, ppsz_values, NULL);
345 
346     if (p_entry)
347         vlc_keystore_release_entry(p_entry);
348     else
349     {
350         p_entry = ks_list_new_entry(&list);
351         if (!p_entry)
352             goto end;
353     }
354     if (ks_values_copy((const char **)p_entry->ppsz_values, ppsz_values))
355         goto end;
356 
357 #ifdef CRYPTFILE
358     if (p_sys->b_crypted)
359     {
360         struct crypt *p_crypt = &p_sys->crypt;
361         uint8_t *p_enc_secret;
362         size_t i_enc_secret_len =
363             p_crypt->pf_encrypt(p_keystore, p_crypt->p_ctx, p_secret,
364                                 i_secret_len, &p_enc_secret);
365         if (i_enc_secret_len == 0)
366             goto end;
367 
368         if (vlc_keystore_entry_set_secret(p_entry, p_enc_secret,
369                                           i_enc_secret_len))
370             goto end;
371         free(p_enc_secret);
372     }
373     else
374 #endif
375     {
376         if (vlc_keystore_entry_set_secret(p_entry, p_secret, i_secret_len))
377             goto end;
378     }
379 
380 
381     i_ret = file_save(p_keystore, p_file, i_fd, &list);
382 
383 end:
384     file_close(p_file);
385     ks_list_free(&list);
386     return i_ret;
387 }
388 
389 static unsigned int
Find(vlc_keystore * p_keystore,const char * const ppsz_values[KEY_MAX],vlc_keystore_entry ** pp_entries)390 Find(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX],
391      vlc_keystore_entry **pp_entries)
392 {
393     vlc_keystore_sys *p_sys = p_keystore->p_sys;
394     struct ks_list list = { 0 };
395     struct ks_list out_list = { 0 };
396     FILE *p_file;
397     int i_fd = file_open(p_sys->psz_file, "r", &p_file);
398     if (i_fd == -1)
399         return 0;
400 
401     if (file_read(p_keystore, p_file, i_fd, &list) != VLC_SUCCESS)
402         goto end;
403 
404     vlc_keystore_entry *p_entry;
405     unsigned i_index = 0;
406     while ((p_entry = ks_list_find_entry(&list, ppsz_values, &i_index)))
407     {
408         vlc_keystore_entry *p_out_entry = ks_list_new_entry(&out_list);
409 
410         if (!p_out_entry
411          || ks_values_copy((const char **)p_out_entry->ppsz_values,
412                            (const char *const*)p_entry->ppsz_values))
413         {
414             ks_list_free(&out_list);
415             goto end;
416         }
417 
418 #ifdef CRYPTFILE
419         if (p_sys->b_crypted)
420         {
421             struct crypt *p_crypt = &p_sys->crypt;
422             uint8_t *p_dec_secret;
423             size_t i_dec_secret_len =
424                 p_crypt->pf_decrypt(p_keystore, p_crypt->p_ctx, p_entry->p_secret,
425                                     p_entry->i_secret_len, &p_dec_secret);
426             if (i_dec_secret_len == 0)
427             {
428                 ks_list_free(&out_list);
429                 goto end;
430             }
431 
432             free(p_entry->p_secret);
433             p_entry->p_secret = p_dec_secret;
434             p_entry->i_secret_len = i_dec_secret_len;
435         }
436 #endif
437 
438         if (vlc_keystore_entry_set_secret(p_out_entry, p_entry->p_secret,
439                                           p_entry->i_secret_len))
440         {
441             ks_list_free(&out_list);
442             goto end;
443         }
444     }
445 
446     *pp_entries = out_list.p_entries;
447 end:
448     file_close(p_file);
449     ks_list_free(&list);
450     return out_list.i_count;
451 }
452 
453 static unsigned int
Remove(vlc_keystore * p_keystore,const char * const ppsz_values[KEY_MAX])454 Remove(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX])
455 {
456     vlc_keystore_sys *p_sys = p_keystore->p_sys;
457     unsigned int i_count = 0;
458     struct ks_list list = { 0 };
459     FILE *p_file;
460     int i_fd = file_open(p_sys->psz_file, "r+", &p_file);
461     if (i_fd == -1)
462         return 0;
463 
464     if (file_read(p_keystore, p_file, i_fd, &list) != VLC_SUCCESS)
465         goto end;
466 
467     vlc_keystore_entry *p_entry;
468     unsigned i_index = 0;
469     while ((p_entry = ks_list_find_entry(&list, ppsz_values, &i_index)))
470     {
471         vlc_keystore_release_entry(p_entry);
472         i_count++;
473     }
474 
475     if (i_count > 0
476      && file_save(p_keystore, p_file, i_fd, &list) != VLC_SUCCESS)
477         i_count = 0;
478 
479 end:
480     file_close(p_file);
481     ks_list_free(&list);
482     return i_count;
483 }
484 
485 static void
Close(vlc_object_t * p_this)486 Close(vlc_object_t *p_this)
487 {
488     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
489     vlc_keystore_sys *p_sys = p_keystore->p_sys;
490 
491     free(p_sys->psz_file);
492     free(p_sys);
493 }
494 
495 static int
Open(vlc_object_t * p_this)496 Open(vlc_object_t *p_this)
497 {
498     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
499 
500     vlc_keystore_sys *p_sys = calloc(1, sizeof(vlc_keystore_sys));
501     if (!p_sys)
502         return VLC_EGENERIC;
503 
504     char *psz_file = var_InheritString(p_this, "keystore-file");
505     if (!psz_file)
506     {
507         free(p_sys);
508         return VLC_EGENERIC;
509     }
510 
511     struct stat stat;
512     bool b_file_exists = false;
513     if (vlc_stat(psz_file, &stat) != 0)
514     {
515         FILE *p_file = vlc_fopen(psz_file, "a+");
516         if (p_file != NULL)
517         {
518             b_file_exists= true;
519             fclose(p_file);
520         }
521     }
522     else
523         b_file_exists = true;
524 
525     if (!b_file_exists)
526     {
527         free(p_sys);
528         free(psz_file);
529         return VLC_EGENERIC;
530     }
531 
532     p_sys->psz_file = psz_file;
533     p_keystore->p_sys = p_sys;
534     p_keystore->pf_store = Store;
535     p_keystore->pf_find = Find;
536     p_keystore->pf_remove = Remove;
537 
538     return VLC_SUCCESS;
539 }
540 
541 #ifdef CRYPTFILE
542 static void
CloseCrypt(vlc_object_t * p_this)543 CloseCrypt(vlc_object_t *p_this)
544 {
545     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
546     struct crypt *p_crypt = &p_keystore->p_sys->crypt;
547 
548     if (p_crypt->pf_clean != NULL)
549         p_crypt->pf_clean(p_keystore, p_crypt->p_ctx);
550 
551     Close(p_this);
552 }
553 
554 static int
OpenCrypt(vlc_object_t * p_this)555 OpenCrypt(vlc_object_t *p_this)
556 {
557     int i_ret = Open(p_this);
558 
559     if (i_ret != VLC_SUCCESS)
560         return i_ret;
561 
562     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
563     vlc_keystore_sys *p_sys = p_keystore->p_sys;
564 
565     if (CryptInit(p_keystore, &p_sys->crypt) != VLC_SUCCESS)
566     {
567         Close(p_this);
568         return VLC_EGENERIC;
569     }
570     assert(p_sys->crypt.pf_encrypt != NULL && p_sys->crypt.pf_decrypt != NULL);
571     p_sys->b_crypted = true;
572 
573     return VLC_SUCCESS;
574 }
575 #endif /* CRYPTFILE */
576