1 /* 2 * ssh server - serve SSH protocol v2 3 * /net/ssh does most of the work; we copy bytes back and forth 4 */ 5 #include <u.h> 6 #include <libc.h> 7 #include <ip.h> 8 #include <auth.h> 9 #include "ssh2.h" 10 11 char *confine(char *, char *); 12 char *get_string(char *, char *); 13 void newchannel(int, char *, int); 14 void runcmd(int, int, char *, char *, char *, char *); 15 16 int errfd, toppid, sflag, tflag, prevent; 17 int debug; 18 char *idstring; 19 char *netdir; /* /net/ssh/<conn> */ 20 char *nsfile = nil; 21 char *restdir; 22 char *shell; 23 char *srvpt; 24 char *uname; 25 26 void 27 usage(void) 28 { 29 fprint(2, "usage: %s [-i id] [-s shell] [-r dir] [-R dir] [-S srvpt] " 30 "[-n ns] [-t] [netdir]\n", argv0); 31 exits("usage"); 32 } 33 34 static int 35 getctlfd(void) 36 { 37 int ctlfd; 38 char *name; 39 40 name = smprint("%s/clone", netdir); 41 ctlfd = -1; 42 if (name) 43 ctlfd = open(name, ORDWR); 44 if (ctlfd < 0) { 45 syslog(0, "ssh", "server can't clone: %s: %r", name); 46 exits("open clone"); 47 } 48 free(name); 49 return ctlfd; 50 } 51 52 static int 53 getdatafd(int ctlfd) 54 { 55 int fd; 56 char *name; 57 58 name = smprint("%s/data", netdir); 59 fd = -1; 60 if (name) 61 fd = open(name, OREAD); 62 if (fd < 0) { 63 syslog(0, "ssh", "can't open %s: %r", name); 64 hangup(ctlfd); 65 exits("open data"); 66 } 67 free(name); 68 return fd; 69 } 70 71 static void 72 auth(char *buf, int n, int ctlfd) 73 { 74 int fd; 75 76 fd = open("#¤/capuse", OWRITE); 77 if (fd < 0) { 78 syslog(0, "ssh", "server can't open capuse: %r"); 79 hangup(ctlfd); 80 exits("capuse"); 81 } 82 if (write(fd, buf, n) != n) { 83 syslog(0, "ssh", "server write `%.*s' to capuse failed: %r", 84 n, buf); 85 hangup(ctlfd); 86 exits("capuse"); 87 } 88 close(fd); 89 } 90 91 /* 92 * mount tunnel if there isn't one visible. 93 */ 94 static void 95 mounttunnel(int ctlfd) 96 { 97 int fd; 98 char *p, *np, *q; 99 100 if (access(netdir, AEXIST) >= 0) 101 return; 102 103 p = smprint("/srv/%s", srvpt? srvpt: "ssh"); 104 np = strdup(netdir); 105 if (p == nil || np == nil) 106 sysfatal("out of memory"); 107 q = strstr(np, "/ssh"); 108 if (q != nil) 109 *q = '\0'; 110 fd = open(p, ORDWR); 111 if (fd < 0) { 112 syslog(0, "ssh", "can't open %s: %r", p); 113 hangup(ctlfd); 114 exits("open"); 115 } 116 if (mount(fd, -1, np, MBEFORE, "") < 0) { 117 syslog(0, "ssh", "can't mount %s in %s: %r", p, np); 118 hangup(ctlfd); 119 exits("can't mount"); 120 } 121 free(p); 122 free(np); 123 } 124 125 static int 126 authnewns(int ctlfd, char *buf, int size, int n) 127 { 128 char *p, *q; 129 130 USED(size); 131 if (n <= 0) 132 return 0; 133 buf[n] = '\0'; 134 if (strcmp(buf, "n/a") == 0) 135 return 0; 136 137 auth(buf, n, ctlfd); 138 139 p = strchr(buf, '@'); 140 if (p == nil) 141 return 0; 142 ++p; 143 q = strchr(p, '@'); 144 if (q) { 145 *q = '\0'; 146 uname = strdup(p); 147 } 148 if (!tflag && newns(p, nsfile) < 0) { 149 syslog(0, "ssh", "server: newns(%s,%s) failed: %r", p, nsfile); 150 return -1; 151 } 152 return 0; 153 } 154 155 static void 156 listenloop(char *listfile, int ctlfd, char *buf, int size) 157 { 158 int fd, n; 159 160 while ((fd = open(listfile, ORDWR)) >= 0) { 161 n = read(fd, buf, size - 1); 162 fprint(errfd, "read from listen file returned %d\n", n); 163 if (n <= 0) { 164 syslog(0, "ssh", "read on listen failed: %r"); 165 break; 166 } 167 buf[n >= 0? n: 0] = '\0'; 168 fprint(errfd, "read %s\n", buf); 169 170 switch (fork()) { 171 case 0: /* child */ 172 close(ctlfd); 173 newchannel(fd, netdir, atoi(buf)); /* never returns */ 174 case -1: 175 syslog(0, "ssh", "fork failed: %r"); 176 hangup(ctlfd); 177 exits("fork"); 178 } 179 close(fd); 180 } 181 if (fd < 0) 182 syslog(0, "ssh", "listen failed: %r"); 183 } 184 185 void 186 main(int argc, char *argv[]) 187 { 188 char *listfile; 189 int ctlfd, fd, n; 190 char buf[Arbpathlen], path[Arbpathlen]; 191 192 rfork(RFNOTEG); 193 toppid = getpid(); 194 shell = "/bin/rc -il"; 195 ARGBEGIN { 196 case 'd': 197 debug++; 198 break; 199 case 'i': 200 idstring = EARGF(usage()); 201 break; 202 case 'n': 203 nsfile = EARGF(usage()); 204 break; 205 case 'R': 206 prevent = 1; 207 /* fall through */ 208 case 'r': 209 restdir = EARGF(usage()); 210 break; 211 case 's': 212 sflag = 1; 213 shell = EARGF(usage()); 214 break; 215 case 'S': 216 srvpt = EARGF(usage()); 217 break; 218 case 't': 219 tflag = 1; 220 break; 221 default: 222 usage(); 223 break; 224 } ARGEND; 225 226 errfd = -1; 227 if (debug) 228 errfd = 2; 229 230 /* work out network connection's directory */ 231 if (argc >= 1) 232 netdir = argv[0]; 233 else /* invoked by listen1 */ 234 netdir = getenv("net"); 235 if (netdir == nil) { 236 syslog(0, "ssh", "server netdir is nil"); 237 exits("nil netdir"); 238 } 239 syslog(0, "ssh", "server netdir is %s", netdir); 240 241 uname = getenv("user"); 242 if (uname == nil) 243 uname = "none"; 244 245 /* extract dfd and cfd from netdir */ 246 ctlfd = getctlfd(); 247 fd = getdatafd(ctlfd); 248 249 n = read(fd, buf, sizeof buf - 1); 250 if (n < 0) { 251 syslog(0, "ssh", "server read error for data file: %r"); 252 hangup(ctlfd); 253 exits("read cap"); 254 } 255 close(fd); 256 authnewns(ctlfd, buf, sizeof buf, n); 257 258 /* get connection number in buf */ 259 n = read(ctlfd, buf, sizeof buf - 1); 260 buf[n >= 0? n: 0] = '\0'; 261 262 /* tell netssh our id string */ 263 fd2path(ctlfd, path, sizeof path); 264 if (0 && idstring) { /* was for coexistence */ 265 syslog(0, "ssh", "server conn %s, writing \"id %s\" to %s", 266 buf, idstring, path); 267 fprint(ctlfd, "id %s", idstring); 268 } 269 270 /* announce */ 271 fprint(ctlfd, "announce session"); 272 273 /* construct listen file name */ 274 listfile = smprint("%s/%s/listen", netdir, buf); 275 if (listfile == nil) { 276 syslog(0, "ssh", "out of memory"); 277 exits("out of memory"); 278 } 279 syslog(0, "ssh", "server listen is %s", listfile); 280 281 mounttunnel(ctlfd); 282 listenloop(listfile, ctlfd, buf, sizeof buf); 283 hangup(ctlfd); 284 exits(nil); 285 } 286 287 /* an abbreviation. note the assumed variables. */ 288 #define REPLY(s) if (want_reply) fprint(reqfd, s); 289 290 static void 291 forkshell(char *cmd, int reqfd, int datafd, int want_reply) 292 { 293 switch (fork()) { 294 case 0: 295 if (sflag) 296 snprint(cmd, sizeof cmd, "-s%s", shell); 297 else 298 cmd[0] = '\0'; 299 USED(cmd); 300 syslog(0, "ssh", "server starting ssh shell for %s", uname); 301 /* runcmd doesn't return */ 302 runcmd(reqfd, datafd, "con", "/bin/ip/telnetd", "-nt", nil); 303 case -1: 304 REPLY("failure"); 305 syslog(0, "ssh", "server can't fork: %r"); 306 exits("fork"); 307 } 308 } 309 310 static void 311 forkcmd(char *cmd, char *p, int reqfd, int datafd, int want_reply) 312 { 313 char *q; 314 315 switch (fork()) { 316 case 0: 317 if (restdir && chdir(restdir) < 0) { 318 syslog(0, "ssh", "can't chdir(%s): %r", restdir); 319 exits("can't chdir"); 320 } 321 if (!prevent || (q = getenv("sshsession")) && 322 strcmp(q, "allow") == 0) 323 get_string(p+1, cmd); 324 else 325 confine(p+1, cmd); 326 syslog(0, "ssh", "server running `%s' for %s", cmd, uname); 327 /* runcmd doesn't return */ 328 runcmd(reqfd, datafd, "rx", "/bin/rc", "-lc", cmd); 329 case -1: 330 REPLY("failure"); 331 syslog(0, "ssh", "server can't fork: %r"); 332 exits("fork"); 333 } 334 } 335 336 void 337 newchannel(int fd, char *conndir, int channum) 338 { 339 char *p, *reqfile, *datafile; 340 int n, reqfd, datafd, want_reply, already_done; 341 char buf[Maxpayload], cmd[Bigbufsz]; 342 343 close(fd); 344 345 already_done = 0; 346 reqfile = smprint("%s/%d/request", conndir, channum); 347 if (reqfile == nil) 348 sysfatal("out of memory"); 349 reqfd = open(reqfile, ORDWR); 350 if (reqfd < 0) { 351 syslog(0, "ssh", "can't open request file %s: %r", reqfile); 352 exits("net"); 353 } 354 datafile = smprint("%s/%d/data", conndir, channum); 355 if (datafile == nil) 356 sysfatal("out of memory"); 357 datafd = open(datafile, ORDWR); 358 if (datafd < 0) { 359 syslog(0, "ssh", "can't open data file %s: %r", datafile); 360 exits("net"); 361 } 362 while ((n = read(reqfd, buf, sizeof buf - 1)) > 0) { 363 fprint(errfd, "read from request file returned %d\n", n); 364 for (p = buf; p < buf + n && *p != ' '; ++p) 365 ; 366 *p++ = '\0'; 367 want_reply = (*p == 't'); 368 /* shell, exec, and various flavours of failure */ 369 if (strcmp(buf, "shell") == 0) { 370 if (already_done) { 371 REPLY("failure"); 372 continue; 373 } 374 forkshell(cmd, reqfd, datafd, want_reply); 375 already_done = 1; 376 REPLY("success"); 377 } else if (strcmp(buf, "exec") == 0) { 378 if (already_done) { 379 REPLY("failure"); 380 continue; 381 } 382 forkcmd(cmd, p, reqfd, datafd, want_reply); 383 already_done = 1; 384 REPLY("success"); 385 } else if (strcmp(buf, "pty-req") == 0 || 386 strcmp(buf, "window-change") == 0) { 387 REPLY("success"); 388 } else if (strcmp(buf, "x11-req") == 0 || 389 strcmp(buf, "env") == 0 || strcmp(buf, "subsystem") == 0) { 390 REPLY("failure"); 391 } else if (strcmp(buf, "xon-xoff") == 0 || 392 strcmp(buf, "signal") == 0 || 393 strcmp(buf, "exit-status") == 0 || 394 strcmp(buf, "exit-signal") == 0) { 395 ; 396 } else 397 syslog(0, "ssh", "server unknown channel request: %s", 398 buf); 399 } 400 if (n < 0) 401 syslog(0, "ssh", "server read failed: %r"); 402 exits(nil); 403 } 404 405 char * 406 get_string(char *q, char *s) 407 { 408 int n; 409 410 n = nhgetl(q); 411 q += 4; 412 memmove(s, q, n); 413 s[n] = '\0'; 414 q += n; 415 return q; 416 } 417 418 char * 419 confine(char *q, char *s) 420 { 421 int i, n, m; 422 char *p, *e, *r, *buf, *toks[Maxtoks]; 423 424 n = nhgetl(q); 425 q += 4; 426 buf = malloc(n+1); 427 if (buf == nil) 428 return nil; 429 memmove(buf, q, n); 430 buf[n] = 0; 431 m = tokenize(buf, toks, nelem(toks)); 432 e = s + n + 1; 433 for (i = 0, r = s; i < m; ++i) { 434 p = strrchr(toks[i], '/'); 435 if (p == nil) 436 r = seprint(r, e, "%s ", toks[i]); 437 else if (p[0] != '\0' && p[1] != '\0') 438 r = seprint(r, e, "%s ", p+1); 439 else 440 r = seprint(r, e, ". "); 441 } 442 free(buf); 443 q += n; 444 return q; 445 } 446 447 void 448 runcmd(int reqfd, int datafd, char *svc, char *cmd, char *arg1, char *arg2) 449 { 450 char *p; 451 int fd, cmdpid, child; 452 453 cmdpid = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG|RFENVG); 454 switch (cmdpid) { 455 case -1: 456 syslog(0, "ssh", "fork failed: %r"); 457 exits("fork"); 458 case 0: 459 if (restdir == nil) { 460 p = smprint("/usr/%s", uname); 461 if (p && access(p, AREAD) == 0 && chdir(p) < 0) { 462 syslog(0, "ssh", "can't chdir(%s): %r", p); 463 exits("can't chdir"); 464 } 465 free(p); 466 } 467 p = strrchr(cmd, '/'); 468 if (p) 469 ++p; 470 else 471 p = cmd; 472 473 dup(datafd, 0); 474 dup(datafd, 1); 475 dup(datafd, 2); 476 close(datafd); 477 putenv("service", svc); 478 fprint(errfd, "starting %s\n", cmd); 479 execl(cmd, p, arg1, arg2, nil); 480 481 syslog(0, "ssh", "cannot exec %s: %r", cmd); 482 exits("exec"); 483 default: 484 close(datafd); 485 fprint(errfd, "waiting for child %d\n", cmdpid); 486 while ((child = waitpid()) != cmdpid && child != -1) 487 fprint(errfd, "child %d passed\n", child); 488 if (child == -1) 489 fprint(errfd, "wait failed: %r\n"); 490 491 syslog(0, "ssh", "server closing ssh session for %s", uname); 492 fprint(errfd, "closing connection\n"); 493 fprint(reqfd, "close"); 494 p = smprint("/proc/%d/notepg", toppid); 495 if (p) { 496 fd = open(p, OWRITE); 497 fprint(fd, "interrupt"); 498 close(fd); 499 } 500 exits(nil); 501 } 502 } 503