Coverage Report

Created: 2026-09-03 07:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/lib/util/substitute.c
Line
Count
Source
1
/* 
2
   Unix SMB/CIFS implementation.
3
   Samba utility functions
4
   
5
   Copyright (C) Andrew Tridgell 1992-2001
6
   Copyright (C) Simo Sorce      2001-2002
7
   Copyright (C) Martin Pool     2003
8
   Copyright (C) James Peach   2005
9
   
10
   This program is free software; you can redistribute it and/or modify
11
   it under the terms of the GNU General Public License as published by
12
   the Free Software Foundation; either version 3 of the License, or
13
   (at your option) any later version.
14
   
15
   This program is distributed in the hope that it will be useful,
16
   but WITHOUT ANY WARRANTY; without even the implied warranty of
17
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
   GNU General Public License for more details.
19
   
20
   You should have received a copy of the GNU General Public License
21
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
*/
23
24
#include "replace.h"
25
#include "system/locale.h"
26
#include "debug.h"
27
#ifndef SAMBA_UTIL_CORE_ONLY
28
#include "lib/util/fault.h"
29
#include "lib/util/talloc_stack.h"
30
#include "charset/charset.h"
31
#else
32
#include "charset_compat.h"
33
#endif
34
#include "substitute.h"
35
36
/**
37
 * @file
38
 * @brief Substitute utilities.
39
 **/
40
41
static inline
42
char mask_unsafe_character(char in,
43
         bool is_last,
44
         bool allow_trailing_dollar,
45
         const char *unsafe_characters,
46
         char safe_out)
47
14.3k
{
48
14.3k
  const char *unsafe = NULL;
49
50
14.3k
  if (unsafe_characters == NULL) {
51
0
    return in;
52
0
  }
53
54
  /* allow a trailing $ (as in machine accounts) */
55
14.3k
  if (allow_trailing_dollar && is_last && in == '$') {
56
0
    return in;
57
0
  }
58
59
14.3k
  if (iscntrl(in)) {
60
0
    return safe_out;
61
0
  }
62
63
14.3k
  unsafe = strchr(unsafe_characters, in);
64
14.3k
  if (unsafe != NULL) {
65
0
    return safe_out;
66
0
  }
67
68
  /* ok */
69
14.3k
  return in;
70
14.3k
}
71
72
/**
73
 Substitute a string for a pattern in another string. Make sure there is
74
 enough room!
75
76
 This routine looks for pattern in s and replaces it with
77
 insert. It may do multiple replacements or just one.
78
79
 Any of STRING_SUB_UNSAFE_CHARACTERS and any character
80
 caught by calling iscntrl() in the insert string are replaced with _
81
82
 if len==0 then the string cannot be extended. This is different from the old
83
 use of len==0 which was for no length checks to be done.
84
**/
85
86
void string_sub(char *s, const char *pattern, const char *insert, size_t len)
87
2.22k
{
88
2.22k
  const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
89
2.22k
  char safe_character = '_';
90
2.22k
  char *p;
91
2.22k
  size_t ls, lp, li, i;
92
93
2.22k
  if (!insert || !pattern || !*pattern || !s)
94
0
    return;
95
96
2.22k
  ls = strlen(s);
97
2.22k
  lp = strlen(pattern);
98
2.22k
  li = strlen(insert);
99
100
2.22k
  if (len == 0)
101
2.22k
    len = ls + 1; /* len is number of *bytes* */
102
103
20.9k
  while (lp <= ls && (p = strstr_m(s,pattern))) {
104
18.7k
    if (ls + li - lp >= len) {
105
0
      DBG_ERR("ERROR: string overflow by "
106
0
        "%zu in string_sub(%.50s, %zu)\n",
107
0
        ls + li - lp + 1 - len,
108
0
        pattern,
109
0
        len);
110
0
      break;
111
0
    }
112
18.7k
    if (li != lp) {
113
7.34k
      memmove(p+li,p+lp,strlen(p+lp)+1);
114
7.34k
    }
115
33.0k
    for (i=0;i<li;i++) {
116
      /*
117
       * Without allow_trailing_dollar we don't
118
       * need to calculate is_last...
119
       */
120
14.3k
      const bool is_last = false;
121
14.3k
      const bool allow_trailing_dollar = false;
122
123
14.3k
      p[i] = mask_unsafe_character(insert[i],
124
14.3k
                 is_last,
125
14.3k
                 allow_trailing_dollar,
126
14.3k
                 unsafe_characters,
127
14.3k
                 safe_character);
128
14.3k
    }
129
18.7k
    s = p + li;
130
18.7k
    ls = ls + li - lp;
131
18.7k
  }
132
2.22k
}
133
134
/**
135
 Similar to string_sub() but allows for any character to be substituted. 
136
 Use with caution!
137
 if len==0 then the string cannot be extended. This is different from the old
138
 use of len==0 which was for no length checks to be done.
139
**/
140
141
_PUBLIC_ void all_string_sub(char *s,const char *pattern,const char *insert, size_t len)
142
1.76k
{
143
1.76k
  char *p;
144
1.76k
  size_t ls,lp,li;
145
146
1.76k
  if (!insert || !pattern || !s)
147
0
    return;
148
149
1.76k
  ls = strlen(s);
150
1.76k
  lp = strlen(pattern);
151
1.76k
  li = strlen(insert);
152
153
1.76k
  if (!*pattern)
154
0
    return;
155
156
1.76k
  if (len == 0)
157
1.76k
    len = ls + 1; /* len is number of *bytes* */
158
159
20.9k
  while (lp <= ls && (p = strstr_m(s,pattern))) {
160
19.2k
    if (ls + li - lp >= len) {
161
0
      DBG_ERR("ERROR: string overflow by "
162
0
        "%zu in all_string_sub(%.50s, %zu)\n",
163
0
        ls + li - lp + 1 - len,
164
0
        pattern,
165
0
        len);
166
0
      break;
167
0
    }
168
19.2k
    if (li != lp) {
169
18.3k
      memmove(p+li,p+lp,strlen(p+lp)+1);
170
18.3k
    }
171
19.2k
    memcpy(p, insert, li);
172
19.2k
    s = p + li;
173
19.2k
    ls = ls + li - lp;
174
19.2k
  }
175
1.76k
}
176
177
/*
178
 * Internal guts of talloc_string_sub and talloc_all_string_sub.
179
 * talloc version of string_sub2.
180
 */
181
182
bool realloc_string_sub_raw(char **_string,
183
          const char *pattern,
184
          const char *insert,
185
          bool replace_once,
186
          bool allow_trailing_dollar,
187
          const char *unsafe_characters,
188
          char safe_character)
189
0
{
190
0
  char *p = NULL;
191
0
  char *s = NULL;
192
0
  char *string = NULL;
193
0
  ssize_t ls,lp,li,ld, i;
194
195
0
  if (!insert || !pattern || !*pattern || !_string|| !*_string) {
196
0
    return false;
197
0
  }
198
199
0
  s = string = *_string;
200
201
0
  ls = (ssize_t)strlen(s);
202
0
  lp = (ssize_t)strlen(pattern);
203
0
  li = (ssize_t)strlen(insert);
204
0
  ld = li - lp;
205
206
0
  while ((p = strstr_m(s,pattern))) {
207
0
    if (ld > 0) {
208
0
      ptrdiff_t offset = PTR_DIFF(s,string);
209
0
      string = talloc_realloc(NULL, string, char, ls + ld + 1);
210
0
      if (!string) {
211
0
        DBG_ERR("out of memory(realloc)!\n");
212
0
        return false;
213
0
      }
214
0
      *_string = string;
215
0
      p = string + offset + (p - s);
216
0
    }
217
0
    if (li != lp) {
218
0
      memmove(p+li,p+lp,strlen(p+lp)+1);
219
0
    }
220
0
    for (i=0; i < li; i++) {
221
0
      bool is_last = (i == li - 1);
222
223
0
      p[i] = mask_unsafe_character(insert[i],
224
0
                 is_last,
225
0
                 allow_trailing_dollar,
226
0
                 unsafe_characters,
227
0
                 safe_character);
228
0
    }
229
0
    s = p + li;
230
0
    ls += ld;
231
232
0
    if (replace_once) {
233
0
      break;
234
0
    }
235
0
  }
236
0
  return true;
237
0
}
238
239
char *talloc_string_sub2(TALLOC_CTX *mem_ctx,
240
       const char *src,
241
       const char *pattern,
242
       const char *insert,
243
       bool remove_unsafe_characters,
244
       bool replace_once,
245
       bool allow_trailing_dollar)
246
0
{
247
0
  const char *unsafe_characters = NULL;
248
0
  char safe_character = '\0';
249
0
  char *string = NULL;
250
0
  bool ok;
251
252
0
  if (!insert || !pattern || !*pattern || !src) {
253
0
    return NULL;
254
0
  }
255
256
0
  if (remove_unsafe_characters) {
257
0
    unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS;
258
0
    safe_character = '_';
259
0
  }
260
261
0
  string = talloc_strdup(mem_ctx, src);
262
0
  if (string == NULL) {
263
0
    DBG_ERR("out of memory, talloc_strdup(src)!\n");
264
0
    return NULL;
265
0
  }
266
267
0
  ok = realloc_string_sub_raw(&string,
268
0
            pattern,
269
0
            insert,
270
0
            replace_once,
271
0
            allow_trailing_dollar,
272
0
            unsafe_characters,
273
0
            safe_character);
274
0
  if (!ok) {
275
0
    TALLOC_FREE(string);
276
0
    DBG_ERR("out of memory, realloc_string_sub_raw()!\n");
277
0
    return NULL;
278
0
  }
279
280
0
  return string;
281
0
}
282
283
/* Same as string_sub, but returns a talloc'ed string */
284
285
char *talloc_string_sub(TALLOC_CTX *mem_ctx,
286
      const char *src,
287
      const char *pattern,
288
      const char *insert)
289
0
{
290
0
  return talloc_string_sub2(mem_ctx, src, pattern, insert,
291
0
      true, false, false);
292
0
}
293
294
char *talloc_all_string_sub(TALLOC_CTX *ctx,
295
        const char *src,
296
        const char *pattern,
297
        const char *insert)
298
0
{
299
0
  return talloc_string_sub2(ctx, src, pattern, insert,
300
0
      false, false, false);
301
0
}
302
303
#ifndef SAMBA_UTIL_CORE_ONLY
304
305
bool talloc_string_sub_mixed_quoting(const char *full_cmd, char variable_char)
306
0
{
307
  /*
308
   * Try to make sure talloc_string_sub_unsafe()
309
   * won't return NULL, instead talloc_stackframe_pool()
310
   * would panic
311
   */
312
0
  size_t cmd_len = full_cmd != NULL ? strlen(full_cmd) : 0;
313
0
  size_t pool_size = 512 + cmd_len;
314
0
  TALLOC_CTX *frame = talloc_stackframe_pool(pool_size);
315
0
  char *cmd = NULL;
316
0
  bool modified = false;
317
0
  bool masked = false;
318
0
  bool mixed_fallback = false;
319
320
0
  cmd = talloc_string_sub_unsafe(frame,
321
0
               full_cmd,
322
0
               variable_char,
323
0
               "U",  /* unsafe_value */
324
0
               "'\"%", /* unsafe_characters */
325
0
               '_',    /* safe_character */
326
0
               "F",  /* fallback_value */
327
0
               &modified,
328
0
               &masked,
329
0
               &mixed_fallback);
330
0
  if (cmd == NULL) {
331
0
    mixed_fallback = false;
332
0
  }
333
0
  TALLOC_FREE(frame);
334
0
  return mixed_fallback;
335
0
}
336
337
char *talloc_string_sub_unsafe(TALLOC_CTX *mem_ctx,
338
             const char *orig_cmd,
339
             char variable_char,
340
             const char *unsafe_value,
341
             const char *unsafe_characters,
342
             char safe_character,
343
             const char *fallback_value,
344
             bool *_modified,
345
             bool *_masked,
346
             bool *_mixed_fallback)
347
0
{
348
0
  TALLOC_CTX *frame = talloc_stackframe();
349
0
  const char variable[3] =
350
0
    { '%', variable_char, '\0' };
351
0
  const char variable_s_quoted[5] =
352
0
    { '\'', '%', variable_char, '\'', '\0' };
353
0
  const char variable_d_quoted[5] =
354
0
    { '"', '%', variable_char, '"', '\0' };
355
0
  char *cmd = NULL;
356
0
  char *masked_value = NULL;
357
0
  char *quoted_value = NULL;
358
0
  bool has_s_quotes;
359
0
  bool has_d_quotes;
360
0
  bool has_variable;
361
0
  bool has_variable_s_quoted;
362
0
  bool has_variable_d_quoted;
363
0
  bool modified = false;
364
0
  bool masked = false;
365
0
  bool mixed_fallback = false;
366
0
  bool ok;
367
368
  /*
369
   * The unsafe_characters argument should contain
370
   * single and double quotes.
371
   * Otherwise We can't safely handle this.
372
   */
373
0
  SMB_ASSERT(unsafe_characters != NULL);
374
0
  SMB_ASSERT(strchr(unsafe_characters, '\'') != NULL);
375
0
  SMB_ASSERT(strchr(unsafe_characters, '"') != NULL);
376
0
  SMB_ASSERT(strchr(unsafe_characters, '%') != NULL);
377
378
0
  cmd = talloc_strdup(mem_ctx, orig_cmd);
379
0
  if (cmd == NULL) {
380
0
    TALLOC_FREE(frame);
381
0
    return NULL;
382
0
  }
383
0
  cmd = talloc_steal(frame, cmd);
384
385
0
  has_variable = strstr(orig_cmd, variable) != NULL;
386
0
  if (!has_variable) {
387
    /*
388
     * Nothing to do...
389
     */
390
0
    goto done;
391
0
  }
392
0
  modified = true;
393
394
  /*
395
   * Replace all unsafe characters as well as control
396
   * characters.
397
   *
398
   * Note that we start with masked_value = "%u"
399
   * and then replace "%u" with unsafe_value,
400
   * as a result we have a masked version of
401
   * unsafe_value.
402
   *
403
   * And don't allow option injected like
404
   *
405
   * '-h value'
406
   * '--help value'
407
   *
408
   */
409
0
  masked_value = talloc_strdup(frame, variable);
410
0
  if (masked_value == NULL) {
411
0
    goto nomem;
412
0
  }
413
0
  ok = realloc_string_sub_raw(&masked_value,
414
0
            variable,
415
0
            unsafe_value,
416
0
            false, /* replace_once */
417
0
            false, /* allow_trailing_dollar */
418
0
            unsafe_characters,
419
0
            safe_character);
420
0
  if (!ok) {
421
0
    goto nomem;
422
0
  }
423
0
  if (masked_value[0] == '-') {
424
0
    masked_value[0] = safe_character;
425
0
  }
426
0
  masked = strcmp(masked_value, unsafe_value) != 0;
427
428
0
retry:
429
430
0
  has_s_quotes = strchr(cmd, '\'') != NULL;
431
0
  has_d_quotes = strchr(cmd, '"') != NULL;
432
0
  has_variable = strstr(cmd, variable) != NULL;
433
0
  has_variable_s_quoted = strstr(cmd, variable_s_quoted) != NULL;
434
0
  has_variable_d_quoted = strstr(cmd, variable_d_quoted) != NULL;
435
436
0
  if (has_variable_s_quoted) {
437
    /*
438
     * In smb.conf we have something like
439
     *
440
     * some script = /usr/bin/script '%u'
441
     *
442
     * It is safe to replace '%u' (or '%J' etc, depending
443
     * on variable_char) with '<masked_value>' if
444
     * masked_value does not contain single quotes. We
445
     * have checked that.
446
     */
447
448
0
    if (quoted_value == NULL) {
449
0
      quoted_value = talloc_asprintf(frame, "'%s'",
450
0
                   masked_value);
451
0
      if (quoted_value == NULL) {
452
0
        goto nomem;
453
0
      }
454
0
    }
455
456
0
    ok = realloc_string_sub_raw(&cmd,
457
0
              variable_s_quoted,
458
0
              quoted_value,
459
0
              false, /* replace_once */
460
0
              false, /* allow_trailing_dollar */
461
0
              NULL,  /* unsafe_characters */
462
0
              '\0'); /* safe_character */
463
0
    if (!ok) {
464
0
      goto nomem;
465
0
    }
466
467
0
    goto retry;
468
0
  }
469
470
0
  if (has_variable_d_quoted && !has_s_quotes) {
471
    /*
472
     * replace the "%u"
473
     *
474
     * some script = /usr/bin/script "%u"
475
     *
476
     * with '%u' and try the '%u' -> 'variable' substitution
477
     * again.
478
     */
479
480
0
    ok = realloc_string_sub_raw(&cmd,
481
0
              variable_d_quoted,
482
0
              variable_s_quoted,
483
0
              false, /* replace_once */
484
0
              false, /* allow_trailing_dollar */
485
0
              NULL,  /* unsafe_characters */
486
0
              '\0'); /* safe_character */
487
0
    if (!ok) {
488
0
      goto nomem;
489
0
    }
490
491
0
    goto retry;
492
0
  }
493
494
0
  if (has_variable && !has_s_quotes && !has_d_quotes) {
495
    /*
496
     * In this case:
497
     *
498
     * some script = /usr/bin/script %u
499
     *
500
     * we can safely substitute %u -> '%u' and try the
501
     * single quote test again.
502
     */
503
504
0
    ok = realloc_string_sub_raw(&cmd,
505
0
              variable,
506
0
              variable_s_quoted,
507
0
              false, /* replace_once */
508
0
              false, /* allow_trailing_dollar */
509
0
              NULL,  /* unsafe_characters */
510
0
              '\0'); /* safe_character */
511
0
    if (!ok) {
512
0
      goto nomem;
513
0
    }
514
515
0
    goto retry;
516
0
  }
517
518
0
  if (has_variable) {
519
    /*
520
     * There are single or double quotes, but not tightly
521
     * bound around a %u.
522
     *
523
     * Or there's a mix of single and double quotes.
524
     *
525
     * We just use a generic fallback value.
526
     * and let the caller warn about this
527
     * and give the admin a hind to fix the smb.conf
528
     * option.
529
     */
530
0
    mixed_fallback = true;
531
532
0
    ok = realloc_string_sub_raw(&cmd,
533
0
              variable,
534
0
              fallback_value,
535
0
              false, /* replace_once */
536
0
              false, /* allow_trailing_dollar */
537
0
              NULL,  /* unsafe_characters */
538
0
              '\0'); /* safe_character */
539
0
    if (!ok) {
540
0
      goto nomem;
541
0
    }
542
0
  }
543
544
0
done:
545
0
  *_modified = modified;
546
0
  *_masked = masked;
547
0
  *_mixed_fallback = mixed_fallback;
548
0
  cmd = talloc_steal(mem_ctx, cmd);
549
0
  TALLOC_FREE(frame);
550
0
  return cmd;
551
552
0
nomem:
553
0
  *_modified = false;
554
0
  *_masked = false;
555
0
  *_mixed_fallback = false;
556
0
  TALLOC_FREE(frame);
557
  return NULL;
558
0
}
559
#endif /* ! SAMBA_UTIL_CORE_ONLY */