s6-ipcserverd.c (10430B)
1 /* ISC license. */ 2 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <sys/wait.h> 6 #include <errno.h> 7 #include <string.h> 8 #include <unistd.h> 9 #include <fcntl.h> 10 #include <signal.h> 11 12 #include <skalibs/posixplz.h> 13 #include <skalibs/types.h> 14 #include <skalibs/gccattributes.h> 15 #include <skalibs/allreadwrite.h> 16 #include <skalibs/sgetopt.h> 17 #include <skalibs/strerr.h> 18 #include <skalibs/djbunix.h> 19 #include <skalibs/sig.h> 20 #include <skalibs/selfpipe.h> 21 #include <skalibs/iopause.h> 22 #include <skalibs/socket.h> 23 #include <skalibs/env.h> 24 #include <skalibs/cspawn.h> 25 26 #define USAGE "s6-ipcserverd [ -v verbosity ] [ -1 ] [ -P | -p ] [ -c maxconn ] [ -C localmaxconn ] prog..." 27 28 #define ABSOLUTE_MAXCONN 16384 29 30 static unsigned int maxconn = 40 ; 31 static unsigned int localmaxconn = 40 ; 32 static char fmtmaxconn[UINT_FMT+1] = "/" ; 33 static char fmtlocalmaxconn[UINT_FMT+1] = "/" ; 34 static int flaglookup = 1 ; 35 static unsigned int verbosity = 1 ; 36 static int cont = 1 ; 37 38 typedef struct piduid_s piduid_t, *piduid_t_ref ; 39 struct piduid_s 40 { 41 pid_t left ; 42 uid_t right ; 43 } ; 44 45 typedef struct uidnum_s uidnum_t, *uidnum_t_ref ; 46 struct uidnum_s 47 { 48 uid_t left ; 49 unsigned int right ; 50 } ; 51 52 static piduid_t *piduid ; 53 static unsigned int numconn = 0 ; 54 static uidnum_t *uidnum ; 55 static unsigned int uidlen = 0 ; 56 57 static inline void dieusage () 58 { 59 strerr_dieusage(100, USAGE) ; 60 } 61 62 static inline void X (void) 63 { 64 strerr_dief1x(101, "internal inconsistency. Please submit a bug-report.") ; 65 } 66 67 static unsigned int lookup_pid (pid_t pid) 68 { 69 unsigned int i = 0 ; 70 for (; i < numconn ; i++) if (pid == piduid[i].left) break ; 71 return i ; 72 } 73 74 static inline unsigned int lookup_uid (uid_t uid) 75 { 76 unsigned int i = 0 ; 77 for (; i < uidlen ; i++) if (uid == uidnum[i].left) break ; 78 return i ; 79 } 80 81 static inline void log_start (void) 82 { 83 strerr_warni1x("starting") ; 84 } 85 86 static inline void log_exit (void) 87 { 88 strerr_warni1x("exiting") ; 89 } 90 91 static void log_status (void) 92 { 93 char fmt[UINT_FMT] ; 94 fmt[uint_fmt(fmt, numconn)] = 0 ; 95 strerr_warni3x("status: ", fmt, fmtmaxconn) ; 96 } 97 98 static inline void log_deny (uid_t uid, gid_t gid, unsigned int num) 99 { 100 char fmtuid[UID_FMT] = "?" ; 101 char fmtgid[GID_FMT] = "?" ; 102 char fmtnum[UINT_FMT] = "?" ; 103 if (flaglookup) 104 { 105 fmtuid[uid_fmt(fmtuid, uid)] = 0 ; 106 fmtgid[gid_fmt(fmtgid, gid)] = 0 ; 107 fmtnum[uint_fmt(fmtnum, num)] = 0 ; 108 } 109 strerr_warni7sys("deny ", fmtuid, ":", fmtgid, " count ", fmtnum, fmtlocalmaxconn) ; 110 } 111 112 static inline void log_accept (pid_t pid, uid_t uid, gid_t gid, unsigned int num) 113 { 114 char fmtuidgid[UID_FMT + GID_FMT + 1] = "?:?" ; 115 char fmtpid[UINT_FMT] ; 116 char fmtnum[UINT_FMT] = "?" ; 117 if (flaglookup) 118 { 119 size_t n = uid_fmt(fmtuidgid, uid) ; 120 fmtuidgid[n++] = ':' ; 121 n += gid_fmt(fmtuidgid + n, gid) ; 122 fmtuidgid[n] = 0 ; 123 fmtnum[uint_fmt(fmtnum, num)] = 0 ; 124 } 125 fmtpid[pid_fmt(fmtpid, pid)] = 0 ; 126 strerr_warni7x("allow ", fmtuidgid, " pid ", fmtpid, " count ", fmtnum, fmtlocalmaxconn) ; 127 } 128 129 static inline void log_close (pid_t pid, uid_t uid, int w) 130 { 131 char fmtpid[PID_FMT] ; 132 char fmtuid[UID_FMT] = "?" ; 133 char fmtw[UINT_FMT] ; 134 fmtpid[pid_fmt(fmtpid, pid)] = 0 ; 135 if (flaglookup) fmtuid[uid_fmt(fmtuid, uid)] = 0 ; 136 fmtw[uint_fmt(fmtw, WIFSIGNALED(w) ? WTERMSIG(w) : WEXITSTATUS(w))] = 0 ; 137 strerr_warni6x("end pid ", fmtpid, " uid ", fmtuid, WIFSIGNALED(w) ? " signal " : " exitcode ", fmtw) ; 138 } 139 140 static void killthem (int sig) 141 { 142 unsigned int i = 0 ; 143 for (; i < numconn ; i++) kill(piduid[i].left, sig) ; 144 } 145 146 static inline void wait_children (void) 147 { 148 for (;;) 149 { 150 unsigned int i ; 151 int w ; 152 pid_t pid = wait_nohang(&w) ; 153 if (pid < 0) 154 if (errno != ECHILD) strerr_diefu1sys(111, "wait_nohang") ; 155 else break ; 156 else if (!pid) break ; 157 i = lookup_pid(pid) ; 158 if (i < numconn) 159 { 160 uid_t uid = piduid[i].right ; 161 unsigned int j = lookup_uid(uid) ; 162 if (j >= uidlen) X() ; 163 if (!--uidnum[j].right) uidnum[j] = uidnum[--uidlen] ; 164 piduid[i] = piduid[--numconn] ; 165 if (verbosity >= 2) 166 { 167 log_close(pid, uid, w) ; 168 log_status() ; 169 } 170 } 171 } 172 } 173 174 static inline void handle_signals (void) 175 { 176 for (;;) switch (selfpipe_read()) 177 { 178 case -1 : strerr_diefu1sys(111, "read selfpipe") ; 179 case 0 : return ; 180 case SIGCHLD : wait_children() ; break ; 181 case SIGTERM : 182 { 183 if (verbosity >= 2) 184 strerr_warni3x("received ", "SIGTERM,", " quitting") ; 185 cont = 0 ; 186 break ; 187 } 188 case SIGHUP : 189 { 190 if (verbosity >= 2) 191 strerr_warni5x("received ", "SIGHUP,", " sending ", "SIGTERM+SIGCONT", " to all connections") ; 192 killthem(SIGTERM) ; 193 killthem(SIGCONT) ; 194 break ; 195 } 196 case SIGQUIT : 197 { 198 if (verbosity >= 2) 199 strerr_warni6x("received ", "SIGQUIT,", " sending ", "SIGTERM+SIGCONT", " to all connections", " and quitting") ; 200 cont = 0 ; 201 killthem(SIGTERM) ; 202 killthem(SIGCONT) ; 203 break ; 204 } 205 case SIGABRT : 206 { 207 if (verbosity >= 2) 208 strerr_warni6x("received ", "SIGABRT,", " sending ", "SIGKILL", " to all connections", " and quitting") ; 209 cont = 0 ; 210 killthem(SIGKILL) ; 211 break ; 212 } 213 default : X() ; 214 } 215 } 216 217 static void new_connection (int s, char const *remotepath, char const *const *argv, char const *const *envp, size_t envlen) 218 { 219 uid_t uid = 0 ; 220 gid_t gid = 0 ; 221 size_t m = 0 ; 222 size_t rplen = strlen(remotepath) + 1 ; 223 pid_t pid ; 224 unsigned int num, i ; 225 cspawn_fileaction fa[2] = 226 { 227 [0] = { .type = CSPAWN_FA_MOVE, .x = { .fd2 = { [0] = 0, [1] = s } } }, 228 [1] = { .type = CSPAWN_FA_COPY, .x = { .fd2 = { [0] = 1, [1] = 0 } } } 229 } ; 230 char const *newenvp[envlen + 6] ; 231 char fmt[65 + UID_FMT + GID_FMT + UINT_FMT + rplen] ; 232 233 if (flaglookup && (getpeereid(s, &uid, &gid) < 0)) 234 { 235 if (verbosity) strerr_warnwu1sys("getpeereid") ; 236 return ; 237 } 238 i = lookup_uid(uid) ; 239 num = (i < uidlen) ? uidnum[i].right : 0 ; 240 if (num >= localmaxconn) 241 { 242 log_deny(uid, gid, num) ; 243 return ; 244 } 245 246 memcpy(fmt + m, "PROTO=IPC\0IPCREMOTEEUID", 23) ; m += 23 ; 247 if (flaglookup) 248 { 249 fmt[m++] = '=' ; 250 m += uid_fmt(fmt + m, uid) ; 251 } 252 fmt[m++] = 0 ; 253 memcpy(fmt + m, "IPCREMOTEEGID", 13) ; m += 13 ; 254 if (flaglookup) 255 { 256 fmt[m++] = '=' ; 257 m += gid_fmt(fmt + m, gid) ; 258 } 259 fmt[m++] = 0 ; 260 memcpy(fmt + m, "IPCCONNNUM=", 11) ; m += 11 ; 261 if (flaglookup) m += uint_fmt(fmt + m, num) ; 262 fmt[m++] = 0 ; 263 memcpy(fmt + m, "IPCREMOTEPATH=", 14) ; m += 14 ; 264 memcpy(fmt + m, remotepath, rplen) ; m += rplen ; 265 env_mergen(newenvp, envlen + 6, envp, envlen, fmt, m, 5) ; 266 pid = cspawn(argv[0], argv, newenvp, CSPAWN_FLAGS_SELFPIPE_FINISH, fa, 2) ; 267 if (!pid) 268 { 269 if (verbosity) strerr_warnwu2sys("spawn ", argv[0]) ; 270 return ; 271 } 272 273 if (i < uidlen) uidnum[i].right = num + 1 ; 274 else 275 { 276 uidnum[uidlen].left = uid ; 277 uidnum[uidlen++].right = 1 ; 278 } 279 piduid[numconn].left = pid ; 280 piduid[numconn++].right = uid ; 281 if (verbosity >= 2) 282 { 283 log_accept(pid, uid, gid, uidnum[i].right) ; 284 log_status() ; 285 } 286 } 287 288 int main (int argc, char const *const *argv) 289 { 290 iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .fd = 0, .events = IOPAUSE_READ | IOPAUSE_EXCEPT } } ; 291 PROG = "s6-ipcserverd" ; 292 { 293 subgetopt l = SUBGETOPT_ZERO ; 294 int flag1 = 0 ; 295 for (;;) 296 { 297 int opt = subgetopt_r(argc, argv, "Pp1c:C:v:", &l) ; 298 if (opt == -1) break ; 299 switch (opt) 300 { 301 case 'P' : flaglookup = 0 ; break ; 302 case 'p' : flaglookup = 1 ; break ; 303 case '1' : flag1 = 1 ; break ; 304 case 'c' : if (!uint0_scan(l.arg, &maxconn)) dieusage() ; break ; 305 case 'C' : if (!uint0_scan(l.arg, &localmaxconn)) dieusage() ; break ; 306 case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ; 307 default : dieusage() ; 308 } 309 } 310 argc -= l.ind ; argv += l.ind ; 311 if (!argc || !*argv[0]) dieusage() ; 312 { 313 struct stat st ; 314 if (fstat(0, &st) < 0) strerr_diefu1sys(111, "fstat stdin") ; 315 if (!S_ISSOCK(st.st_mode)) strerr_dief1x(100, "stdin is not a socket") ; 316 } 317 if (coe(0) < 0) strerr_diefu1sys(111, "make socket close-on-exec") ; 318 if (flag1) 319 { 320 if (fcntl(1, F_GETFD) < 0) 321 strerr_dief1sys(100, "called with option -1 but stdout said") ; 322 } 323 else close(1) ; 324 if (!maxconn) maxconn = 1 ; 325 if (maxconn > ABSOLUTE_MAXCONN) maxconn = ABSOLUTE_MAXCONN ; 326 if (!flaglookup || (localmaxconn > maxconn)) localmaxconn = maxconn ; 327 328 x[0].fd = selfpipe_init() ; 329 if (x[0].fd == -1) strerr_diefu1sys(111, "create selfpipe") ; 330 if (!sig_altignore(SIGPIPE)) strerr_diefu1sys(111, "ignore SIGPIPE") ; 331 { 332 sigset_t set ; 333 sigemptyset(&set) ; 334 sigaddset(&set, SIGCHLD) ; 335 sigaddset(&set, SIGTERM) ; 336 sigaddset(&set, SIGHUP) ; 337 sigaddset(&set, SIGQUIT) ; 338 sigaddset(&set, SIGABRT) ; 339 if (!selfpipe_trapset(&set)) strerr_diefu1sys(111, "trap signals") ; 340 } 341 342 fmtlocalmaxconn[1+uint_fmt(fmtlocalmaxconn+1, localmaxconn)] = 0 ; 343 if (verbosity >= 2) 344 { 345 fmtmaxconn[1+uint_fmt(fmtmaxconn+1, maxconn)] = 0 ; 346 log_start() ; 347 log_status() ; 348 } 349 if (flag1) 350 { 351 fd_write(1, "\n", 1) ; 352 fd_close(1) ; 353 } 354 } 355 356 { 357 piduid_t inyostack0[maxconn] ; 358 uidnum_t inyostack1[flaglookup ? maxconn : 1] ; 359 size_t envlen = env_len((char const *const *)environ) ; 360 piduid = inyostack0 ; 361 uidnum = inyostack1 ; 362 363 while (cont) 364 { 365 if (iopause_g(x, 1 + (numconn < maxconn), 0) < 0) 366 strerr_diefu1sys(111, "iopause") ; 367 368 if (x[0].revents & IOPAUSE_EXCEPT) strerr_dief1x(111, "trouble with selfpipe") ; 369 if (x[0].revents & IOPAUSE_READ) { handle_signals() ; continue ; } 370 if (numconn < maxconn) 371 { 372 if (x[1].revents & IOPAUSE_EXCEPT) strerr_dief1x(111, "trouble with socket") ; 373 if (x[1].revents & IOPAUSE_READ) 374 { 375 int dummy ; 376 char remotepath[IPCPATH_MAX+1] ; 377 int sock = ipc_accept(x[1].fd, remotepath, IPCPATH_MAX+1, &dummy) ; 378 if (sock < 0) 379 { 380 if (verbosity) strerr_warnwu1sys("accept") ; 381 } 382 else 383 { 384 new_connection(sock, remotepath, argv, (char const *const *)environ, envlen) ; 385 fd_close(sock) ; 386 } 387 } 388 } 389 } 390 } 391 if (verbosity >= 2) log_exit() ; 392 return 0 ; 393 }