1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "alloc-util.h"
4 #include "env-file.h"
5 #include "env-util.h"
6 #include "escape.h"
7 #include "fd-util.h"
8 #include "fileio.h"
9 #include "fs-util.h"
10 #include "string-util.h"
11 #include "strv.h"
12 #include "tmpfile-util.h"
13 #include "utf8.h"
14 
parse_env_file_internal(FILE * f,const char * fname,int (* push)(const char * filename,unsigned line,const char * key,char * value,void * userdata),void * userdata)15 static int parse_env_file_internal(
16                 FILE *f,
17                 const char *fname,
18                 int (*push) (const char *filename, unsigned line,
19                              const char *key, char *value, void *userdata),
20                 void *userdata) {
21 
22         size_t n_key = 0, n_value = 0, last_value_whitespace = SIZE_MAX, last_key_whitespace = SIZE_MAX;
23         _cleanup_free_ char *contents = NULL, *key = NULL, *value = NULL;
24         unsigned line = 1;
25         int r;
26 
27         enum {
28                 PRE_KEY,
29                 KEY,
30                 PRE_VALUE,
31                 VALUE,
32                 VALUE_ESCAPE,
33                 SINGLE_QUOTE_VALUE,
34                 DOUBLE_QUOTE_VALUE,
35                 DOUBLE_QUOTE_VALUE_ESCAPE,
36                 COMMENT,
37                 COMMENT_ESCAPE
38         } state = PRE_KEY;
39 
40         if (f)
41                 r = read_full_stream(f, &contents, NULL);
42         else
43                 r = read_full_file(fname, &contents, NULL);
44         if (r < 0)
45                 return r;
46 
47         for (char *p = contents; *p; p++) {
48                 char c = *p;
49 
50                 switch (state) {
51 
52                 case PRE_KEY:
53                         if (strchr(COMMENTS, c))
54                                 state = COMMENT;
55                         else if (!strchr(WHITESPACE, c)) {
56                                 state = KEY;
57                                 last_key_whitespace = SIZE_MAX;
58 
59                                 if (!GREEDY_REALLOC(key, n_key+2))
60                                         return -ENOMEM;
61 
62                                 key[n_key++] = c;
63                         }
64                         break;
65 
66                 case KEY:
67                         if (strchr(NEWLINE, c)) {
68                                 state = PRE_KEY;
69                                 line++;
70                                 n_key = 0;
71                         } else if (c == '=') {
72                                 state = PRE_VALUE;
73                                 last_value_whitespace = SIZE_MAX;
74                         } else {
75                                 if (!strchr(WHITESPACE, c))
76                                         last_key_whitespace = SIZE_MAX;
77                                 else if (last_key_whitespace == SIZE_MAX)
78                                          last_key_whitespace = n_key;
79 
80                                 if (!GREEDY_REALLOC(key, n_key+2))
81                                         return -ENOMEM;
82 
83                                 key[n_key++] = c;
84                         }
85 
86                         break;
87 
88                 case PRE_VALUE:
89                         if (strchr(NEWLINE, c)) {
90                                 state = PRE_KEY;
91                                 line++;
92                                 key[n_key] = 0;
93 
94                                 if (value)
95                                         value[n_value] = 0;
96 
97                                 /* strip trailing whitespace from key */
98                                 if (last_key_whitespace != SIZE_MAX)
99                                         key[last_key_whitespace] = 0;
100 
101                                 r = push(fname, line, key, value, userdata);
102                                 if (r < 0)
103                                         return r;
104 
105                                 n_key = 0;
106                                 value = NULL;
107                                 n_value = 0;
108 
109                         } else if (c == '\'')
110                                 state = SINGLE_QUOTE_VALUE;
111                         else if (c == '"')
112                                 state = DOUBLE_QUOTE_VALUE;
113                         else if (c == '\\')
114                                 state = VALUE_ESCAPE;
115                         else if (!strchr(WHITESPACE, c)) {
116                                 state = VALUE;
117 
118                                 if (!GREEDY_REALLOC(value, n_value+2))
119                                         return  -ENOMEM;
120 
121                                 value[n_value++] = c;
122                         }
123 
124                         break;
125 
126                 case VALUE:
127                         if (strchr(NEWLINE, c)) {
128                                 state = PRE_KEY;
129                                 line++;
130 
131                                 key[n_key] = 0;
132 
133                                 if (value)
134                                         value[n_value] = 0;
135 
136                                 /* Chomp off trailing whitespace from value */
137                                 if (last_value_whitespace != SIZE_MAX)
138                                         value[last_value_whitespace] = 0;
139 
140                                 /* strip trailing whitespace from key */
141                                 if (last_key_whitespace != SIZE_MAX)
142                                         key[last_key_whitespace] = 0;
143 
144                                 r = push(fname, line, key, value, userdata);
145                                 if (r < 0)
146                                         return r;
147 
148                                 n_key = 0;
149                                 value = NULL;
150                                 n_value = 0;
151 
152                         } else if (c == '\\') {
153                                 state = VALUE_ESCAPE;
154                                 last_value_whitespace = SIZE_MAX;
155                         } else {
156                                 if (!strchr(WHITESPACE, c))
157                                         last_value_whitespace = SIZE_MAX;
158                                 else if (last_value_whitespace == SIZE_MAX)
159                                         last_value_whitespace = n_value;
160 
161                                 if (!GREEDY_REALLOC(value, n_value+2))
162                                         return -ENOMEM;
163 
164                                 value[n_value++] = c;
165                         }
166 
167                         break;
168 
169                 case VALUE_ESCAPE:
170                         state = VALUE;
171 
172                         if (!strchr(NEWLINE, c)) {
173                                 /* Escaped newlines we eat up entirely */
174                                 if (!GREEDY_REALLOC(value, n_value+2))
175                                         return -ENOMEM;
176 
177                                 value[n_value++] = c;
178                         }
179                         break;
180 
181                 case SINGLE_QUOTE_VALUE:
182                         if (c == '\'')
183                                 state = PRE_VALUE;
184                         else {
185                                 if (!GREEDY_REALLOC(value, n_value+2))
186                                         return -ENOMEM;
187 
188                                 value[n_value++] = c;
189                         }
190 
191                         break;
192 
193                 case DOUBLE_QUOTE_VALUE:
194                         if (c == '"')
195                                 state = PRE_VALUE;
196                         else if (c == '\\')
197                                 state = DOUBLE_QUOTE_VALUE_ESCAPE;
198                         else {
199                                 if (!GREEDY_REALLOC(value, n_value+2))
200                                         return -ENOMEM;
201 
202                                 value[n_value++] = c;
203                         }
204 
205                         break;
206 
207                 case DOUBLE_QUOTE_VALUE_ESCAPE:
208                         state = DOUBLE_QUOTE_VALUE;
209 
210                         if (strchr(SHELL_NEED_ESCAPE, c)) {
211                                 /* If this is a char that needs escaping, just unescape it. */
212                                 if (!GREEDY_REALLOC(value, n_value+2))
213                                         return -ENOMEM;
214                                 value[n_value++] = c;
215                         } else if (c != '\n') {
216                                 /* If other char than what needs escaping, keep the "\" in place, like the
217                                  * real shell does. */
218                                 if (!GREEDY_REALLOC(value, n_value+3))
219                                         return -ENOMEM;
220                                 value[n_value++] = '\\';
221                                 value[n_value++] = c;
222                         }
223 
224                         /* Escaped newlines (aka "continuation lines") are eaten up entirely */
225                         break;
226 
227                 case COMMENT:
228                         if (c == '\\')
229                                 state = COMMENT_ESCAPE;
230                         else if (strchr(NEWLINE, c)) {
231                                 state = PRE_KEY;
232                                 line++;
233                         }
234                         break;
235 
236                 case COMMENT_ESCAPE:
237                         state = COMMENT;
238                         break;
239                 }
240         }
241 
242         if (IN_SET(state,
243                    PRE_VALUE,
244                    VALUE,
245                    VALUE_ESCAPE,
246                    SINGLE_QUOTE_VALUE,
247                    DOUBLE_QUOTE_VALUE,
248                    DOUBLE_QUOTE_VALUE_ESCAPE)) {
249 
250                 key[n_key] = 0;
251 
252                 if (value)
253                         value[n_value] = 0;
254 
255                 if (state == VALUE)
256                         if (last_value_whitespace != SIZE_MAX)
257                                 value[last_value_whitespace] = 0;
258 
259                 /* strip trailing whitespace from key */
260                 if (last_key_whitespace != SIZE_MAX)
261                         key[last_key_whitespace] = 0;
262 
263                 r = push(fname, line, key, value, userdata);
264                 if (r < 0)
265                         return r;
266 
267                 value = NULL;
268         }
269 
270         return 0;
271 }
272 
check_utf8ness_and_warn(const char * filename,unsigned line,const char * key,char * value)273 static int check_utf8ness_and_warn(
274                 const char *filename, unsigned line,
275                 const char *key, char *value) {
276 
277         if (!utf8_is_valid(key)) {
278                 _cleanup_free_ char *p = NULL;
279 
280                 p = utf8_escape_invalid(key);
281                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
282                                        "%s:%u: invalid UTF-8 in key '%s', ignoring.",
283                                        strna(filename), line, p);
284         }
285 
286         if (value && !utf8_is_valid(value)) {
287                 _cleanup_free_ char *p = NULL;
288 
289                 p = utf8_escape_invalid(value);
290                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
291                                        "%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
292                                        strna(filename), line, key, p);
293         }
294 
295         return 0;
296 }
297 
parse_env_file_push(const char * filename,unsigned line,const char * key,char * value,void * userdata)298 static int parse_env_file_push(
299                 const char *filename, unsigned line,
300                 const char *key, char *value,
301                 void *userdata) {
302 
303         const char *k;
304         va_list aq, *ap = userdata;
305         int r;
306 
307         r = check_utf8ness_and_warn(filename, line, key, value);
308         if (r < 0)
309                 return r;
310 
311         va_copy(aq, *ap);
312 
313         while ((k = va_arg(aq, const char *))) {
314                 char **v;
315 
316                 v = va_arg(aq, char **);
317 
318                 if (streq(key, k)) {
319                         va_end(aq);
320                         free(*v);
321                         *v = value;
322 
323                         return 1;
324                 }
325         }
326 
327         va_end(aq);
328         free(value);
329 
330         return 0;
331 }
332 
parse_env_filev(FILE * f,const char * fname,va_list ap)333 int parse_env_filev(
334                 FILE *f,
335                 const char *fname,
336                 va_list ap) {
337 
338         int r;
339         va_list aq;
340 
341         va_copy(aq, ap);
342         r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
343         va_end(aq);
344         return r;
345 }
346 
parse_env_file_sentinel(FILE * f,const char * fname,...)347 int parse_env_file_sentinel(
348                 FILE *f,
349                 const char *fname,
350                 ...) {
351 
352         va_list ap;
353         int r;
354 
355         va_start(ap, fname);
356         r = parse_env_filev(f, fname, ap);
357         va_end(ap);
358 
359         return r;
360 }
361 
load_env_file_push(const char * filename,unsigned line,const char * key,char * value,void * userdata)362 static int load_env_file_push(
363                 const char *filename, unsigned line,
364                 const char *key, char *value,
365                 void *userdata) {
366         char ***m = userdata;
367         char *p;
368         int r;
369 
370         r = check_utf8ness_and_warn(filename, line, key, value);
371         if (r < 0)
372                 return r;
373 
374         p = strjoin(key, "=", value);
375         if (!p)
376                 return -ENOMEM;
377 
378         r = strv_env_replace_consume(m, p);
379         if (r < 0)
380                 return r;
381 
382         free(value);
383         return 0;
384 }
385 
load_env_file(FILE * f,const char * fname,char *** rl)386 int load_env_file(FILE *f, const char *fname, char ***rl) {
387         _cleanup_strv_free_ char **m = NULL;
388         int r;
389 
390         r = parse_env_file_internal(f, fname, load_env_file_push, &m);
391         if (r < 0)
392                 return r;
393 
394         *rl = TAKE_PTR(m);
395         return 0;
396 }
397 
load_env_file_push_pairs(const char * filename,unsigned line,const char * key,char * value,void * userdata)398 static int load_env_file_push_pairs(
399                 const char *filename, unsigned line,
400                 const char *key, char *value,
401                 void *userdata) {
402 
403         char ***m = ASSERT_PTR(userdata);
404         int r;
405 
406         r = check_utf8ness_and_warn(filename, line, key, value);
407         if (r < 0)
408                 return r;
409 
410         /* Check if the key is present */
411         for (char **t = *m; t && *t; t += 2)
412                 if (streq(t[0], key)) {
413                         if (value)
414                                 return free_and_replace(t[1], value);
415                         else
416                                 return free_and_strdup(t+1, "");
417                 }
418 
419         r = strv_extend(m, key);
420         if (r < 0)
421                 return r;
422 
423         if (value)
424                 return strv_push(m, value);
425         else
426                 return strv_extend(m, "");
427 }
428 
load_env_file_pairs(FILE * f,const char * fname,char *** rl)429 int load_env_file_pairs(FILE *f, const char *fname, char ***rl) {
430         _cleanup_strv_free_ char **m = NULL;
431         int r;
432 
433         r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m);
434         if (r < 0)
435                 return r;
436 
437         *rl = TAKE_PTR(m);
438         return 0;
439 }
440 
merge_env_file_push(const char * filename,unsigned line,const char * key,char * value,void * userdata)441 static int merge_env_file_push(
442                 const char *filename, unsigned line,
443                 const char *key, char *value,
444                 void *userdata) {
445 
446         char ***env = userdata;
447         char *expanded_value;
448 
449         assert(env);
450 
451         if (!value) {
452                 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key);
453                 return 0;
454         }
455 
456         if (!env_name_is_valid(key)) {
457                 log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key);
458                 free(value);
459                 return 0;
460         }
461 
462         expanded_value = replace_env(value, *env,
463                                      REPLACE_ENV_USE_ENVIRONMENT|
464                                      REPLACE_ENV_ALLOW_BRACELESS|
465                                      REPLACE_ENV_ALLOW_EXTENDED);
466         if (!expanded_value)
467                 return -ENOMEM;
468 
469         free_and_replace(value, expanded_value);
470 
471         log_debug("%s:%u: setting %s=%s", filename, line, key, value);
472 
473         return load_env_file_push(filename, line, key, value, env);
474 }
475 
merge_env_file(char *** env,FILE * f,const char * fname)476 int merge_env_file(
477                 char ***env,
478                 FILE *f,
479                 const char *fname) {
480 
481         /* NOTE: this function supports braceful and braceless variable expansions,
482          * plus "extended" substitutions, unlike other exported parsing functions.
483          */
484 
485         return parse_env_file_internal(f, fname, merge_env_file_push, env);
486 }
487 
write_env_var(FILE * f,const char * v)488 static void write_env_var(FILE *f, const char *v) {
489         const char *p;
490 
491         p = strchr(v, '=');
492         if (!p) {
493                 /* Fallback */
494                 fputs_unlocked(v, f);
495                 fputc_unlocked('\n', f);
496                 return;
497         }
498 
499         p++;
500         fwrite_unlocked(v, 1, p-v, f);
501 
502         if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
503                 fputc_unlocked('"', f);
504 
505                 for (; *p; p++) {
506                         if (strchr(SHELL_NEED_ESCAPE, *p))
507                                 fputc_unlocked('\\', f);
508 
509                         fputc_unlocked(*p, f);
510                 }
511 
512                 fputc_unlocked('"', f);
513         } else
514                 fputs_unlocked(p, f);
515 
516         fputc_unlocked('\n', f);
517 }
518 
write_env_file(const char * fname,char ** l)519 int write_env_file(const char *fname, char **l) {
520         _cleanup_fclose_ FILE *f = NULL;
521         _cleanup_free_ char *p = NULL;
522         int r;
523 
524         assert(fname);
525 
526         r = fopen_temporary(fname, &f, &p);
527         if (r < 0)
528                 return r;
529 
530         (void) fchmod_umask(fileno(f), 0644);
531 
532         STRV_FOREACH(i, l)
533                 write_env_var(f, *i);
534 
535         r = fflush_and_check(f);
536         if (r >= 0) {
537                 if (rename(p, fname) >= 0)
538                         return 0;
539 
540                 r = -errno;
541         }
542 
543         (void) unlink(p);
544         return r;
545 }
546