File: process.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 (784 lines) | stat: -rw-r--r-- 23,504 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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
/*
 * userv - process.c
 * daemon code to process one request (is parent of service 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/>.
 */

/*
 * We do some horrible asynchronous stuff with signals.
 *
 * The following objects &c. are used in signal handlers and so
 * must be protected by calls to blocksignals if they are used in
 * the main program:
 *  the syslog() family of calls, and the associated
 *    syslogopenfacility variable
 *  swfile (stdio stream)
 *
 * The following objects are used in the main program unprotected
 * and so must not be used in signal handlers:
 *  srfile
 *
 * child and childtokill are used for communication between the
 * main thread and the signal handlers; none of the signal handlers
 * return so errno is OK too.
 */

#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <syslog.h>
#include <pwd.h>
#include <grp.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>

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

/* NB: defaults for the execution state are not set here, but in
 * the RESET_CONFIGURATION #define in daemon.h. */
struct request_msg request_mbuf;
struct keyvaluepair *defvararray;
struct fdstate *fdarray;
int fdarraysize, fdarrayused;
int restfdwantstate= tokv_word_rejectfd, restfdwantrw;
int service_ngids;
char **argarray;
char *serviceuser, *service, *loginname, *cwd;
char *overridedata, *userrcfile;
char *serviceuser_dir, *serviceuser_shell, *callinguser_shell;
gid_t *calling_gids, *service_gids;
uid_t serviceuser_uid=-1;
const char **calling_groups, **service_groups;
char *execpath, **execargs;
int execute;
int setenvironment, suppressargs, disconnecthup;
builtinserviceexec_fnt *execbuiltin;
int syslogopenfacility=-1;

static FILE *swfile, *srfile;
static pid_t child=-1, childtokill=-1;
static pid_t mypid;

/* Function shared with servexec.c: */

int synchread(int fd, int ch) {
  char synchmsg;
  int r;
  
  for (;;) {
    r= read(fd,&synchmsg,1);
    if (r==1) break;
    if (r==0) { errno= ECONNRESET; return -1; }
    assert(r<0);
    if (errno!=EINTR) return -1;
  };
  assert(synchmsg==ch);
  return 0;
}

const char *defaultpath(void) {
  return serviceuser_uid ? DEFAULTPATH_USER : DEFAULTPATH_ROOT;
}

/* General-purpose functions; these do nothing special about signals */

static void blocksignals(void) {
  int r;
  sigset_t set;

  sigemptyset(&set);
  sigaddset(&set,SIGCHLD);
  sigaddset(&set,SIGPIPE);
  r= sigprocmask(SIG_BLOCK,&set,0); assert(!r);
}

static void xfwriteerror(void) {
  if (errno != EPIPE) syscallerror("writing to client");
  blocksignals();
  ensurelogopen(USERVD_LOGFACILITY);
  syslog(LOG_INFO,"client went away (broken pipe)");
  disconnect(8);
}

static void xfwrite(const void *p, size_t sz, FILE *file) {
  size_t nr;
  nr= fwrite(p,1,sz,file);
  if (nr != sz) xfwriteerror();
}

static void xfflush(FILE *file) {
  if (fflush(file)) xfwriteerror();
}

/* Functions which may be called only from the main thread.  These may
 * use main-thread objects and must block signals before using signal
 * handler objects.
 */

static void xfread(void *p, size_t sz) {
  size_t nr;
  nr= working_fread(p,sz,srfile); if (nr == sz) return;
  if (ferror(srfile)) syscallerror("reading from client");
  blocksignals();
  assert(feof(srfile));
  syslog(LOG_INFO,"client went away (unexpected EOF)");
  swfile= 0;
  disconnect(8);
}

static char *xfreadsetstring(int l) {
  char *s;
  assert(l<=MAX_GENERAL_STRING);
  s= xmalloc(l+1);
  xfread(s,sizeof(*s)*l);
  s[l]= 0;
  return s;
}

static char *xfreadstring(void) {
  int l;
  xfread(&l,sizeof(l));
  return xfreadsetstring(l);
}

static void getevent(struct event_msg *event_r) {
  int fd;
  
  for (;;) {
    xfread(event_r,sizeof(struct event_msg));
    switch (event_r->type) {
    case et_closereadfd:
      fd= event_r->data.closereadfd.fd;
      if (fd >= fdarrayused) {
	blocksignals();
	syslog(LOG_ERR,"client sent bad file descriptor %d to close (max %d)",
	       fd,fdarrayused-1);
	disconnect(20);
      }
      if (fdarray[fd].holdfd!=-1) {
	if (close(fdarray[fd].holdfd)) syscallerror("cannot close holding fd");
	fdarray[fd].holdfd= -1;
      }
      break;
    case et_disconnect:
      blocksignals();
      syslog(LOG_INFO,"client disconnected");
      disconnect(4);
    default:
      return;
    }
  }
}

/* Functions which may be called either from signal handlers or from
 * the main thread.  They block signals in case they are on the main
 * thread, and may only use signal handler objects.  None of them
 * return.  If they did they'd have to restore the signal mask.
 */

void miscerror(const char *what) {
  blocksignals();
  syslog(LOG_ERR,"failure: %s",what);
  disconnect(16);
}

void syscallerror(const char *what) {
  int e;

  e= errno;
  blocksignals();
  syslog(LOG_ERR,"system call failure: %s: %s",what,strerror(e));
  disconnect(16);
}

/* Functions which may be called from signal handlers.  These
 * may use signal-handler objects.  The main program may only
 * call them with signals blocked, and they may not use any
 * main-thread objects.
 */

void ensurelogopen(int wantfacility) {
  if (syslogopenfacility==wantfacility) return;
  if (syslogopenfacility!=-1) closelog();
  openlog(USERVD_LOGIDENT,LOG_NDELAY|LOG_PID,wantfacility);
  syslogopenfacility= wantfacility;
}

void NONRETURNING disconnect(int exitstatus) {
  /* This function can sometimes indirectly call itself (eg,
   * xfwrite, syscallerror can cause it to be called).  So, all
   * the global variables indicating need for action are reset
   * before the action is taken so that if it fails it isn't
   * attempted again.
   */
  struct progress_msg progress_mbuf;
  FILE *swfilereal;
  pid_t orgtokill;
  int r;
  
  if (childtokill!=-1 && disconnecthup) {
    orgtokill= childtokill;
    childtokill= -1;
    if (disconnecthup) {
      r= kill(-orgtokill,SIGHUP);
      if (r && errno!=EPERM && errno!=ESRCH)
	syscallerror("sending SIGHUP to service process group");
    }
    child= -1;
  }
  if (swfile) {
    swfilereal= swfile;
    swfile= 0;
    memset(&progress_mbuf,0,sizeof(progress_mbuf));
    progress_mbuf.magic= PROGRESS_MAGIC;
    progress_mbuf.type= pt_failed;
    xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfilereal);
    xfflush(swfilereal);
  }

  _exit(exitstatus);
}

static void reporttermination(int status) {
  struct progress_msg progress_mbuf;
  
  memset(&progress_mbuf,0,sizeof(progress_mbuf));
  progress_mbuf.magic= PROGRESS_MAGIC;
  progress_mbuf.type= pt_terminated;
  progress_mbuf.data.terminated.status= status;
  xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile);
  xfflush(swfile);
}

static void NONRETURNING sighandler_chld(int ignored) {
  int status;
  pid_t returned;

  returned= wait3(&status,WNOHANG,0);
  if (returned==-1) syscallerror("wait for child failed");
  if (!returned) syscallerror("spurious sigchld");
  if (returned!=child) syscallerror("spurious child process");
  child= childtokill= -1;

  reporttermination(status);
  syslog(LOG_INFO,"service completed (status %d %d)",(status>>8)&0x0ff,status&0x0ff);
  _exit(0);
}

/* Functions which are called only during setup, before
 * the signal asynchronicity starts.  They can do anything they like.
 */

void ensurefdarray(int fd) {
  if (fd < fdarrayused) return;
  if (fd >= fdarraysize) {
    fdarraysize= ((fd+2)<<1);
    fdarray= xrealloc(fdarray,sizeof(struct fdstate)*fdarraysize);
  }
  while (fd >= fdarrayused) {
    fdarray[fdarrayused].iswrite= -1;
    fdarray[fdarrayused].realfd= -1;
    fdarray[fdarrayused].holdfd= -1;
    fdarray[fdarrayused].wantstate= restfdwantstate;
    fdarray[fdarrayused].wantrw= restfdwantrw;
    fdarrayused++;
  }
}

static void NONRETURNING generalfailure(const char *prefix, int reserveerrno,
					int errnoval, const char *fmt, va_list al) {
  char errmsg[MAX_ERRMSG_LEN];

  if (prefix) {
    strnycpy(errmsg,prefix,sizeof(errmsg));
    strnytcat(errmsg,": ",sizeof(errmsg));
  } else {
    errmsg[0]= 0;
  }
  vsnytprintfcat(errmsg,sizeof(errmsg)-reserveerrno,fmt,al);
  if (reserveerrno) {
    strnytcat(errmsg,": ",sizeof(errmsg));
    strnytcat(errmsg,strerror(errnoval),sizeof(errmsg));
  }
  senderrmsgstderr(errmsg);
  syslog(LOG_INFO,"service failed (%s)",errmsg);
  disconnect(12);
}

static void NONRETURNPRINTFFORMAT(1,2) failure(const char *fmt, ...) {
  va_list al;

  va_start(al,fmt);
  generalfailure(0,0,0,fmt,al);
}  

static void NONRETURNPRINTFFORMAT(1,2) syscallfailure(const char *fmt, ...) {
  va_list al;
  int e;

  e= errno;
  va_start(al,fmt);
  generalfailure("system call failed",ERRMSG_RESERVE_ERRNO,e,fmt,al);
}

void senderrmsgstderr(const char *errmsg) {
  struct progress_msg progress_mbuf;
  unsigned long ul;
  int l;

  l= strlen(errmsg);
  memset(&progress_mbuf,0,sizeof(progress_mbuf));
  progress_mbuf.magic= PROGRESS_MAGIC;
  progress_mbuf.type= pt_errmsg;
  progress_mbuf.data.errmsg.messagelen= l;
  xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile);
  xfwrite(errmsg,l,swfile);
  ul= PROGRESS_ERRMSG_END_MAGIC;
  xfwrite(&ul,sizeof(ul),swfile);
  xfflush(swfile);
}

/* The per-request main program and its subfunctions. */

static void setup_comms(int sfd) {
  static char swbuf[BUFSIZ];
  static char srbuf[BUFSIZ];

  struct sigaction sig;
  
  ensurelogopen(USERVD_LOGFACILITY);
  syslog(LOG_DEBUG,"call connected");

  mypid= getpid(); if (mypid == -1) syscallerror("getpid");

  sig.sa_handler= SIG_IGN;
  sigemptyset(&sig.sa_mask);
  sig.sa_flags= 0;
  if (sigaction(SIGPIPE,&sig,0)) syscallerror("cannot ignore sigpipe");

  srfile= fdopen(sfd,"r");
  if (!srfile) syscallerror("turn socket fd into reading FILE*");
  if (setvbuf(srfile,srbuf,_IOFBF,sizeof(srbuf)))
    syscallerror("set buffering on socket reads");

  swfile= fdopen(sfd,"w");
  if (!swfile) syscallerror("turn socket fd into writing FILE*");
  if (setvbuf(swfile,swbuf,_IOFBF,sizeof(swbuf)))
    syscallerror("set buffering on socket writes");
}

static void send_opening(void) {
  struct opening_msg opening_mbuf;

  memset(&opening_mbuf,0,sizeof(opening_mbuf));
  opening_mbuf.magic= OPENING_MAGIC;
  memcpy(opening_mbuf.protocolchecksumversion,protocolchecksumversion,PCSUMSIZE);
  opening_mbuf.overlordpid= overlordpid;
  opening_mbuf.serverpid= mypid;
  xfwrite(&opening_mbuf,sizeof(opening_mbuf),swfile);
  xfflush(swfile);
}

static void receive_request(void) {
  int i, fd;
  unsigned long ul;

  xfread(&request_mbuf,sizeof(request_mbuf));
  serviceuser= xfreadsetstring(request_mbuf.serviceuserlen);
  service= xfreadsetstring(request_mbuf.servicelen);
  assert(request_mbuf.spoofed==0 || request_mbuf.spoofed==1);
  loginname= xfreadsetstring(request_mbuf.loginnamelen);
  cwd= xfreadsetstring(request_mbuf.cwdlen);
  if (request_mbuf.overridelen >= 0) {
    assert(request_mbuf.overridelen <= MAX_OVERRIDE_LEN);
    overridedata= xfreadsetstring(request_mbuf.overridelen);
  } else {
    assert(request_mbuf.overridelen == -1);
    overridedata= 0;
  }
  assert(request_mbuf.ngids <= MAX_GIDS);
  calling_gids= xmalloc(sizeof(gid_t)*request_mbuf.ngids);
  xfread(calling_gids,sizeof(gid_t)*request_mbuf.ngids);

  fdarraysize= 4; fdarray= xmalloc(sizeof(struct fdstate)*fdarraysize);
  fdarrayused= 1; fdarray[0].iswrite= -1;
  fdarray[0].wantstate= tokv_word_rejectfd;
  assert(request_mbuf.nreadfds+request_mbuf.nwritefds <= MAX_ALLOW_FD+1);
  for (i=0; i<request_mbuf.nreadfds+request_mbuf.nwritefds; i++) {
    xfread(&fd,sizeof(int));
    assert(fd <= MAX_ALLOW_FD);
    ensurefdarray(fd);
    assert(fdarray[fd].iswrite == -1);
    fdarray[fd].iswrite= (i>=request_mbuf.nreadfds);
  }
  /* fdarray[].iswrite now set; rest is still blank
   * (ie want reject read, no realfd holdfd). */

  assert(request_mbuf.nargs <= MAX_ARGSDEFVAR);
  argarray= xmalloc(sizeof(char*)*(request_mbuf.nargs));
  for (i=0; i<request_mbuf.nargs; i++) argarray[i]= xfreadstring();
  assert(request_mbuf.nvars <= MAX_ARGSDEFVAR);
  defvararray= xmalloc(sizeof(struct keyvaluepair)*request_mbuf.nvars);
  for (i=0; i<request_mbuf.nvars; i++) {
    defvararray[i].key= xfreadstring();
    assert(defvararray[i].key[0]);
    defvararray[i].value= xfreadstring();
  }
  xfread(&ul,sizeof(ul));
  assert(ul == REQUEST_END_MAGIC);
}

static void establish_pipes(void) {
  int fd, tempfd;
  char pipepathbuf[PIPEMAXLEN+2];
  
  for (fd=0; fd<fdarrayused; fd++) {
    if (fdarray[fd].iswrite == -1) continue;
    pipepathbuf[sizeof(pipepathbuf)-2]= 0;
    snyprintf(pipepathbuf,sizeof(pipepathbuf),PIPEFORMAT,
	      (unsigned long)request_mbuf.clientpid,(unsigned long)mypid,fd);
    assert(!pipepathbuf[sizeof(pipepathbuf)-2]);
    tempfd= open(pipepathbuf,O_RDWR);
    if (tempfd<0) syscallerror("prelim open pipe");
    if (fdarray[fd].iswrite) {
      fdarray[fd].holdfd= -1;
      fdarray[fd].realfd= open(pipepathbuf, O_WRONLY);
    } else {
      fdarray[fd].holdfd= open(pipepathbuf, O_WRONLY);
      if (fdarray[fd].holdfd<0) syscallerror("hold open pipe");
      fdarray[fd].realfd= open(pipepathbuf, O_RDONLY);
    }
    if (fdarray[fd].realfd<0) syscallerror("real open pipe");
    if (unlink(pipepathbuf)) syscallerror("unlink pipe");
    if (close(tempfd)) syscallerror("close prelim fd onto pipe");
  }
  /* Now fdarray[].realfd is pipe end for service in case service
   * wants it.  If it's an input pipe, then .holdfd is the other
   * (writing) end of the pipe - we keep it around so that the service
   * doesn't get an apparently clean EOF if the caller disappears (eg
   * due to a file read error) or the like (ie so that on disconnect
   * we can guarantee to send the service SIGHUP before it gets EOF on
   * the input fd).  Otherwise, .holdfd=-1.
   */
}

static void groupnames(int ngids, gid_t *gids, const char ***names_r) {
  const char **names;
  struct group *gr;
  int i;
  
  names= xmalloc(sizeof(char*)*ngids);
  for (i=0; i<ngids; i++) {
    gr= getgrgid(gids[i]);
    if (!gr) miscerror("get group entry");
    names[i]= xstrsave(gr->gr_name);
  }
  *names_r= names;
}
  
static void lookup_uidsgids(void) {
  struct passwd *pw;

  pw= getpwnam(loginname);
  if (!pw) miscerror("look up calling user");
  assert(!strcmp(pw->pw_name,loginname));
  callinguser_shell= xstrsave(pw->pw_shell);

  pw= getpwnam(serviceuser);
  if (!pw) miscerror("look up service user");
  assert(!strcmp(pw->pw_name,serviceuser));
  serviceuser_dir= xstrsave(nondebug_serviceuserdir(pw->pw_dir));
  serviceuser_shell= xstrsave(pw->pw_shell);
  serviceuser_uid= pw->pw_uid;
  
  if (setregid(pw->pw_gid,pw->pw_gid)) syscallerror("setregid 1");
  if (initgroups(pw->pw_name,pw->pw_gid)) syscallerror("initgroups");
  if (setreuid(pw->pw_uid,pw->pw_uid)) syscallerror("setreuid 1");
  if (setreuid(pw->pw_uid,pw->pw_uid)) syscallerror("setreuid 2");
  if (pw->pw_uid) {
    if (!setreuid(pw->pw_uid,0)) miscerror("setreuid 3 unexpectedly succeeded");
    if (errno != EPERM) syscallerror("setreuid 3 failed in unexpected way");
  }
  if (setregid(pw->pw_gid,pw->pw_gid)) syscallerror("setregid 2");

  service_ngids= getgroups(0,0); if (service_ngids == -1) syscallerror("getgroups(0,0)");
  if (service_ngids > MAX_GIDS) miscerror("service user is in far too many groups");
  service_gids= xmalloc(sizeof(gid_t)*(service_ngids+1));
  service_gids[0]= pw->pw_gid;
  if (getgroups(service_ngids,service_gids+1) != service_ngids)
    syscallerror("getgroups(size,list)");

  groupnames(request_mbuf.ngids,calling_gids,&calling_groups);
  groupnames(service_ngids,service_gids,&service_groups);
}

static void findinpath(char *program) {
  char *part, *exectry;
  const char *string, *delim, *nextstring;
  struct stat stab;
  int r, partsize;
  
  if (strchr(program,'/')) {
    r= stat(program,&stab);
    if (r) syscallfailure("failed check for program (containing slash) `%s'",program);
    execpath= program;
  } else {
    string= getenv("PATH");
    if (!string) string= defaultpath();
    while (string) {
      delim= strchr(string,':');
      if (delim) {
	if (delim-string > MAX_GENERAL_STRING)
	  failure("execute-from-path, but PATH component too long");
	partsize= delim-string;
	nextstring= delim+1;
      } else {
	partsize= strlen(string);
	nextstring= 0;
      }
      part= xstrsubsave(string,partsize);
      exectry= part[0] ? xstrcat3save(part,"/",program) : xstrsave(program);
      free(part);
      r= stat(exectry,&stab);
      if (!r) { execpath= exectry; break; }
      free(exectry);
      string= nextstring;
    }
    if (!execpath) failure("program `%s' not found on default PATH",program);
  }
}
  
static void check_find_executable(void) {
  struct stat stab;
  int r;

  switch (execute) {
  case tokv_word_reject:
    failure("request rejected");
  case tokv_word_execute:
    findinpath(execpath);
    break;
  case tokv_word_executefromdirectory:
    r= stat(execpath,&stab);
    if (r) syscallfailure("checking for executable in directory, `%s'",execpath);
    break;
  case tokv_word_executebuiltin:
    break;
  case tokv_word_executefrompath:
    findinpath(service);
    break;
  default:
    abort();
  }
}

static void makenonexistentfd(int fd) {
  if (fdarray[fd].realfd == -1) {
    assert(fdarray[fd].holdfd == -1);
  } else {
    if (close(fdarray[fd].realfd))
      syscallfailure("close unwanted file descriptor %d",fd);
    fdarray[fd].realfd= -1;
  
    if (fdarray[fd].holdfd != -1) {
      if (close(fdarray[fd].holdfd))
	syscallfailure("close unwanted hold descriptor for %d",fd);
      fdarray[fd].holdfd= -1;
    }
  }
}

static void makenullfd(int fd) {
  fdarray[fd].realfd= open("/dev/null",
			   fdarray[fd].wantrw == tokv_word_read ? O_RDONLY :
			   fdarray[fd].wantrw == tokv_word_write ? O_WRONLY :
			   0);
  if (fdarray[fd].realfd<0)
    syscallfailure("cannot open /dev/null for null or allowed, unprovided fd");
}

static void check_fds(void) {
  int fd;
  
  assert(fdarrayused>=2);
  if (!(fdarray[2].wantstate == tokv_word_requirefd ||
	fdarray[2].wantstate == tokv_word_allowfd) ||
      fdarray[2].wantrw != tokv_word_write)
    failure("must have stderr (fd 2), but file descriptor setup in "
	    "configuration does not have it or not for writing");

  for (fd=0; fd<fdarrayused; fd++) {
    switch (fdarray[fd].wantstate) {
    case tokv_word_rejectfd:
      if (fdarray[fd].realfd != -1)
	failure("file descriptor %d provided but rejected",fd);
      break;
    case tokv_word_ignorefd:
      makenonexistentfd(fd);
      break;
    case tokv_word_nullfd:
      makenonexistentfd(fd);
      makenullfd(fd);
      break;
    case tokv_word_requirefd:
      if (fdarray[fd].realfd == -1)
	failure("file descriptor %d required but not provided",fd);
      /* fall through */
    case tokv_word_allowfd:
      if (fdarray[fd].realfd == -1) {
	assert(fdarray[fd].holdfd == -1);
	makenullfd(fd);
      } else {
	if (fdarray[fd].iswrite) {
	  if (fdarray[fd].wantrw == tokv_word_read)
	    failure("file descriptor %d provided write, wanted read",fd);
	} else {
	  if (fdarray[fd].wantrw == tokv_word_write)
	    failure("file descriptor %d provided read, wanted write",fd);
	}
      }
    }
  }
  /* Now fdarray[].realfd is exactly what service wants: pipe end or
   * /dev/null or -1.  If .realfd is not -1 then .holdfd may be the fd
   * for the writing end of the corresponding pipe.
   */
}

static void send_progress_ok(void) {
  struct progress_msg progress_mbuf;

  memset(&progress_mbuf,0,sizeof(progress_mbuf));
  progress_mbuf.magic= PROGRESS_MAGIC;
  progress_mbuf.type= pt_ok;
  xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile);
  xfflush(swfile);
}

static void fork_service_synch(void) {
  pid_t newchild;
  struct sigaction sig;
  int r, synchsocket[2];
  char synchmsg;

  r= socketpair(AF_UNIX,SOCK_STREAM,0,synchsocket);
  if (r) syscallerror("cannot create socket for synch");

  /* Danger here.  Firstly, we start handling signals asynchronously.
   * Secondly after we fork the service we want it to put
   * itself in a separate process group so that we can kill it and all
   * its children - but, we mustn't kill the whole pgrp before it has
   * done that (or we kill ourselves) and it mustn't fork until it
   * knows that we are going to kill it the right way ...
   */
  sig.sa_handler= sighandler_chld;
  sigemptyset(&sig.sa_mask);
  sigaddset(&sig.sa_mask,SIGCHLD);
  sig.sa_flags= 0;
  if (sigaction(SIGCHLD,&sig,0)) syscallerror("cannot set sigchld handler");

  newchild= fork();
  if (newchild == -1) syscallerror("cannot fork to invoke service");
  if (!newchild) execservice(synchsocket,fileno(swfile));
  childtokill= child= newchild;

  if (close(synchsocket[1])) syscallerror("cannot close other end of synch socket");

  r= synchread(synchsocket[0],'y');
  if (r) syscallerror("read synch byte from child");

  childtokill= -child;

  synchmsg= 'g';
  r= write(synchsocket[0],&synchmsg,1);
  if (r!=1) syscallerror("write synch byte to child");

  if (close(synchsocket[0])) syscallerror("cannot close my end of synch socket");
}

void servicerequest(int sfd) {
  struct event_msg event_mbuf;
  int r;

  setup_comms(sfd);
  send_opening();
  receive_request();
  if (request_mbuf.clientpid == (pid_t)-1) _exit(2);
  establish_pipes();
  lookup_uidsgids();
  debug_dumprequest(mypid);
  syslog(LOG_INFO,"%s %s -> %s %c %s",
	 request_mbuf.spoofed ? "spoof" : "user",
	 loginname, serviceuser, overridedata?'!':':', service);

  if (overridedata)
    r= parse_string(TOPLEVEL_OVERRIDDEN_CONFIGURATION,
		    "<builtin toplevel override configuration>",1);
  else
    r= parse_string(TOPLEVEL_CONFIGURATION,
		    "<builtin toplevel configuration>",1);
  
  ensurelogopen(USERVD_LOGFACILITY);
  if (r == tokv_error) failure("error encountered while parsing configuration");
  assert(r == tokv_quit);

  debug_dumpexecsettings();

  check_find_executable();
  check_fds();
  send_progress_ok();

  getevent(&event_mbuf);
  assert(event_mbuf.type == et_confirm);

  if (execbuiltin == bisexec_shutdown && !serviceuser_uid) {
    /* The check for the uid is just so we can give a nice
     * error message (in the actual code for bisexec_shutdown).
     * If this is spoofed somehow then the unlink() will simply fail.
     */
    r= unlink(RENDEZVOUSPATH);
    if (r) syscallfailure("remove rendezvous socket %s",RENDEZVOUSPATH);
    syslog(LOG_NOTICE,"arranging for termination, due to client request");
    reporttermination(0);
    _exit(10);
  }

  fork_service_synch();
  
  getevent(&event_mbuf);
  abort();
}