File: servexec.c

package info (click to toggle)
userv 1.2.0
  • links: PTS
  • area: main
  • in suites: bullseye, buster, sid, stretch
  • size: 700 kB
  • ctags: 1,013
  • sloc: ansic: 4,220; lex: 287; sh: 207; makefile: 206
file content (330 lines) | stat: -rw-r--r-- 10,396 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/*
 * userv - execserv.c
 * daemon code which executes actual service (ie child process)
 *
 * userv is
 * Copyright 1996-2017 Ian Jackson <ian@davenant.greenend.org.uk>.
 * Copyright 2000      Ben Harris <bjh21@cam.ac.uk>
 * Copyright 2016-2017 Peter Benie <pjb1008@cam.ac.uk>
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with userv; if not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>

#include "config.h"
#include "common.h"
#include "daemon.h"
#include "lib.h"
#include "both.h"
#include "version.h"

static void NONRETURNING serv_syscallfail(const char *msg) {
  fputs("uservd(service): ",stderr);
  perror(msg);
  _exit(-1);
}

static void NONRETURNING serv_checkstdoutexit(void) {
  if (ferror(stdout) || fclose(stdout)) serv_syscallfail("write stdout");
  _exit(0);
}

void bisexec_environment(const char *const *argv) {
  execlp("env","env",(char*)0);
  serv_syscallfail("execute `env'");
}

void bisexec_parameter(const char *const *argv) {
  always_dumpparameter(execargs[0],execargs+1);
  serv_checkstdoutexit();
}
  
void bisexec_help(const char *const *argv) {
  const char *const *pp;
  
  fputs("recognised builtin services:\n",stdout);
  for (pp= builtinservicehelpstrings; *pp; pp++) printf("  %s\n",*pp);
  serv_checkstdoutexit();
}
  
void bisexec_version(const char *const *argv) {
  const unsigned char *p;
  int i;
  
  printf("uservd version " VERSION VEREXT "\n"
#ifdef DEBUG
	 "DEBUGGING VERSION"
#else
	 "production version"
#endif
	 " - protocol magic number %08lx\n"
	 "maximums:    fd %-10d                general string %d\n"
	 "             gids %-10d              override length %d\n"
	 "             args or variables %-10d error message %d\n"
	 "             nested inclusion %-10d  errno string reserve %d\n"
	 "protocol checksum: ",
	 BASE_MAGIC,
	 MAX_ALLOW_FD, MAX_GENERAL_STRING,
	 MAX_GIDS, MAX_OVERRIDE_LEN,
	 MAX_ARGSDEFVAR, MAX_ERRMSG_LEN,
	 MAX_INCLUDE_NEST, ERRMSG_RESERVE_ERRNO);
  for (i=0, p=protocolchecksumversion; i<sizeof(protocolchecksumversion); i++, p++)
    printf("%02x",*p);
  printf("\n"
	 "rendezvous socket: `" RENDEZVOUSPATH "'\n"
	 "system config dir: `" SYSTEMCONFIGDIR "'\n"
	 "pipe filename format: `%s' (max length %d)\n"
	 COPYRIGHT("","\n"),
	 PIPEFORMAT, PIPEMAXLEN);
  serv_checkstdoutexit();
}

static void NONRETURNING dumpconfig(const char *string) {
  int nspaces, c, lnl;
  
  nspaces= 0;
  lnl= 1;
  while ((c= *string++)) {
    switch (c) {
    case ' ': nspaces++; break;
    case '\n':
      if (!lnl) putchar('\n');
      nspaces= 0; lnl= 1;
      break;
    default:
      while (nspaces>0) { putchar(' '); nspaces--; }
      putchar(c);
      lnl= 0;
      break;
    }
  }
  assert(lnl);
  serv_checkstdoutexit();
}

void bisexec_toplevel(const char *const *argv) {
  dumpconfig(TOPLEVEL_CONFIGURATION);
}

void bisexec_override(const char *const *argv) {
  dumpconfig(TOPLEVEL_OVERRIDDEN_CONFIGURATION);
}

void bisexec_reset(const char *const *argv) {
  dumpconfig(RESET_CONFIGURATION);
}

void bisexec_execute(const char *const *argv) {
  always_dumpexecsettings();
  serv_checkstdoutexit();
}

void bisexec_shutdown(const char *const *argv) {
  /* This is only reached if the serviceuser_uid test in
   * process.c:servicerequest() fails (we have to handle the
   * shutdown request there, unfortunately).
   */
  fputs("uservd: builtin service shutdown: permission denied\n",stderr);
  _exit(-1);
}

static void serv_resetsignal(int signo) {
  struct sigaction sig;
  
  sig.sa_handler= SIG_DFL;
  sigemptyset(&sig.sa_mask);
  sig.sa_flags= 0;
  if (sigaction(signo,&sig,0)) serv_syscallfail("reset signal handler");
}

static const char *see_loginname(void) { return serviceuser; }
static const char *see_home(void) { return serviceuser_dir; }
static const char *see_shell(void) { return serviceuser_shell; }

static const char *see_service(void) { return service; }
static const char *see_c_cwd(void) { return cwd; }
static const char *see_c_loginname(void) { return loginname; }
static const char *see_c_uid(void) {
  static char buf[CHAR_BIT*sizeof(uid_t)/3+4];
  snyprintf(buf,sizeof(buf),"%lu",(unsigned long)request_mbuf.callinguid);
  return buf;
}

static const char *see_c_list(int n, const char *(*fn)(int i)) {
  int l, i;
  char *r;
  
  for (i=0, l=1; i<n; i++) l+= strlen(fn(i))+1;
  r= xmalloc(l); r[l-1]= '*';
  for (i=0, *r=0; i<n; i++) snytprintfcat(r,l,"%s ",fn(i));
  assert(!r[l-1] && r[l-2]==' ');
  r[l-2]= 0;
  return r;
}

static const char *seei_group(int i) {
  return calling_groups[i];
}
static const char *see_c_group(void) {
  return see_c_list(request_mbuf.ngids,seei_group);
}

static const char *seei_gid(int i) {
  static char buf[CHAR_BIT*sizeof(gid_t)/3+4];
  
  snyprintf(buf,sizeof(buf),"%ld",(long)calling_gids[i]);
  return buf;
}
static const char *see_c_gid(void) {
  return see_c_list(request_mbuf.ngids,seei_gid);
}

static const struct serv_envinfo {
  const char *name;
  const char *(*fn)(void);
} serv_envinfos[]= {
  { "USER",           see_loginname   },
  { "LOGNAME",        see_loginname   },
  { "HOME",           see_home        },
  { "SHELL",          see_shell       },
  { "PATH",           defaultpath     },
  { "USERV_SERVICE",  see_service     },
  { "USERV_CWD",      see_c_cwd       },
  { "USERV_USER",     see_c_loginname },
  { "USERV_UID",      see_c_uid       },
  { "USERV_GROUP",    see_c_group     },
  { "USERV_GID",      see_c_gid       },
  {  0                                }
};

void execservice(const int synchsocket[], int clientfd) {
  static const char *const setenvpfargs[]= {
    "/bin/sh",
    "-c",
    ". " SETENVIRONMENTPATH "; exec \"$@\"",
    "-",
    0
  };
  int fd, realfd, holdfd, newfd, r, envvarbufsize=0, targ, nargs, i, l, fdflags;
  char *envvarbuf=0;
  const char **args, *const *cpp;
  char *const *pp;
  char synchmsg;
  const struct serv_envinfo *sei;

  if (dup2(fdarray[2].realfd,2)<0) {
    static const char duperrmsg[]= "uservd(service): cannot dup2 for stderr\n";
    write(fdarray[2].realfd,duperrmsg,sizeof(duperrmsg)-1);
    _exit(-1);
  }
  serv_resetsignal(SIGPIPE);
  serv_resetsignal(SIGCHLD);

  if (close(synchsocket[0])) serv_syscallfail("close parent synch socket");

  if (setpgid(0,0)) serv_syscallfail("set process group");
  synchmsg= 'y';
  r= write(synchsocket[1],&synchmsg,1);
  if (r!=1) serv_syscallfail("write synch byte to parent");
  r= synchread(synchsocket[1],'g');
  if (r) serv_syscallfail("reach synch byte from parent");

  if (close(clientfd)) serv_syscallfail("close client socket fd");

  /* First we need to close the holding writing ends of the pipes
   * inherited from our parent: */
  for (fd=0; fd<fdarrayused; fd++) {
    if (fdarray[fd].holdfd == -1) continue;
    if (close(fdarray[fd].holdfd)) serv_syscallfail("close pipe hold fd");
    fdarray[fd].holdfd= -1;
  }
  /* Now we can reuse the .holdfd member of the fdarray entries. */

  /* We have to make all the fd's work.  It's rather a complicated
   * algorithm, unfortunately.  We remember in holdfd[fd] whether fd
   * is being used to hold a file descriptor we actually want for some
   * other real fd in the service program; holdfd[fd] contains the fd
   * we eventually want fd to be dup'd into, so that realfd[holdfd[fd]]==fd.
   * After setting up the holdfds we go through the fds in order of
   * eventual fd making sure that fd is the one we want it to be.  If the
   * holdfd tells us we're currently storing some other fd in there we
   * move it out of the way with dup and record its new location.
   */
  for (fd=0; fd<fdarrayused; fd++) {
    if (fdarray[fd].realfd < fdarrayused && fdarray[fd].realfd >= 0)
      fdarray[fdarray[fd].realfd].holdfd= fd;
  }
  for (fd=0; fd<fdarrayused; fd++) {
    realfd= fdarray[fd].realfd;
    if (realfd == -1) continue;
    holdfd= fdarray[fd].holdfd;
    if (holdfd == fd) {
      assert(realfd == fd);
      fdarray[fd].holdfd= -1;
      continue;
    } else if (holdfd != -1) {
      assert(fdarray[holdfd].realfd == fd);
      newfd= dup(fd); if (newfd<0) serv_syscallfail("dup out of the way");
      fdarray[holdfd].realfd= newfd;
      if (newfd<fdarrayused) fdarray[newfd].holdfd= holdfd;
      fdarray[fd].holdfd= -1;
    }
    if (dup2(fdarray[fd].realfd,fd)<0) serv_syscallfail("dup2 set up fd");
    if (close(fdarray[fd].realfd)) serv_syscallfail("close old fd");
    fdflags= fcntl(fd,F_GETFD); if (fdflags<0) serv_syscallfail("get fd flags");
    if (fcntl(fd,F_SETFD,fdflags&~FD_CLOEXEC)==-1) serv_syscallfail("set no-close-on-exec on fd");
    fdarray[fd].realfd= fd;
  }

  for (sei= serv_envinfos; sei->name; sei++)
    if (setenv(sei->name,sei->fn(),1)) serv_syscallfail("setenv standard");
  for (i=0; i<request_mbuf.nvars; i++) {
    l= strlen(defvararray[i].key)+9;
    if (l>envvarbufsize) { envvarbufsize= l; envvarbuf= xrealloc(envvarbuf,l); }
    snyprintf(envvarbuf,l,"USERV_U_%s",defvararray[i].key);
    if (setenv(envvarbuf,defvararray[i].value,1)) serv_syscallfail("setenv defvar");
  }

  nargs= 0;
  if (setenvironment) for (cpp= setenvpfargs; *cpp; cpp++) nargs++;
  nargs++;
  if (execargs) for (pp= execargs; *pp; pp++) nargs++;
  if (!suppressargs) nargs+= request_mbuf.nargs;
  args= xmalloc(sizeof(char*)*(nargs+1));
  targ= 0;
  if (setenvironment) for (cpp= setenvpfargs; *cpp; cpp++) args[targ++]= *cpp;
  args[targ++]= execpath;
  if (execargs) for (pp= execargs; *pp; pp++) args[targ++]= *pp;
  if (!suppressargs) for (i=0; i<request_mbuf.nargs; i++) args[targ++]= argarray[i];
  args[targ]= 0;

  if (execbuiltin)
    execbuiltin(args);
  else
    execv(args[0],(char* const*)args);

  serv_syscallfail("exec service program");
  _exit(-1);
}