Coverage Report

Created: 2025-07-23 07:04

/src/samba/source3/lib/smbrun.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   Unix SMB/CIFS implementation.
3
   run a command as a specified user
4
   Copyright (C) Andrew Tridgell 1992-1998
5
6
   This program is free software; you can redistribute it and/or modify
7
   it under the terms of the GNU General Public License as published by
8
   the Free Software Foundation; either version 3 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 General Public License for more details.
15
16
   You should have received a copy of the GNU General Public License
17
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
*/
19
20
#include "includes.h"
21
#include "system/filesys.h"
22
23
/* need to move this from here!! need some sleep ... */
24
struct current_user current_user;
25
26
/****************************************************************************
27
This is a utility function of smbrun().
28
****************************************************************************/
29
30
static int setup_out_fd(void)
31
0
{
32
0
  int fd;
33
0
  TALLOC_CTX *ctx = talloc_stackframe();
34
0
  char *path = NULL;
35
0
  mode_t mask;
36
37
0
  path = talloc_asprintf(ctx,
38
0
        "%s/smb.XXXXXX",
39
0
        tmpdir());
40
0
  if (!path) {
41
0
    TALLOC_FREE(ctx);
42
0
    errno = ENOMEM;
43
0
    return -1;
44
0
  }
45
46
  /* now create the file */
47
0
  mask = umask(S_IRWXO | S_IRWXG);
48
0
  fd = mkstemp(path);
49
0
  umask(mask);
50
51
0
  if (fd == -1) {
52
0
    DEBUG(0,("setup_out_fd: Failed to create file %s. (%s)\n",
53
0
      path, strerror(errno) ));
54
0
    TALLOC_FREE(ctx);
55
0
    return -1;
56
0
  }
57
58
0
  DEBUG(10,("setup_out_fd: Created tmp file %s\n", path ));
59
60
  /* Ensure file only kept around by open fd. */
61
0
  unlink(path);
62
0
  TALLOC_FREE(ctx);
63
0
  return fd;
64
0
}
65
66
/****************************************************************************
67
run a command being careful about uid/gid handling and putting the output in
68
outfd (or discard it if outfd is NULL).
69
****************************************************************************/
70
71
static int smbrun_internal(const char *cmd, int *outfd, bool sanitize,
72
  char * const *env)
73
0
{
74
0
  pid_t pid;
75
0
  uid_t uid = current_user.ut.uid;
76
0
  gid_t gid = current_user.ut.gid;
77
0
  void (*saved_handler)(int);
78
79
  /*
80
   * Lose any elevated privileges.
81
   */
82
0
  drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
83
0
  drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
84
85
  /* point our stdout at the file we want output to go into */
86
87
0
  if (outfd && ((*outfd = setup_out_fd()) == -1)) {
88
0
    return -1;
89
0
  }
90
91
  /* in this method we will exec /bin/sh with the correct
92
     arguments, after first setting stdout to point at the file */
93
94
  /*
95
   * We need to temporarily stop CatchChild from eating
96
   * SIGCLD signals as it also eats the exit status code. JRA.
97
   */
98
99
0
  saved_handler = CatchChildLeaveStatus();
100
                                    
101
0
  if ((pid=fork()) < 0) {
102
0
    DEBUG(0,("smbrun: fork failed with error %s\n", strerror(errno) ));
103
0
    (void)CatchSignal(SIGCLD, saved_handler);
104
0
    if (outfd) {
105
0
      close(*outfd);
106
0
      *outfd = -1;
107
0
    }
108
0
    return errno;
109
0
  }
110
111
0
  if (pid) {
112
    /*
113
     * Parent.
114
     */
115
0
    int status=0;
116
0
    pid_t wpid;
117
118
    
119
    /* the parent just waits for the child to exit */
120
0
    while((wpid = waitpid(pid,&status,0)) < 0) {
121
0
      if(errno == EINTR) {
122
0
        errno = 0;
123
0
        continue;
124
0
      }
125
0
      break;
126
0
    }
127
128
0
    (void)CatchSignal(SIGCLD, saved_handler);
129
130
0
    if (wpid != pid) {
131
0
      DEBUG(2,("waitpid(%d) : %s\n",(int)pid,strerror(errno)));
132
0
      if (outfd) {
133
0
        close(*outfd);
134
0
        *outfd = -1;
135
0
      }
136
0
      return -1;
137
0
    }
138
139
    /* Reset the seek pointer. */
140
0
    if (outfd) {
141
0
      lseek(*outfd, 0, SEEK_SET);
142
0
    }
143
144
0
#if defined(WIFEXITED) && defined(WEXITSTATUS)
145
0
    if (WIFEXITED(status)) {
146
0
      return WEXITSTATUS(status);
147
0
    }
148
0
#endif
149
150
0
    return status;
151
0
  }
152
  
153
0
  (void)CatchChild();
154
  
155
  /* we are in the child. we exec /bin/sh to do the work for us. we
156
     don't directly exec the command we want because it may be a
157
     pipeline or anything else the config file specifies */
158
  
159
  /* point our stdout at the file we want output to go into */
160
0
  if (outfd) {
161
0
    close(1);
162
0
    if (dup2(*outfd,1) != 1) {
163
0
      DEBUG(2,("Failed to create stdout file descriptor\n"));
164
0
      close(*outfd);
165
0
      exit(80);
166
0
    }
167
0
  }
168
169
  /* now completely lose our privileges. This is a fairly paranoid
170
     way of doing it, but it does work on all systems that I know of */
171
172
0
  become_user_permanently(uid, gid);
173
174
0
  if (!non_root_mode()) {
175
0
    if (getuid() != uid || geteuid() != uid ||
176
0
        getgid() != gid || getegid() != gid) {
177
      /* we failed to lose our privileges - do not execute
178
         the command */
179
0
      exit(81); /* we can't print stuff at this stage,
180
             instead use exit codes for debugging */
181
0
    }
182
0
  }
183
184
  /* close all other file descriptors, leaving only 0, 1 and 2. 0 and
185
     2 point to /dev/null from the startup code */
186
0
  closefrom(3);
187
188
0
  {
189
0
    char *newcmd = NULL;
190
0
    if (sanitize) {
191
0
      newcmd = escape_shell_string(cmd);
192
0
      if (!newcmd)
193
0
        exit(82);
194
0
    }
195
196
0
    if (env != NULL) {
197
0
      execle("/bin/sh","sh","-c",
198
0
        newcmd ? (const char *)newcmd : cmd, NULL,
199
0
        env);
200
0
    } else {
201
0
      execl("/bin/sh","sh","-c",
202
0
        newcmd ? (const char *)newcmd : cmd, NULL);
203
0
    }
204
205
0
    SAFE_FREE(newcmd);
206
0
  }
207
  
208
  /* not reached */
209
0
  exit(83);
210
0
  return 1;
211
0
}
212
213
/****************************************************************************
214
 Use only in known safe shell calls (printing).
215
****************************************************************************/
216
217
int smbrun_no_sanitize(const char *cmd, int *outfd, char * const *env)
218
0
{
219
0
  return smbrun_internal(cmd, outfd, false, env);
220
0
}
221
222
/****************************************************************************
223
 By default this now sanitizes shell expansion.
224
****************************************************************************/
225
226
int smbrun(const char *cmd, int *outfd, char * const *env)
227
0
{
228
0
  return smbrun_internal(cmd, outfd, true, env);
229
0
}
230
231
/****************************************************************************
232
run a command being careful about uid/gid handling and putting the output in
233
outfd (or discard it if outfd is NULL).
234
sends the provided secret to the child stdin.
235
****************************************************************************/
236
237
int smbrunsecret(const char *cmd, const char *secret)
238
0
{
239
0
  pid_t pid;
240
0
  uid_t uid = current_user.ut.uid;
241
0
  gid_t gid = current_user.ut.gid;
242
0
  int ifd[2];
243
0
  void (*saved_handler)(int);
244
  
245
  /*
246
   * Lose any elevated privileges.
247
   */
248
0
  drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
249
0
  drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
250
251
  /* build up an input pipe */
252
0
  if(pipe(ifd)) {
253
0
    return -1;
254
0
  }
255
256
  /* in this method we will exec /bin/sh with the correct
257
     arguments, after first setting stdout to point at the file */
258
259
  /*
260
   * We need to temporarily stop CatchChild from eating
261
   * SIGCLD signals as it also eats the exit status code. JRA.
262
   */
263
264
0
  saved_handler = CatchChildLeaveStatus();
265
                                    
266
0
  if ((pid=fork()) < 0) {
267
0
    DEBUG(0, ("smbrunsecret: fork failed with error %s\n", strerror(errno)));
268
0
    (void)CatchSignal(SIGCLD, saved_handler);
269
0
    return errno;
270
0
      }
271
272
0
  if (pid) {
273
    /*
274
     * Parent.
275
     */
276
0
    int status = 0;
277
0
    pid_t wpid;
278
0
    size_t towrite;
279
0
    ssize_t wrote;
280
    
281
0
    close(ifd[0]);
282
    /* send the secret */
283
0
    towrite = strlen(secret);
284
0
    wrote = write(ifd[1], secret, towrite);
285
0
    if ( wrote != towrite ) {
286
0
        DEBUG(0,("smbrunsecret: wrote %ld of %lu bytes\n",(long)wrote,(unsigned long)towrite));
287
0
    }
288
0
    fsync(ifd[1]);
289
0
    close(ifd[1]);
290
291
    /* the parent just waits for the child to exit */
292
0
    while((wpid = waitpid(pid, &status, 0)) < 0) {
293
0
      if(errno == EINTR) {
294
0
        errno = 0;
295
0
        continue;
296
0
      }
297
0
      break;
298
0
    }
299
300
0
    (void)CatchSignal(SIGCLD, saved_handler);
301
302
0
    if (wpid != pid) {
303
0
      DEBUG(2, ("waitpid(%d) : %s\n", (int)pid, strerror(errno)));
304
0
      return -1;
305
0
    }
306
307
0
#if defined(WIFEXITED) && defined(WEXITSTATUS)
308
0
    if (WIFEXITED(status)) {
309
0
      return WEXITSTATUS(status);
310
0
    }
311
0
#endif
312
313
0
    return status;
314
0
  }
315
  
316
0
  (void)CatchChild();
317
  
318
  /* we are in the child. we exec /bin/sh to do the work for us. we
319
     don't directly exec the command we want because it may be a
320
     pipeline or anything else the config file specifies */
321
  
322
0
  close(ifd[1]);
323
0
  close(0);
324
0
  if (dup2(ifd[0], 0) != 0) {
325
0
    DEBUG(2,("Failed to create stdin file descriptor\n"));
326
0
    close(ifd[0]);
327
0
    exit(80);
328
0
  }
329
330
  /* now completely lose our privileges. This is a fairly paranoid
331
     way of doing it, but it does work on all systems that I know of */
332
333
0
  become_user_permanently(uid, gid);
334
335
0
  if (!non_root_mode()) {
336
0
    if (getuid() != uid || geteuid() != uid ||
337
0
        getgid() != gid || getegid() != gid) {
338
      /* we failed to lose our privileges - do not execute
339
         the command */
340
0
      exit(81); /* we can't print stuff at this stage,
341
             instead use exit codes for debugging */
342
0
    }
343
0
  }
344
345
  /* close all other file descriptors, leaving only 0, 1 and 2. 0 and
346
     2 point to /dev/null from the startup code */
347
0
  closefrom(3);
348
349
0
  execl("/bin/sh", "sh", "-c", cmd, NULL);  
350
351
  /* not reached */
352
0
  exit(82);
353
0
  return 1;
354
0
}