chiark / gitweb /
util: overflow hardening
[elogind.git] / src / shared / unit-name.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <assert.h>
25
26 #include "path-util.h"
27 #include "util.h"
28 #include "unit-name.h"
29
30 #define VALID_CHARS                             \
31         "0123456789"                            \
32         "abcdefghijklmnopqrstuvwxyz"            \
33         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"            \
34         ":-_.\\"
35
36 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
37         [UNIT_SERVICE] = "service",
38         [UNIT_SOCKET] = "socket",
39         [UNIT_TARGET] = "target",
40         [UNIT_DEVICE] = "device",
41         [UNIT_MOUNT] = "mount",
42         [UNIT_AUTOMOUNT] = "automount",
43         [UNIT_SNAPSHOT] = "snapshot",
44         [UNIT_TIMER] = "timer",
45         [UNIT_SWAP] = "swap",
46         [UNIT_PATH] = "path",
47 };
48
49 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
50
51 static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
52         [UNIT_STUB] = "stub",
53         [UNIT_LOADED] = "loaded",
54         [UNIT_ERROR] = "error",
55         [UNIT_MERGED] = "merged",
56         [UNIT_MASKED] = "masked"
57 };
58
59 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
60
61 bool unit_name_is_valid(const char *n, bool template_ok) {
62         const char *e, *i, *at;
63
64         /* Valid formats:
65          *
66          *         string@instance.suffix
67          *         string.suffix
68          */
69
70         assert(n);
71
72         if (strlen(n) >= UNIT_NAME_MAX)
73                 return false;
74
75         e = strrchr(n, '.');
76         if (!e || e == n)
77                 return false;
78
79         if (unit_type_from_string(e + 1) < 0)
80                 return false;
81
82         for (i = n, at = NULL; i < e; i++) {
83
84                 if (*i == '@' && !at)
85                         at = i;
86
87                 if (!strchr("@" VALID_CHARS, *i))
88                         return false;
89         }
90
91         if (at) {
92                 if (at == n)
93                         return false;
94
95                 if (!template_ok && at+1 == e)
96                         return false;
97         }
98
99         return true;
100 }
101
102 bool unit_instance_is_valid(const char *i) {
103         assert(i);
104
105         /* The max length depends on the length of the string, so we
106          * don't really check this here. */
107
108         if (i[0] == 0)
109                 return false;
110
111         /* We allow additional @ in the instance string, we do not
112          * allow them in the prefix! */
113
114         for (; *i; i++)
115                 if (!strchr("@" VALID_CHARS, *i))
116                         return false;
117
118         return true;
119 }
120
121 bool unit_prefix_is_valid(const char *p) {
122
123         /* We don't allow additional @ in the instance string */
124
125         if (p[0] == 0)
126                 return false;
127
128         for (; *p; p++)
129                 if (!strchr(VALID_CHARS, *p))
130                         return false;
131
132         return true;
133 }
134
135 int unit_name_to_instance(const char *n, char **instance) {
136         const char *p, *d;
137         char *i;
138
139         assert(n);
140         assert(instance);
141
142         /* Everything past the first @ and before the last . is the instance */
143         p = strchr(n, '@');
144         if (!p) {
145                 *instance = NULL;
146                 return 0;
147         }
148
149         assert_se(d = strrchr(n, '.'));
150         assert(p < d);
151
152         i = strndup(p+1, d-p-1);
153         if (!i)
154                 return -ENOMEM;
155
156         *instance = i;
157         return 0;
158 }
159
160 char *unit_name_to_prefix_and_instance(const char *n) {
161         const char *d;
162
163         assert(n);
164
165         assert_se(d = strrchr(n, '.'));
166
167         return strndup(n, d - n);
168 }
169
170 char *unit_name_to_prefix(const char *n) {
171         const char *p;
172
173         p = strchr(n, '@');
174         if (p)
175                 return strndup(n, p - n);
176
177         return unit_name_to_prefix_and_instance(n);
178 }
179
180 char *unit_name_change_suffix(const char *n, const char *suffix) {
181         char *e, *r;
182         size_t a, b;
183
184         assert(n);
185         assert(unit_name_is_valid(n, true));
186         assert(suffix);
187
188         assert_se(e = strrchr(n, '.'));
189         a = e - n;
190         b = strlen(suffix);
191
192         r = new(char, a + b + 1);
193         if (!r)
194                 return NULL;
195
196         memcpy(r, n, a);
197         memcpy(r+a, suffix, b+1);
198
199         return r;
200 }
201
202 char *unit_name_build(const char *prefix, const char *instance, const char *suffix) {
203         assert(prefix);
204         assert(unit_prefix_is_valid(prefix));
205         assert(!instance || unit_instance_is_valid(instance));
206         assert(suffix);
207
208         if (!instance)
209                 return strappend(prefix, suffix);
210
211         return strjoin(prefix, "@", instance, suffix, NULL);
212 }
213
214 static char *do_escape_char(char c, char *t) {
215         *(t++) = '\\';
216         *(t++) = 'x';
217         *(t++) = hexchar(c >> 4);
218         *(t++) = hexchar(c);
219         return t;
220 }
221
222 static char *do_escape(const char *f, char *t) {
223         assert(f);
224         assert(t);
225
226         /* do not create units with a leading '.', like for "/.dotdir" mount points */
227         if (*f == '.') {
228                 t = do_escape_char(*f, t);
229                 f++;
230         }
231
232         for (; *f; f++) {
233                 if (*f == '/')
234                         *(t++) = '-';
235                 else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
236                         t = do_escape_char(*f, t);
237                 else
238                         *(t++) = *f;
239         }
240
241         return t;
242 }
243
244 char *unit_name_escape(const char *f) {
245         char *r, *t;
246
247         r = new(char, strlen(f)*4+1);
248         if (!r)
249                 return NULL;
250
251         t = do_escape(f, r);
252         *t = 0;
253
254         return r;
255 }
256
257 char *unit_name_unescape(const char *f) {
258         char *r, *t;
259
260         assert(f);
261
262         r = strdup(f);
263         if (!r)
264                 return NULL;
265
266         for (t = r; *f; f++) {
267                 if (*f == '-')
268                         *(t++) = '/';
269                 else if (*f == '\\') {
270                         int a, b;
271
272                         if (f[1] != 'x' ||
273                             (a = unhexchar(f[2])) < 0 ||
274                             (b = unhexchar(f[3])) < 0) {
275                                 /* Invalid escape code, let's take it literal then */
276                                 *(t++) = '\\';
277                         } else {
278                                 *(t++) = (char) ((a << 4) | b);
279                                 f += 3;
280                         }
281                 } else
282                         *(t++) = *f;
283         }
284
285         *t = 0;
286
287         return r;
288 }
289
290 char *unit_name_path_escape(const char *f) {
291         char *p, *e;
292
293         assert(f);
294
295         p = strdup(f);
296         if (!p)
297                 return NULL;
298
299         path_kill_slashes(p);
300
301         if (streq(p, "/")) {
302                 free(p);
303                 return strdup("-");
304         }
305
306         e = unit_name_escape(p[0] == '/' ? p + 1 : p);
307         free(p);
308
309         return e;
310 }
311
312 char *unit_name_path_unescape(const char *f) {
313         char *e;
314
315         assert(f);
316
317         e = unit_name_unescape(f);
318         if (!e)
319                 return NULL;
320
321         if (e[0] != '/') {
322                 char *w;
323
324                 w = strappend("/", e);
325                 free(e);
326
327                 return w;
328         }
329
330         return e;
331 }
332
333 bool unit_name_is_template(const char *n) {
334         const char *p;
335
336         assert(n);
337
338         p = strchr(n, '@');
339         if (!p)
340                 return false;
341
342         return p[1] == '.';
343 }
344
345 bool unit_name_is_instance(const char *n) {
346         const char *p;
347
348         assert(n);
349
350         p = strchr(n, '@');
351         if (!p)
352                 return false;
353
354         return p[1] != '.';
355 }
356
357 char *unit_name_replace_instance(const char *f, const char *i) {
358         const char *p, *e;
359         char *r, *k;
360         size_t a, b;
361
362         assert(f);
363
364         p = strchr(f, '@');
365         if (!p)
366                 return strdup(f);
367
368         e = strrchr(f, '.');
369         if (!e)
370                 assert_se(e = strchr(f, 0));
371
372         a = p - f;
373         b = strlen(i);
374
375         r = new(char, a + 1 + b + strlen(e) + 1);
376         if (!r)
377                 return NULL;
378
379         k = mempcpy(r, f, a + 1);
380         k = mempcpy(k, i, b);
381         strcpy(k, e);
382
383         return r;
384 }
385
386 char *unit_name_template(const char *f) {
387         const char *p, *e;
388         char *r;
389         size_t a;
390
391         p = strchr(f, '@');
392         if (!p)
393                 return strdup(f);
394
395         assert_se(e = strrchr(f, '.'));
396         a = p - f + 1;
397
398         r = new(char, a + strlen(e) + 1);
399         if (!r)
400                 return NULL;
401
402         strcpy(mempcpy(r, f, a), e);
403         return r;
404
405 }
406
407 char *unit_name_from_path(const char *path, const char *suffix) {
408         char *p, *r;
409
410         assert(path);
411         assert(suffix);
412
413         p = unit_name_path_escape(path);
414         if (!p)
415                 return NULL;
416
417         r = strappend(p, suffix);
418         free(p);
419
420         return r;
421 }
422
423 char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) {
424         char *p, *r;
425
426         assert(prefix);
427         assert(path);
428         assert(suffix);
429
430         p = unit_name_path_escape(path);
431         if (!p)
432                 return NULL;
433
434         r = strjoin(prefix, "@", p, suffix, NULL);
435         free(p);
436
437         return r;
438 }
439
440 char *unit_name_to_path(const char *name) {
441         char *w, *e;
442
443         assert(name);
444
445         w = unit_name_to_prefix(name);
446         if (!w)
447                 return NULL;
448
449         e = unit_name_path_unescape(w);
450         free(w);
451
452         return e;
453 }
454
455 char *unit_dbus_path_from_name(const char *name) {
456         char *e, *p;
457
458         assert(name);
459
460         e = bus_path_escape(name);
461         if (!e)
462                 return NULL;
463
464         p = strappend("/org/freedesktop/systemd1/unit/", e);
465         free(e);
466
467         return p;
468 }
469
470 char *unit_name_mangle(const char *name) {
471         char *r, *t;
472         const char *f;
473         bool dot = false;
474
475         assert(name);
476
477         /* Try to turn a string that might not be a unit name into a
478          * sensible unit name. */
479
480         if (is_device_path(name))
481                 return unit_name_from_path(name, ".device");
482
483         if (path_is_absolute(name))
484                 return unit_name_from_path(name, ".mount");
485
486         /* We'll only escape the obvious characters here, to play
487          * safe. */
488
489         r = new(char, strlen(name) * 4 + 1 + sizeof(".service")-1);
490         if (!r)
491                 return NULL;
492
493         for (f = name, t = r; *f; f++) {
494
495                 if (*f == '.')
496                         dot = true;
497
498                 if (*f == '/')
499                         *(t++) = '-';
500                 else if (!strchr("@" VALID_CHARS, *f))
501                         t = do_escape_char(*f, t);
502                 else
503                         *(t++) = *f;
504         }
505
506         if (!dot)
507                 strcpy(t, ".service");
508         else
509                 *t = 0;
510
511         return r;
512 }
513
514 UnitType unit_name_to_type(const char *n) {
515         const char *e;
516
517         assert(n);
518
519         e = strrchr(n, '.');
520         if (!e)
521                 return _UNIT_TYPE_INVALID;
522
523         return unit_type_from_string(e + 1);
524 }