File: client.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 (1361 lines) | stat: -rw-r--r-- 43,861 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
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
/*
 * userv - client.c
 * client code
 *
 * 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/>.
 */

/*
 * Here too, 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:
 *  stderr
 *  swfile
 *
 * The following objects are used in the main program unprotected
 * and so must not be used in signal handlers:
 *  srfile
 *  fdsetup[].copyfd
 *  fdsetup[].pipefd
 *
 * The following objects/functions are not modified/called after the
 * asynchronicity starts:
 *  malloc
 *  fdsetupsize
 *  fdsetup[].mods
 *  fdsetup[].filename
 *  fdsetup[].oflags
 *  results of argument parsing
 *
 * systemerror, swfile, fdsetup[].catpid and fdsetup[].killed are used
 * for communication between the main thread and the signal handlers.
 *
 * All the signal handlers save errno so that is OK too.
 */

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

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

enum fdmodifiervalues {
  fdm_read=       00001,
  fdm_write=      00002,
  fdm_create=     00004,
  fdm_exclusive=  00010,
  fdm_truncate=   00020,
  fdm_append=     00040,
  fdm_sync=       00100,
  fdm_fd=         00200,
  fdm_wait=       01000,
  fdm_nowait=     02000,
  fdm_close=      04000,
};

struct fdmodifierinfo {
  const char *string;
  int implies;
  int conflicts;
  int oflags;
};

struct fdsetupstate {
  const char *filename; /* non-null iff this fd has been specified */
  int copyfd; /* fd to copy, -1 unless mods & fdm_fd */
  int mods, oflags, pipefd, killed; /* 0,0,-1,0 unless otherwise set */
  pid_t catpid; /* -1 indicates no cat process */
};

enum signalsexitspecials { se_number=-100, se_numbernocore, se_highbit, se_stdout };
enum overridetypes { ot_none, ot_string, ot_file, ot_builtin };

struct constkeyvaluepair { const char *key, *value; };

/* Variables from command-line arguments */
static const char *serviceuser;
static uid_t serviceuid;
static struct fdsetupstate *fdsetup;
static int fdsetupsize;
static struct constkeyvaluepair *defvararray;
static int defvaravail, defvarused;
static unsigned long timeout;
static int signalsexit=254;
static int sigpipeok, hidecwd;
static int overridetype= ot_none;
static const char *overridevalue, *spoofuser=0;

/* Other state variables */
static FILE *srfile, *swfile;
static pid_t mypid;
static uid_t myuid, spoofuid;
static gid_t mygid, spoofgid, *gidarray;
static int ngids;
static struct opening_msg opening_mbuf;
static const char *loginname;
static char *cwdbuf;
static size_t cwdbufsize;
static char *ovbuf;
static int ovused, systemerror, socketfd;

static void blocksignals(int how) {
  sigset_t set;
  static const char blockerrmsg[]= "userv: failed to [un]block signals: ";
  const char *str;

  sigemptyset(&set);
  sigaddset(&set,SIGCHLD);
  sigaddset(&set,SIGALRM);
  if (sigprocmask(how,&set,0)) {
    str= strerror(errno);
    write(2,blockerrmsg,sizeof(blockerrmsg)-1);
    write(2,str,strlen(str));
    write(2,"\n",1);
    exit(-1);
  }
}

/* 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.
 */

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

  blocksignals(SIG_BLOCK);
  va_start(al,fmt);
  fprintf(stderr,"userv: failure: ");
  vfprintf(stderr,fmt,al);
  fprintf(stderr,"\n");
  exit(-1);
}

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

  e= errno;
  blocksignals(SIG_BLOCK);
  va_start(al,fmt);
  fprintf(stderr,"userv: system call failure: ");
  vfprintf(stderr,fmt,al);
  fprintf(stderr,": %s\n",strerror(e));
  exit(-1);
}

void syscallerror(const char *what) {
  fsyscallerror("%s",what);
}

static void NONRETURNING protoreaderror(FILE *file, const char *where) {
  int e;

  e= errno;
  blocksignals(SIG_BLOCK);
  if (ferror(file)) {
    fprintf(stderr,"userv: failure: read error %s: %s\n",where,strerror(e));
  } else {
    assert(feof(file));
    fprintf(stderr,"userv: internal failure: EOF from server %s\n",where);
  }
  exit(-1);
}

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

  blocksignals(SIG_BLOCK);
  va_start(al,fmt);
  fprintf(stderr,"userv: internal failure: protocol error: ");
  vfprintf(stderr,fmt,al);
  fprintf(stderr,"\n");
  exit(-1);
}

/*
 * General-purpose functions; these do nothing special about signals,
 * except that they can call error-handlers which may block them
 * to print error messages.
 */

static void xfread(void *p, size_t sz, FILE *file) {
  size_t nr;
  nr= working_fread(p,sz,file);
  if (nr != sz) protoreaderror(file,"in data");
}

static void xfwrite(const void *p, size_t sz, FILE *file) {
  size_t nr;
  nr= fwrite(p,1,sz,file); if (nr == sz) return;
  syscallerror("writing to server");
}

static void xfflush(FILE *file) {
  if (fflush(file)) syscallerror("flush server socket");
}

/* 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.
 */

#ifdef DEBUG
static void priv_suspend(void) { }
static void priv_resume(void) { }
static void priv_permanentlyrevokesuspended(void) { }
#else
static void priv_suspend(void) {
  if (setreuid(0,myuid) != 0) syscallerror("suspend root setreuid(0,myuid)");
}
static void priv_resume(void) {
  if (setreuid(myuid,0) != 0) syscallerror("resume root setreuid(myuid,0)");
}
static void priv_permanentlyrevokesuspended(void) {
  if (setreuid(myuid,myuid) != 0) syscallerror("revoke root setreuid(myuid,myuid)");
  if (setreuid(myuid,myuid) != 0) syscallerror("rerevoke root setreuid(myuid,myuid)");
  if (myuid) {
    if (!setreuid(myuid,0)) miscerror("revoked root but setreuid(0,0) succeeded !");
    if (errno != EPERM) syscallerror("revoked and setreuid(myuid,0) unexpected error");
  }
}
#endif

static void checkmagic(unsigned long was, unsigned long should, const char *when) {
  if (was != should)
    protoerror("magic number %s was %08lx, expected %08lx",when,was,should);
}

static void getprogress(struct progress_msg *progress_r, FILE *file) {
  int i, c;
  unsigned long ul;

  for (;;) {
    xfread(progress_r,sizeof(struct progress_msg),file);
    checkmagic(progress_r->magic,PROGRESS_MAGIC,"in progress message");
    switch (progress_r->type) {
    case pt_failed:
      blocksignals(SIG_BLOCK);
      fputs("userv: uservd reports that service failed\n",stderr);
      exit(-1);
    case pt_errmsg:
      blocksignals(SIG_BLOCK);
      fputs("uservd: ",stderr);
      if (progress_r->data.errmsg.messagelen>MAX_ERRMSG_STRING)
	protoerror("stderr message length %d is far too long",
		   progress_r->data.errmsg.messagelen);
      for (i=0; i<progress_r->data.errmsg.messagelen; i++) {
	c= working_getc(file);
	if (c==EOF) protoreaderror(file,"in error message");
	if (ISCHAR(isprint,c)) putc(c,stderr);
	else fprintf(stderr,"\\x%02x",(unsigned char)c);
      }
      putc('\n',stderr);
      if (ferror(stderr)) syscallerror("printing error message");
      xfread(&ul,sizeof(ul),file);
      checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message");
      blocksignals(SIG_UNBLOCK);
      break;
    default:
      return;
    }
  }
}

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

/* This includes xmalloc and xrealloc from both.c */

static void xfwritestring(const char *s, FILE *file) {
  int l;
  l= strlen(s);
  assert(l<=MAX_GENERAL_STRING);
  xfwrite(&l,sizeof(l),file);
  xfwrite(s,sizeof(*s)*l,file);
}

static void xfwritefds(int modifier, int expected, FILE *file) {
  int i, fdcount;

  for (i=0, fdcount=0; i<fdsetupsize; i++) {
    if (!(fdsetup[i].filename && (fdsetup[i].mods & modifier)))
      continue;
    xfwrite(&i,sizeof(int),file); fdcount++;
  }
  assert(fdcount == expected);
}

/* 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.
 */

static void disconnect(void) /* DOES return, unlike in daemon */ {
  struct event_msg event_mbuf;
  int r;

  if (swfile) {
    memset(&event_mbuf,0,sizeof(event_mbuf));
    event_mbuf.magic= EVENT_MAGIC;
    event_mbuf.type= et_disconnect;
    r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
    if ((r != sizeof(event_mbuf) || fflush(swfile)) && errno != EPIPE)
      syscallerror("write to server when disconnecting");
  }
  systemerror= 1;
}

static void sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
  int es;
  es= errno;
  fputs("userv: timeout\n",stderr);
  disconnect();
  errno= es;
}

static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
  struct event_msg event_mbuf;
  pid_t child;
  int status, fd, r, es;

  es= errno;
  for (;;) {
    child= wait3(&status,WNOHANG,0);
    if (child == 0 || (child == -1 && errno == ECHILD)) break;
    if (child == -1) syscallerror("wait for child process (in sigchld handler)");
    for (fd=0; fd<fdsetupsize && fdsetup[fd].catpid != child; fd++);
    if (fd>=fdsetupsize) continue; /* perhaps the caller gave us children */
    if ((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
	(WIFSIGNALED(status) && WTERMSIG(status)==SIGPIPE) ||
	(fdsetup[fd].killed && WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL)) {
      if (swfile && fdsetup[fd].mods & fdm_read) {
	memset(&event_mbuf,0,sizeof(event_mbuf));
	event_mbuf.magic= EVENT_MAGIC;
	event_mbuf.type= et_closereadfd;
        event_mbuf.data.closereadfd.fd= fd;
	r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
	if (r != sizeof(event_mbuf) || fflush(swfile))
	  if (errno != EPIPE) syscallerror("inform service of closed read fd");
      }
    } else {
      if (WIFEXITED(status))
	fprintf(stderr,"userv: cat for fd %d exited with error exit status %d\n",
		fd,WEXITSTATUS(status));
      else if (WIFSIGNALED(status))
	if (WCOREDUMP(status))
	  fprintf(stderr,"userv: cat for fd %d dumped core due to signal %s (%d)\n",
		  fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
	else
	  fprintf(stderr,"userv: cat for fd %d terminated by signal %s (%d)\n",
		  fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
      else
	fprintf(stderr,"userv: cat for fd %d gave unknown wait status %d\n",
		fd,status);
      disconnect();
    }
    fdsetup[fd].catpid= -1;
  }
  errno= es;
}

/*
 * Argument parsing.  These functions which are called only during
 * setup, before the signal asynchronicity starts.
 */

struct optioninfo;

typedef void optionfunction(const struct optioninfo*, const char *value, char *key);

struct optioninfo {
  int abbrev;
  const char *full;
  int values; /* 0: no value; 1: single value; 2: key and value */
  optionfunction *fn;
};

static void usage(FILE *stream) {
  if (fputs(
    "usage: userv <options> [--] <service-user> <service-name> [<argument> ...]\n"
    "usage: userv <options> -B|--builtin [--] <builtin-service> [<info-argument> ...]\n"
    "options: -f|--file <fd>[<fdmodifiers>]=<filename>\n"
    "         -D|--defvar <name>=<value>\n"
    "         -t|--timeout <seconds>\n"
    "         -S|--signals <status>|number|number-nocore|highbit|stdout\n"
    "         -w|--fdwait <fd>=wait|nowait|close\n"
    "         -P|--sigpipe  -H|--hidecwd  -h|--help|--version  --copyright\n"
    "         --override <configuration-data> } available only\n"
    "         --override-file <filename>      }  to root\n"
    "         --spoof-user <username>         }  or same user\n"
    "fdmodifiers:            read    write  overwrite    trunc[ate]\n"
    "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n"
    "userv -B 'X' ... is same as userv --override 'execute-builtin X' - 'X' ...\n"
    "         for help, type `userv -B help'; remember to quote multi-word X\n"
    "userv and uservd version " VERSION VEREXT ".\n"
    COPYRIGHT("","\n"),
            stream) < 0)
    syscallerror("write usage message");
}

static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) {
  va_list al;
  va_start(al,fmt);
  fputs("userv: ",stderr);
  vfprintf(stderr,fmt,al);
  fputs("\n\n",stderr);
  usage(stderr);
  exit(-1);
}

static const struct fdmodifierinfo fdmodifierinfos[]= {
  { "read",      fdm_read,
                 fdm_write,
                 O_RDONLY                                                         },
  { "write",     fdm_write,
                 fdm_read,
                 O_WRONLY                                                         },
  { "overwrite", fdm_write|fdm_create|fdm_truncate,
                 fdm_read|fdm_fd|fdm_exclusive,
                 O_WRONLY|O_CREAT|O_TRUNC                                         },
  { "create",    fdm_write|fdm_create,
                 fdm_read|fdm_fd,
                 O_WRONLY|O_CREAT                                                 },
  { "creat",     fdm_write|fdm_create,
                 fdm_read|fdm_fd,
                 O_WRONLY|O_CREAT                                                 },
  { "exclusive", fdm_write|fdm_create|fdm_exclusive,
                 fdm_read|fdm_fd|fdm_truncate,
                 O_WRONLY|O_CREAT|O_EXCL                                          },
  { "excl",      fdm_write|fdm_create|fdm_exclusive,
                 fdm_read|fdm_fd|fdm_truncate,
                 O_WRONLY|O_CREAT|O_EXCL                                          },
  { "truncate",  fdm_write|fdm_truncate,
                 fdm_read|fdm_fd|fdm_exclusive,
                 O_WRONLY|O_CREAT|O_EXCL                                          },
  { "trunc",     fdm_write|fdm_truncate,
                 fdm_read|fdm_fd|fdm_exclusive,
                 O_WRONLY|O_CREAT|O_EXCL                                          },
  { "append",    fdm_write|fdm_append,
                 fdm_read|fdm_fd,
                 O_WRONLY|O_CREAT|O_APPEND                                        },
  { "sync",      fdm_write|fdm_sync,
                 fdm_read|fdm_fd,
                 O_WRONLY|O_CREAT|O_SYNC                                          },
  { "wait",      fdm_wait,
                 fdm_nowait|fdm_close,
                 0                                                                },
  { "nowait",    fdm_nowait,
                 fdm_wait|fdm_close,
                 0                                                                },
  { "close",     fdm_close,
                 fdm_wait|fdm_nowait,
                 0                                                                },
  { "fd",        fdm_fd,
                 fdm_create|fdm_exclusive|fdm_truncate|fdm_append|fdm_sync,
                 0                                                                },
  {  0                                                                            }
};

static void addfdmodifier(int fd, const char *key, size_t key_len) {
  const struct fdmodifierinfo *fdmip;
  
  if (!*key) return;
  for (fdmip= fdmodifierinfos;
       fdmip->string &&
	 !(strlen(fdmip->string) == key_len &&
	   !memcmp(fdmip->string,key,key_len));
       fdmip++);
  if (!fdmip->string) usageerror("unknown fdmodifer `%.*s' for fd %d",
				 (int)key_len,key,fd);
  if (fdmip->conflicts & fdsetup[fd].mods)
    usageerror("fdmodifier `%.*s' conflicts with another for fd %d",
	       (int)key_len,key,fd);
  fdsetup[fd].mods |= fdmip->implies;
  fdsetup[fd].oflags |= fdmip->oflags;
}

static void addfdmodifier_fixed(int fd, const char *key) {
  addfdmodifier(fd, key, strlen(key));
}

static int fdstdnumber(const char *string, const char **delim_r) {
#define FN(v,s) do{				\
    if (!memcmp(string,s,sizeof(s)-1)) {	\
      *delim_r= string+sizeof(s)-1;		\
      return v;					\
    }						\
  }while(0)

  FN(0,"stdin");
  FN(1,"stdout");
  FN(2,"stderr");

  return -1;
}

static int strtofd(const char *string,
		   const char **mods_r /* 0: no modifiers, must go to end */,
		   const char *what) {
  int fd;
  unsigned long ul;
  char *delim_v;
  const char *mods;
  
  fd= fdstdnumber(string,&mods);
  if (fd>=0) {
    if (*mods && *mods != ',')
      usageerror("%s, when it is `stdin', `stdout' or `stderr',"
		 " must be delimited with a comma from any following"
		 " modifiers - `%s' is not permitted",
		 what, mods);
    goto parsed;
  }

  errno= 0;
  ul= strtoul(string,&delim_v,10);
  if (errno || delim_v == string || ul > INT_MAX)
    usageerror("%s must be must be numeric file descriptor"
	       " or `stdin', `stdout' or `stderr'"
	       " - `%s' is not recognized",
	       what, string);
  mods= delim_v;
  fd= ul;

 parsed:
  if (*mods==',')
    mods++;
  
  if (mods_r)
    *mods_r= mods;
  else if (*mods)
    usageerror("%s must be must be only file descriptor"
	       " - trailing portion or modifiers `%s' not permitted",
	       what, mods);

  if (fd > MAX_ALLOW_FD)
    usageerror("%s file descriptor specified (%d)"
	       " is larger than maximum allowed (%d)",
	       what, fd, MAX_ALLOW_FD);
  
  return fd;
}
  
static void of_file(const struct optioninfo *oip, const char *value, char *key) {
  unsigned long fd, copyfd;
  struct stat stab;
  int oldarraysize, r;
  size_t mod_len;
  const char *mods, *delim;

  fd= strtofd(key,&mods,"first part of argument to -f or --file");

  if (fd >= fdsetupsize) {
    oldarraysize= fdsetupsize;
    fdsetupsize+=2; fdsetupsize<<=1;
    fdsetup= xrealloc(fdsetup,sizeof(struct fdsetupstate)*fdsetupsize);
    while (oldarraysize < fdsetupsize) {
      fdsetup[oldarraysize].filename= 0;
      fdsetup[oldarraysize].pipefd= -1;
      fdsetup[oldarraysize].copyfd= -1;
      fdsetup[oldarraysize].mods= 0;
      fdsetup[oldarraysize].oflags= 0;
      fdsetup[oldarraysize].catpid= -1;
      fdsetup[oldarraysize].killed= 0;
      fdsetup[oldarraysize].filename= 0;
      oldarraysize++;
    }
  }
  fdsetup[fd].filename= value;
  fdsetup[fd].oflags= 0;
  fdsetup[fd].mods= 0;
  fdsetup[fd].copyfd= -1;
  while (mods && *mods) {
    delim= strchr(mods,',');
    mod_len= delim ? delim-mods : strlen(mods);
    addfdmodifier(fd,mods,mod_len);
    mods= delim ? delim+1 : 0;
  }
  if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
    if (fd == 0) {
      addfdmodifier_fixed(fd,"read");
    } else if (fdsetup[fd].mods & fdm_fd) {
      addfdmodifier_fixed(fd,"write");
    } else {
      addfdmodifier_fixed(fd,"overwrite");
    }
  }
  if (fdsetup[fd].mods & fdm_fd) {
    copyfd= strtofd(value,0,
		    "value part of argument to --file with fd modifier");
    r= fstat(copyfd,&stab);
    if (r) {
      if (oip) fsyscallerror("check filedescriptor %lu (named as target of file "
			     "descriptor redirection for %lu)",copyfd,fd);
      else fsyscallerror("check basic filedescriptor %lu at program start",copyfd);
    }
    fdsetup[fd].copyfd= copyfd;
  }
}

static void of_fdwait(const struct optioninfo *oip, const char *value, char *key) {
  const struct fdmodifierinfo *fdmip;
  int fd;
  
  fd= strtofd(key,0,"first part of argument to --fdwait");
  if (fd >= fdsetupsize || !fdsetup[fd].filename)
    usageerror("file descriptor %d specified in --fdwait option is not open",fd);
  for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,value); fdmip++);
  if (!fdmip->string || !(fdmip->implies & (fdm_wait|fdm_nowait|fdm_close)))
    usageerror("value for --fdwait must be `wait', `nowait' or `close', not `%s'",value);
  fdsetup[fd].mods &= ~(fdm_wait|fdm_nowait|fdm_close);
  fdsetup[fd].mods |= fdmip->implies;
}

static void of_defvar(const struct optioninfo *oip, const char *value, char *key) {
  int i;

  if (!key[0])
    usageerror("empty string not allowed as variable name");
  if (strlen(key)>MAX_GENERAL_STRING)
    usageerror("variable name `%s' is far too long",key);
  if (strlen(value)>MAX_GENERAL_STRING)
    usageerror("variable `%s' has value `%s' which is far too long",key,value);
  for (i=0; i<defvarused && strcmp(defvararray[i].key,key); i++);
  if (defvarused >= MAX_ARGSDEFVAR) usageerror("far too many --defvar or -D options");
  if (i>=defvaravail) {
    defvaravail+=10; defvaravail<<=1;
    defvararray= xrealloc(defvararray,sizeof(struct constkeyvaluepair)*defvaravail);
  }
  if (i==defvarused) defvarused++;
  defvararray[i].key= key;
  defvararray[i].value= value;
}

static void of_timeout(const struct optioninfo *oip, const char *value, char *key) {
  char *endp;
  unsigned long ul;

  errno= 0;
  ul= strtoul(value,&endp,10);
  if (errno || *endp)
    usageerror("timeout value `%s' must be a plain decimal string",value);
  if (ul>INT_MAX) usageerror("timeout value %lu too large",ul);
  timeout= ul;
}

static void of_signals(const struct optioninfo *oip, const char *value, char *key) {
  unsigned long numvalue;
  char *endp;

  errno= 0;
  numvalue= strtoul(value,&endp,10);
  if (errno || *endp) {
    if (!strcmp(value,"number")) signalsexit= se_number;
    else if (!strcmp(value,"number-nocore")) signalsexit= se_numbernocore;
    else if (!strcmp(value,"highbit")) signalsexit= se_highbit;
    else if (!strcmp(value,"stdout")) signalsexit= se_stdout;
    else usageerror("value `%s' for --signals not understood",value);
  } else {
    if (numvalue<0 || numvalue>255)
      usageerror("value %lu for --signals not 0...255",numvalue);
    signalsexit= numvalue;
  }
}

static void of_sigpipe(const struct optioninfo *oip, const char *value, char *key) {
  sigpipeok=1;
}

static void of_hidecwd(const struct optioninfo *oip, const char *value, char *key) {
  hidecwd=1;
}

static void of_help(const struct optioninfo *oip, const char *value, char *key) {
  usage(stdout);
  if (fclose(stdout)) syscallerror("fclose stdout after writing usage message");
  exit(0);
}

static void of_version(const struct optioninfo *oip, const char *value, char *key) {
  if (puts(VERSION VEREXT) == EOF || fclose(stdout))
    syscallerror("write version number");
  exit(0);
}

static void of_copyright(const struct optioninfo *oip, const char *value, char *key) {
  if (fputs(
" userv - user service daemon and client\n\n"
COPYRIGHT(" ","\n")
"\n"
" This is free software; you can redistribute it and/or modify it under the\n"
" terms of the GNU General Public License as published by the Free Software\n"
" Foundation; either version 3 of the License, or (at your option) any\n"
" later version.\n\n"
" This program is distributed in the hope that it will be useful, but\n"
" WITHOUT ANY WARRANTY; without even the implied warranty of\n"
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General\n"
" Public License for more details.\n\n"
" You should have received a copy of the GNU General Public License along\n"
" with userv; if not, see <http://www.gnu.org/licenses/>.\n",
	    stdout) < 0) syscallerror("write usage to stderr");
  exit(0);
}

static void of_builtin(const struct optioninfo *oip, const char *value, char *key) {
  overridetype= ot_builtin;
}

static void of_override(const struct optioninfo *oip, const char *value, char *key) {
  overridetype= ot_string;
  overridevalue= value;
}

static void of_overridefile(const struct optioninfo *oip,
                            const char *value, char *key) {
  overridetype= ot_file;
  overridevalue= value;
}

static void of_spoofuser(const struct optioninfo *oip,
			 const char *value, char *key) {
  spoofuser= value;
}

const struct optioninfo optioninfos[]= {
  { 'f', "file",          2, of_file         },
  { 'w', "fdwait",        2, of_fdwait       },
  { 'D', "defvar",        2, of_defvar       },
  { 't', "timeout",       1, of_timeout      },
  { 'S', "signals",       1, of_signals      },
  { 'P', "sigpipe",       0, of_sigpipe      },
  { 'H', "hidecwd",       0, of_hidecwd      },
  { 'B', "builtin",       0, of_builtin      },
  { 'h', "help",          0, of_help         },
  {  0,  "version",       0, of_version      },
  {  0,  "copyright",     0, of_copyright    },
  {  0,  "override",      1, of_override     },
  {  0,  "override-file", 1, of_overridefile },
  {  0,  "spoof-user",    1, of_spoofuser    },
  {  0,   0                                  }
};

static void callvalueoption(const struct optioninfo *oip, char *arg) {
  char *equals;
  if (oip->values == 2) {
    equals= strchr(arg,'=');
    if (!equals) {
      if (oip->abbrev)
        usageerror("option --%s (-%c) passed argument `%s' with no `='",
                   oip->full,oip->abbrev,arg);
      else
        usageerror("option --%s passed argument `%s' with no `='",
                   oip->full,arg);
    }
    *equals++= 0;
    (oip->fn)(oip,equals,arg);
  } else {
    (oip->fn)(oip,arg,0);
  }
}

/*
 * Main thread main processing functions - in order of execution.
 */

static void security_init(void) {
  /* May not open any file descriptors. */
  
  mypid= getpid(); if (mypid == (pid_t)-1) syscallerror("getpid");
  myuid= getuid(); if (myuid == (uid_t)-1) syscallerror("getuid");
  mygid= getgid(); if (mygid == (gid_t)-1) syscallerror("getgid");
  ngids= getgroups(0,0); if (ngids == -1) syscallerror("getgroups(0,0)");
  gidarray= xmalloc(sizeof(gid_t)*ngids);
  if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
  
  priv_suspend();

  if (ngids > MAX_GIDS) miscerror("caller is in far too many gids");
}

static void parse_arguments(int *argcp, char *const **argvp) {
  static char fd0key[]= "stdin,fd,read";
  static char fd1key[]= "stdout,fd,write";
  static char fd2key[]= "stderr,fd,write";

  char *const *argpp;
  char *argp;
  const struct optioninfo *oip;
  int fd;

  assert((*argvp)[0]);
  of_file(0,"stdin",fd0key);
  of_file(0,"stdout",fd1key);
  of_file(0,"stderr",fd2key);

  for (argpp= *argvp+1;
       (argp= *argpp) && *argp == '-' && argp[1];
       argpp++) {
    if (!*++argp) usageerror("unknown option/argument `%s'",*argpp);
    if (*argp == '-') { /* Two hyphens */
      if (!*++argp) { argpp++; break; /* End of options. */ }
      for (oip= optioninfos; oip->full && strcmp(oip->full,argp); oip++);
      if (!oip->full) usageerror("unknown long option `%s'",*argpp);
      if (oip->values) {
        if (!argpp[1]) usageerror("long option `%s' needs a value",*argpp);
        callvalueoption(oip,*++argpp);
      } else {
        (oip->fn)(oip,0,0);
      }
    } else {
      for (; *argp; argp++) {
        for (oip= optioninfos; oip->full && oip->abbrev != *argp; oip++);
        if (!oip->full) usageerror("unknown short option `-%c' in argument `%s'",
                                    *argp, *argpp);
        if (oip->values) {
          if (argp[1]) {
            argp++;
          } else {
            if (!argpp[1]) usageerror("short option `-%c' in argument `%s' needs"
                                      " a value",*argp,*argpp);
            argp= *++argpp;
          }
          callvalueoption(oip,argp);
          break; /* No more options in this argument, go on to the next one. */
        } else {
          (oip->fn)(oip,0,0);
        }
      }
    }
  }
  if (overridetype == ot_builtin) {
    serviceuser= "-";
  } else {
    if (!*argpp) usageerror("no service user given after options");
    serviceuser= *argpp++;
  }
  if (!*argpp) usageerror(overridetype == ot_builtin ?
			  "no service name given after options and service user" :
			  "no builtin service given after options");

  *argcp-= (argpp-*argvp);
  *argvp= argpp;

  if (*argcp > MAX_ARGSDEFVAR) usageerror("far too many arguments");
  
  for (fd=0; fd<fdsetupsize; fd++) {
    if (!fdsetup[fd].filename) continue;
    if (fdsetup[fd].mods & (fdm_wait|fdm_nowait|fdm_close)) continue;
    assert(fdsetup[fd].mods & (fdm_read|fdm_write));
    fdsetup[fd].mods |= (fdsetup[fd].mods & fdm_read) ? fdm_close : fdm_wait;
  }
}

static void determine_users(void) {
  int ngidssize;
  char **mem;
  struct passwd *pw;
  struct group *gr;
  
  spoofuid= myuid;
  spoofgid= mygid;
  loginname= getenv("LOGNAME");
  if (!loginname) loginname= getenv("USER");
  if (loginname) {
    pw= getpwnam(loginname);
    if (!pw || pw->pw_uid != myuid) loginname= 0;
  }
  if (!loginname) {
    pw= getpwuid(myuid); if (!pw) miscerror("cannot determine your login name");
    loginname= xstrsave(pw->pw_name);
  }

  if (!strcmp(serviceuser,"-")) serviceuser= loginname;
  pw= getpwnam(serviceuser);
  if (!pw) miscerror("requested service user `%s' is not a user",serviceuser);
  serviceuid= pw->pw_uid;

  if ((overridetype != ot_none || spoofuser) && myuid != 0 && myuid != serviceuid)
    miscerror("--override and --spoof options only available to root or to"
              " the user who will be providing the service");

  if (spoofuser) {
    loginname= spoofuser;
    pw= getpwnam(loginname);
    if (!pw) miscerror("spoofed login name `%s' is not valid",loginname);
    spoofuid= pw->pw_uid;
    spoofgid= pw->pw_gid;
    ngidssize= ngids; ngids= 0;
    if (ngidssize<5) {
      ngidssize= 5;
      gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize);
    }
    gidarray[ngids++]= spoofgid;
    while ((gr= getgrent())) { /* ouch! getgrent has no error behaviour! */
      for (mem= gr->gr_mem; *mem && strcmp(*mem,loginname); mem++);
      if (!*mem) continue;
      if (ngids>=ngidssize) {
      if (ngids>=MAX_GIDS) miscerror("spoofed user is member of too many groups");
	ngidssize= (ngids+5)<<1;
	gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize);
      }
      gidarray[ngids++]= gr->gr_gid;
    }
  }
}

static void determine_cwd(void) {
  cwdbufsize= 0; cwdbuf= 0;

  if (hidecwd) return;
  
  for (;;) {
    if (cwdbufsize > MAX_GENERAL_STRING) { cwdbufsize= 0; free(cwdbuf); break; }
    cwdbufsize <<= 1; cwdbufsize+= 100;
    cwdbuf= xrealloc(cwdbuf,cwdbufsize);
    cwdbuf[cwdbufsize-1]= 0;
    if (getcwd(cwdbuf,cwdbufsize-1)) { cwdbufsize= strlen(cwdbuf); break; }
    if (errno != ERANGE) { cwdbufsize= 0; free(cwdbuf); break; }
  }
}

static void process_override(const char *servicename) {
  FILE *ovfile;
  int ovavail, l, c;

  switch (overridetype) {
  case ot_none:
    ovused= -1;
    ovbuf= 0;
    break;
  case ot_builtin:
    l= strlen(servicename);
    if (l >= MAX_OVERRIDE_LEN-20)
      miscerror("builtin service string is too long (%d, max is %d)",
		l,MAX_OVERRIDE_LEN-21);
    l+= 20;
    ovbuf= xmalloc(l);
    snprintf(ovbuf,l,"execute-builtin %s\n",servicename);
    ovused= strlen(ovbuf);
    break;
  case ot_string:
    l= strlen(overridevalue);
    if (l >= MAX_OVERRIDE_LEN)
      miscerror("override string is too long (%d, max is %d)",l,MAX_OVERRIDE_LEN-1);
    ovbuf= xmalloc(l+2);
    snprintf(ovbuf,l+2,"%s\n",overridevalue);
    ovused= l+1;
    break;
  case ot_file:
    ovfile= fopen(overridevalue,"r");
    if (!ovfile) fsyscallerror("open overriding configuration file `%s'",overridevalue);
    ovbuf= 0; ovavail= ovused= 0;
    while ((c= getc(ovfile)) != EOF) {
      if (!c) miscerror("overriding config file `%s' contains null(s)",overridevalue);
      if (ovused >= MAX_OVERRIDE_LEN)
        miscerror("override file is too long (max is %d)",MAX_OVERRIDE_LEN);
      if (ovused >= ovavail) {
        ovavail+=80; ovavail<<=2; ovbuf= xrealloc(ovbuf,ovavail);
      }
      ovbuf[ovused++]= c;
    }
    if (ferror(ovfile) || fclose(ovfile))
      fsyscallerror("read overriding configuration file `%s'",overridevalue);
    ovbuf= xrealloc(ovbuf,ovused+1);
    ovbuf[ovused]= 0;
    break;
  default:
    abort();
  }
}

static int server_connect(void) {
  struct sockaddr_un ssockname;
  int sfd;
  struct sigaction sig;
  sigset_t sset;

  sigemptyset(&sset);
  sigaddset(&sset,SIGCHLD);
  sigaddset(&sset,SIGALRM);
  sigaddset(&sset,SIGPIPE);
  if (sigprocmask(SIG_UNBLOCK,&sset,0)) syscallerror("preliminarily unblock signals");

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

  sfd= socket(AF_UNIX,SOCK_STREAM,0);
  if (!sfd) syscallerror("create client socket");

  assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUSPATH));
  ssockname.sun_family= AF_UNIX;
  strcpy(ssockname.sun_path,RENDEZVOUSPATH);
  priv_resume();
  while (connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname))) {
    if (errno == ECONNREFUSED || errno == ENOENT)
      syscallerror("uservd daemon is not running - service not available");
    if (errno != EINTR)
      fsyscallerror("unable to connect to uservd daemon: %m");
  }

  return sfd;
}

static void server_handshake(int sfd) {
  srfile= fdopen(sfd,"r");
  if (!srfile) syscallerror("turn socket fd into FILE* for read");
  if (setvbuf(srfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket reads");

  swfile= fdopen(sfd,"w");
  if (!swfile) syscallerror("turn socket fd into FILE* for write");
  if (setvbuf(swfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket writes");

  xfread(&opening_mbuf,sizeof(opening_mbuf),srfile);
  checkmagic(opening_mbuf.magic,OPENING_MAGIC,"in opening message");
  if (memcmp(protocolchecksumversion,opening_mbuf.protocolchecksumversion,PCSUMSIZE))
    protoerror("protocol version checksum mismatch - server not same as client");
}

static void server_preparepipes(void) {
  char pipepathbuf[PIPEPATHMAXLEN+2];
  int fd, tempfd;
  
  for (fd=0; fd<fdsetupsize; fd++) {
    if (!fdsetup[fd].filename) continue;
    pipepathbuf[PIPEPATHMAXLEN]= 0;
    sprintf(pipepathbuf, PIPEPATHFORMAT,
            (unsigned long)mypid, (unsigned long)opening_mbuf.serverpid, fd);
    assert(!pipepathbuf[PIPEPATHMAXLEN]);
    priv_resume();
    if (unlink(pipepathbuf) && errno != ENOENT)
      fsyscallerror("remove any old pipe `%s'",pipepathbuf);
    if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
      fsyscallerror("create pipe `%s'",pipepathbuf);
    tempfd= open(pipepathbuf,O_RDWR);
    if (tempfd<0) fsyscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
    assert(fdsetup[fd].mods & (fdm_read|fdm_write));
    fdsetup[fd].pipefd=
      open(pipepathbuf, (fdsetup[fd].mods & fdm_read) ? O_WRONLY : O_RDONLY);
    if (fdsetup[fd].pipefd<0) fsyscallerror("real open pipe `%s'",pipepathbuf);
    if (close(tempfd)) fsyscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
    priv_suspend();
  }
}

static void server_sendrequest(int argc, char *const *argv) {
  struct request_msg request_mbuf;
  unsigned long ul;
  int fd, i;
  
  memset(&request_mbuf,0,sizeof(request_mbuf));
  request_mbuf.magic= REQUEST_MAGIC;
  request_mbuf.clientpid= getpid();
  request_mbuf.serviceuserlen= strlen(serviceuser);
  request_mbuf.servicelen= strlen(argv[0]);
  request_mbuf.loginnamelen= strlen(loginname);
  request_mbuf.spoofed= spoofuser ? 1 : 0;
  request_mbuf.cwdlen= cwdbufsize;
  request_mbuf.callinguid= spoofuid;
  request_mbuf.ngids= ngids+1;
  request_mbuf.nreadfds= 0;
  request_mbuf.nwritefds= 0;
  for (fd=0; fd<fdsetupsize; fd++) {
    if (!fdsetup[fd].filename) continue;
    assert(fdsetup[fd].mods & (fdm_write|fdm_read));
    if (fdsetup[fd].mods & fdm_write) request_mbuf.nwritefds++;
    else request_mbuf.nreadfds++;
  }
  request_mbuf.nargs= argc-1;
  request_mbuf.nvars= defvarused;
  request_mbuf.overridelen= ovused;
  xfwrite(&request_mbuf,sizeof(request_mbuf),swfile);
  xfwrite(serviceuser,sizeof(*serviceuser)*request_mbuf.serviceuserlen,swfile);
  xfwrite(argv[0],sizeof(*argv[0])*request_mbuf.servicelen,swfile);
  xfwrite(loginname,sizeof(*loginname)*request_mbuf.loginnamelen,swfile);
  xfwrite(cwdbuf,sizeof(*cwdbuf)*request_mbuf.cwdlen,swfile);
  if (ovused>=0) xfwrite(ovbuf,sizeof(*ovbuf)*ovused,swfile);
  xfwrite(&spoofgid,sizeof(gid_t),swfile);
  xfwrite(gidarray,sizeof(gid_t)*ngids,swfile);
  xfwritefds(fdm_read,request_mbuf.nreadfds,swfile);
  xfwritefds(fdm_write,request_mbuf.nwritefds,swfile);
  for (i=1; i<argc; i++)
    xfwritestring(argv[i],swfile);
  for (i=0; i<defvarused; i++) {
    xfwritestring(defvararray[i].key,swfile);
    xfwritestring(defvararray[i].value,swfile);
  }
  ul= REQUEST_END_MAGIC; xfwrite(&ul,sizeof(ul),swfile);
  xfflush(swfile);
}

static void server_awaitconfirm(void) {
  struct progress_msg progress_mbuf;
  
  getprogress(&progress_mbuf,srfile);
  if (progress_mbuf.type != pt_ok)
    protoerror("progress message during configuration phase"
	       " unexpected type %d",progress_mbuf.type);
}

static void prepare_asynchsignals(void) {
  static char stderrbuf[BUFSIZ], stdoutbuf[BUFSIZ];
  
  struct sigaction sig;

  if (setvbuf(stderr,stderrbuf,_IOLBF,sizeof(stderrbuf)))
    syscallerror("set buffering on stderr");
  if (setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf)))
    syscallerror("set buffering on stdout");
  
  sig.sa_handler= sighandler_chld;
  sigemptyset(&sig.sa_mask);
  sigaddset(&sig.sa_mask,SIGCHLD);
  sigaddset(&sig.sa_mask,SIGALRM);
  sig.sa_flags= 0;
  if (sigaction(SIGCHLD,&sig,0)) syscallerror("set up sigchld handler");

  sig.sa_handler= sighandler_alrm;
  if (sigaction(SIGALRM,&sig,0)) syscallerror("set up sigalrm handler");

  if (!timeout) return;
  alarm(timeout);
}


static void close_unwanted_pipes(void) {
  int fd;

  for (fd=0; fd<fdsetupsize; fd++) {
    if (!fdsetup[fd].filename) continue;
    if (close(fdsetup[fd].pipefd)) fsyscallerror("close pipe fd for %d",fd);
    if (fdsetup[fd].copyfd>2)
      if (close(fdsetup[fd].copyfd))
	if (errno != EBADF)
	  /* EBADF can be induced if cmd line specifies same fd twice */
	  fsyscallerror("close real fd for %d",fd);
  }
}

static void catdup(const char *which, int from, int to) {
  if (dup2(from,to)<0) {
    blocksignals(SIG_BLOCK);
    fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
	    to?"stdout":"stdin", strerror(errno));
    exit(-1);
  }
}

static void connect_pipes(void) {
  struct sigaction sig;
  char catnamebuf[sizeof(int)*3+30];
  int fd, reading;
  pid_t child;
  
  for (fd=0; fd<fdsetupsize; fd++) {
    if (!fdsetup[fd].filename) continue;
    if (!(fdsetup[fd].mods & fdm_fd)) {
      fdsetup[fd].copyfd=
	open(fdsetup[fd].filename,fdsetup[fd].oflags|O_NOCTTY,0777);
      if (fdsetup[fd].copyfd<0)
	fsyscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
    }
    blocksignals(SIG_BLOCK);
    child= fork();
    fdsetup[fd].catpid= child;
    blocksignals(SIG_UNBLOCK);
    if (child==-1) fsyscallerror("fork for cat for fd %d",fd);
    if (!child) {
      snprintf(catnamebuf,sizeof(catnamebuf),"cat fd%d",fd);
      catnamebuf[sizeof(catnamebuf)-1]= 0;
      sig.sa_handler= SIG_DFL;
      sigemptyset(&sig.sa_mask);
      sig.sa_flags= 0;
      if (sigaction(SIGPIPE,&sig,0)) {
	fprintf(stderr,"userv: %s: reset sigpipe handler for cat: %s",
		catnamebuf,strerror(errno));
	exit(-1);
      }
      reading= fdsetup[fd].mods & fdm_read;
      catdup(catnamebuf, fdsetup[fd].copyfd, reading ? 0 : 1);
      catdup(catnamebuf, fdsetup[fd].pipefd, reading ? 1 : 0);
      if (close(socketfd))
	fsyscallerror("%s: close client socket for for cat",catnamebuf);
      close_unwanted_pipes();
      execl("/bin/cat",catnamebuf,(char*)0);
      fprintf(stderr,"userv: %s: cannot exec `cat': %s\n",catnamebuf,strerror(errno));
      exit(-1);
    }
  }
  close_unwanted_pipes();
}

static void server_sendconfirm(void) {
  struct event_msg event_mbuf;

  blocksignals(SIG_BLOCK);
  memset(&event_mbuf,0,sizeof(event_mbuf));
  event_mbuf.magic= EVENT_MAGIC;
  event_mbuf.type= et_confirm;
  xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
  xfflush(swfile);
  blocksignals(SIG_UNBLOCK);
}

static int server_awaitcompletion(void) {
  struct progress_msg progress_mbuf;
  
  getprogress(&progress_mbuf,srfile);
  if (progress_mbuf.type != pt_terminated)
    protoerror("progress message during execution phase"
	       " unexpected type %d",progress_mbuf.type);

  swfile= 0;
  return progress_mbuf.data.terminated.status;
}

static void dispose_remaining_pipes(void) {
  sigset_t sset;
  int fd, r;

  blocksignals(SIG_BLOCK);
  for (fd=0; fd<fdsetupsize; fd++) {
    if (!(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_close))) continue;
    if (kill(fdsetup[fd].catpid,SIGKILL)) fsyscallerror("kill cat for %d",fd);
    fdsetup[fd].killed= 1;
  }
  blocksignals(SIG_UNBLOCK);

  for (;;) {
    blocksignals(SIG_BLOCK);
    for (fd=0;
	 fd<fdsetupsize && !(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_wait));
	 fd++);
    if (fd>=fdsetupsize) break;
    sigemptyset(&sset);
    r= sigsuspend(&sset);
    if (r && errno != EINTR) syscallerror("sigsuspend failed in unexpected way");
    blocksignals(SIG_UNBLOCK);
  }
}

static void NONRETURNING process_exitstatus(int status) {
  blocksignals(SIG_BLOCK);

  if (systemerror) _exit(255);

  if (sigpipeok && signalsexit != se_stdout && WIFSIGNALED(status) &&
      WTERMSIG(status)==SIGPIPE && !WCOREDUMP(status)) status= 0;
  
  switch (signalsexit) {
  case se_number:
  case se_numbernocore:
    if (WIFEXITED(status))
      _exit(WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
      _exit(WTERMSIG(status) + (signalsexit==se_number && WCOREDUMP(status) ? 128 : 0));
    break;
  case se_highbit:
    if (WIFEXITED(status))
      _exit(WEXITSTATUS(status)<=127 ? WEXITSTATUS(status) : 127);
    else if (WIFSIGNALED(status) && WTERMSIG(status)<=126)
      _exit(WTERMSIG(status)+128);
    break;
  case se_stdout:
    printf("\n%d %d ",(status>>8)&0x0ff,status&0x0ff);
    if (WIFEXITED(status))
      printf("exited with code %d",WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
      printf("killed by %s (signal %d)%s",
	     strsignal(WTERMSIG(status)),WTERMSIG(status),
	     WCOREDUMP(status) ? ", core dumped " : "");
    else
      printf("unknown wait status");
    putchar('\n');
    if (ferror(stdout) || fflush(stdout)) syscallerror("write exit status to stdout");
    _exit(0);
  default:
    if (WIFEXITED(status))
      _exit(WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
      _exit(signalsexit);
    break;
  }
      
  fprintf(stderr,"userv: unknown wait status %d\n",status);
  _exit(-1);
}

int main(int argc, char *const *argv) {
  int status;

#ifdef NDEBUG
# error Do not disable assertions in this security-critical code !
#endif

  security_init();
  parse_arguments(&argc,&argv);
  determine_users();
  determine_cwd();
  process_override(argv[0]);

  socketfd= server_connect();
  priv_suspend();
  server_handshake(socketfd);
  server_preparepipes();
  server_sendrequest(argc,argv);

  priv_permanentlyrevokesuspended();
  
  server_awaitconfirm();
  prepare_asynchsignals();
  connect_pipes();
  server_sendconfirm();
  status= server_awaitcompletion();
  
  dispose_remaining_pipes();
  process_exitstatus(status);
}