Annotation of fam/fam/Listener.c++, Revision 1.1
1.1 ! trev 1: // Copyright (C) 1999 Silicon Graphics, Inc. All Rights Reserved.
! 2: //
! 3: // This program is free software; you can redistribute it and/or modify it
! 4: // under the terms of version 2 of the GNU General Public License as
! 5: // published by the Free Software Foundation.
! 6: //
! 7: // This program is distributed in the hope that it would be useful, but
! 8: // WITHOUT ANY WARRANTY; without even the implied warranty of
! 9: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Further, any
! 10: // license provided herein, whether implied or otherwise, is limited to
! 11: // this program in accordance with the express provisions of the GNU
! 12: // General Public License. Patent licenses, if any, provided herein do not
! 13: // apply to combinations of this program with other product or programs, or
! 14: // any other product whatsoever. This program is distributed without any
! 15: // warranty that the program is delivered free of the rightful claim of any
! 16: // third person by way of infringement or the like. See the GNU General
! 17: // Public License for more details.
! 18: //
! 19: // You should have received a copy of the GNU General Public License along
! 20: // with this program; if not, write the Free Software Foundation, Inc., 59
! 21: // Temple Place - Suite 330, Boston MA 02111-1307, USA.
! 22:
! 23: #include "Listener.h"
! 24:
! 25: #include <assert.h>
! 26: #include <fcntl.h>
! 27: #include <stdlib.h>
! 28: #include <sys/types.h>
! 29: #include <netinet/in.h>
! 30: #include <arpa/inet.h> // for inet_ntoa
! 31: #include <rpc/rpc.h>
! 32: #include <rpc/pmap_clnt.h>
! 33: #include <rpc/clnt.h>
! 34: #include <sys/ioctl.h>
! 35: #include <sys/socket.h>
! 36: #include <sys/stat.h>
! 37: #include <sys/un.h>
! 38: #include <unistd.h>
! 39:
! 40: #include <errno.h>
! 41:
! 42: #include "Log.h"
! 43: #include "LocalClient.h"
! 44: #include "Scheduler.h"
! 45: #include "Cred.h"
! 46: #include "BTree.h"
! 47:
! 48: #if !(HAVE_BINDRESVPORT_PROTO)
! 49: extern "C" int bindresvport(int sd, struct sockaddr_in *);
! 50: #endif
! 51:
! 52: struct NegotiatingClient
! 53: {
! 54: NegotiatingClient(int fd, uid_t u, struct sockaddr_un *s);
! 55: int sock;
! 56: uid_t uid;
! 57: struct sockaddr_un sun;
! 58: };
! 59:
! 60: BTree<int, NegotiatingClient *> negotiating_clients;
! 61:
! 62: static void cleanup_negotiation(void *closure);
! 63:
! 64: Listener::Listener(bool sbi, bool lo, unsigned long p, unsigned long v)
! 65: : program(p),
! 66: version(v),
! 67: rendezvous_fd(-1),
! 68: started_by_inetd(sbi),
! 69: _ugly_sock(-1),
! 70: local_only(lo)
! 71: {
! 72: if (started_by_inetd)
! 73: {
! 74: // Portmapper already knows about
! 75: // us, so just wait for the requests to start rolling in.
! 76:
! 77: set_rendezvous_fd(RENDEZVOUS_FD);
! 78:
! 79: dirty_ugly_hack();
! 80: }
! 81: else
! 82: {
! 83: // Need to register with portmapper.
! 84: // Unless we're debugging, fork and close all descriptors.
! 85:
! 86: int sock = socket(PF_INET, SOCK_STREAM, 0);
! 87: if (sock < 0)
! 88: { Log::perror("can't create TCP/IP socket for rendezvous");
! 89: exit(1);
! 90: }
! 91: struct sockaddr_in addr;
! 92: memset(&addr, 0, sizeof addr);
! 93: addr.sin_family = AF_INET;
! 94: addr.sin_addr.s_addr = local_only ? htonl(INADDR_LOOPBACK) : 0;
! 95: addr.sin_port = htons(0);
! 96: if (bindresvport(sock, &addr) < 0)
! 97: {
! 98: Log::perror("can't bind to reserved port");
! 99: exit(1);
! 100: }
! 101: if (listen(sock, 1) < 0)
! 102: {
! 103: Log::perror("can't listen for rendezvous");
! 104: exit(1);
! 105: }
! 106: (void) pmap_unset(program, version);
! 107: if (!pmap_set(program, version, IPPROTO_TCP, ntohs(addr.sin_port)))
! 108: {
! 109: Log::error("can't register with portmapper.");
! 110: exit(1);
! 111: }
! 112: set_rendezvous_fd(sock);
! 113: }
! 114: }
! 115:
! 116: Listener::~Listener()
! 117: {
! 118: if (rendezvous_fd >= 0)
! 119: { if (!started_by_inetd)
! 120: pmap_unset(program, version);
! 121: int rc = close(rendezvous_fd);
! 122: if (rc < 0)
! 123: Log::perror("close rendezvous port(%d)", rendezvous_fd);
! 124: else
! 125: Log::debug("fam service closed");
! 126: (void) Scheduler::remove_read_handler(rendezvous_fd);
! 127: }
! 128: if (_ugly_sock >= 0)
! 129: { (void) close(_ugly_sock);
! 130: (void) unlink("/tmp/.fam_socket");
! 131: }
! 132: }
! 133:
! 134: void
! 135: Listener::set_rendezvous_fd(int newfd)
! 136: {
! 137: if (rendezvous_fd >= 0)
! 138: { (void) Scheduler::remove_read_handler(rendezvous_fd);
! 139: (void) close(rendezvous_fd);
! 140: }
! 141: rendezvous_fd = newfd;
! 142: if (rendezvous_fd >= 0)
! 143: { (void) Scheduler::install_read_handler(rendezvous_fd,
! 144: accept_client, this);
! 145: Log::debug("listening for clients on descriptor %d", rendezvous_fd);
! 146: }
! 147: }
! 148:
! 149: void
! 150: Listener::accept_client(int rendezvous_fd, void *closure)
! 151: {
! 152: Listener *listener = (Listener *)closure;
! 153:
! 154: // Get the new socket.
! 155:
! 156: struct sockaddr_in addr;
! 157: CONFIG_SOCKLEN_T addrlen = sizeof addr;
! 158: int client_fd = accept(rendezvous_fd, (struct sockaddr *) &addr, &addrlen);
! 159: if (client_fd < 0)
! 160: {
! 161: Log::perror("failed to accept new client");
! 162: return;
! 163: }
! 164:
! 165: if (ntohl(addr.sin_addr.s_addr) == INADDR_LOOPBACK)
! 166: {
! 167: Log::debug("client fd %d is local/untrusted.", client_fd);
! 168: // Client is local.
! 169:
! 170: Cred cred = Cred::get_cred_for_untrusted_conn(client_fd);
! 171: new TCP_Client(addr.sin_addr, client_fd, cred);
! 172: // We don't need a reference to this object. The constructor
! 173: // takes care of registering it with the Scheduler.
! 174: }
! 175: else
! 176: {
! 177: assert(!listener->local_only);
! 178:
! 179: // Check that client is superuser.
! 180: Cred cred;
! 181: if (ntohs(addr.sin_port) >= IPPORT_RESERVED)
! 182: {
! 183: // Client isn't connecting on a privileged port, so we will
! 184: // ignore who they say they are, and treat them as our
! 185: // untrusted-user.
! 186: cred = Cred::get_cred_for_untrusted_conn(client_fd);
! 187: }
! 188: Log::debug("client fd %d from %s is remote/%strusted",
! 189: client_fd, inet_ntoa(addr.sin_addr),
! 190: cred.is_valid() ? "un" : "");
! 191:
! 192: new TCP_Client(addr.sin_addr, client_fd, cred);
! 193: // We don't need a reference to this object. The constructor
! 194: // takes care of registering it with the Scheduler.
! 195: }
! 196: }
! 197:
! 198: void
! 199: Listener::create_local_client(TCP_Client &inet_client, uid_t uid)
! 200: {
! 201: // This TCP_Client wants to use a UNIX domain socket instead of the
! 202: // inet socket for communication. Create the new socket owned by the
! 203: // requested user and pass the name back to the client.
! 204:
! 205: // Unset TMPDIR to ensure that tempnam() works as desired
! 206: putenv("TMPDIR=");
! 207:
! 208: char *tmpfile = tempnam("/tmp", ".fam");
! 209: #if defined(__FreeBSD__)
! 210: sockaddr_un sun = { sizeof(sockaddr_un), AF_UNIX, "" };
! 211: #else
! 212: sockaddr_un sun = { AF_UNIX, "" };
! 213: #endif
! 214: if(strlen(tmpfile) >= (sizeof(sun.sun_path) - 1))
! 215: {
! 216: Log::error("tmpnam() too long for sun_path (%d >= %d)!",
! 217: strlen(tmpfile), sizeof(sun.sun_path) - 1);
! 218: free(tmpfile);
! 219: return;
! 220: }
! 221: strcpy(sun.sun_path, tmpfile);
! 222: free(tmpfile);
! 223:
! 224: Cred::SuperUser.become_user();
! 225: int client_sock = socket(PF_UNIX, SOCK_STREAM, 0);
! 226: if (client_sock < 0)
! 227: { Log::perror("localclient socket(PF_UNIX, SOCK_STREAM, 0)");
! 228: return;
! 229: }
! 230:
! 231: Log::debug("client %s said uid %d; creating %s",
! 232: inet_client.name(), uid, sun.sun_path);
! 233:
! 234: unlink(sun.sun_path);
! 235: if (bind(client_sock, (sockaddr *) &sun, sizeof sun) != 0)
! 236: { Log::perror("localclient bind");
! 237: close(client_sock);
! 238: return;
! 239: }
! 240:
! 241: if (chmod(sun.sun_path, 0600) != 0)
! 242: { Log::perror("localclient chmod");
! 243: close(client_sock);
! 244: return;
! 245: }
! 246:
! 247: if (chown(sun.sun_path, uid, (gid_t)-1) != 0)
! 248: { Log::perror("localclient chown");
! 249: close(client_sock);
! 250: return;
! 251: }
! 252:
! 253: // Since we're going to start listening on this socket, set a task
! 254: // to clean it up if we don't receive a connection within 60 seconds.
! 255: NegotiatingClient *nc = new NegotiatingClient(client_sock, uid, &sun);
! 256: negotiating_clients.insert(client_sock, nc);
! 257: timeval nto;
! 258: gettimeofday(&nto, NULL);
! 259: nto.tv_sec += 60; // XXX that should be configurable
! 260: Scheduler::install_onetime_task(nto, cleanup_negotiation, nc);
! 261:
! 262: if (listen(client_sock, 1) != 0)
! 263: { Log::perror("localclient listen");
! 264: close(client_sock);
! 265: return;
! 266: }
! 267:
! 268: Scheduler::install_read_handler(client_sock, accept_localclient, NULL);
! 269: Log::debug("listening for requests for uid %d on descriptor %d (%s)",
! 270: uid, client_sock, sun.sun_path);
! 271:
! 272: inet_client.send_sockaddr_un(sun);
! 273: }
! 274:
! 275: void
! 276: Listener::accept_localclient(int ofd, void *)
! 277: {
! 278: NegotiatingClient *nc = negotiating_clients.find(ofd);
! 279: assert(nc);
! 280:
! 281: // Get the new socket.
! 282:
! 283: #if defined(__FreeBSD__)
! 284: struct sockaddr_un sun = { sizeof(sockaddr_un), AF_UNIX, "" };
! 285: #else
! 286: struct sockaddr_un sun = { AF_UNIX, "" };
! 287: #endif
! 288: CONFIG_SOCKLEN_T sunlen = sizeof(sun);
! 289: int client_fd = accept(ofd, (struct sockaddr *) &sun, &sunlen);
! 290: if (client_fd < 0)
! 291: {
! 292: Log::perror("failed to accept new client");
! 293: return;
! 294: }
! 295:
! 296: // Keep the scheduler from helpfully cleaning this up.
! 297: Scheduler::remove_onetime_task(cleanup_negotiation, nc);
! 298:
! 299: Log::debug("client fd %d is local/trusted (socket %s, uid %d).",
! 300: client_fd, nc->sun.sun_path, nc->uid);
! 301: Cred cred(nc->uid, client_fd);
! 302: new LocalClient(client_fd, &(nc->sun), cred);
! 303: // We don't need a reference to this object. The constructor
! 304: // takes care of registering it with the Scheduler.
! 305:
! 306: // Stop listening for new connections on that socket.
! 307: Scheduler::remove_read_handler(ofd);
! 308: close(ofd);
! 309:
! 310: // This client is no longer negotiating, so remove it from the list
! 311: // and delete it.
! 312: negotiating_clients.remove(ofd);
! 313: delete nc;
! 314: }
! 315:
! 316: // Here's the problem. If the network is stopped and restarted,
! 317: // inetd and portmapper are killed and new ones are launched in their
! 318: // place. Since they have no idea that fam is already running, inetd
! 319: // opens a new TCP/IP socket and registers it with the new portmapper.
! 320: // Meanwhile, the old fam has /dev/imon open, and any new fam launched
! 321: // by the new inetd won't be able to use imon. The old fam can't just
! 322: // quit; it has all the state of all its existing clients to contend with.
! 323:
! 324: // So here's the dirty, ugly, hack solution. The first fam to run is
! 325: // the master. The master opens the UNIX domain socket,
! 326: // /tmp/.fam_socket, and listens for connections from slaves. Each
! 327: // subsequent fam is a slave. It knows it's a slave because it can
! 328: // connect to the master. The slave passes its rendezvous descriptor
! 329: // (the one that fam clients will connect to) through to the master
! 330: // using the access rights mechanism of Unix domain sockets. The
! 331: // master receives the new descriptor and starts accepting clients on
! 332: // it. Meanwhile, the slave blocks, waiting for the master to close
! 333: // the connection on /tmp/.fam_socket. That happens when the master
! 334: // exits.
! 335:
! 336: // This master/slave stuff has nothing to do with fam forwarding
! 337: // requests to fams on other hosts. That's called client/server
! 338: // fams.
! 339:
! 340: // Why not just kill fam when we kill the rest of the network
! 341: // services? Because too many programs need it. The desktop, the
! 342: // desktop sysadmin tools, MediaMail... None of those should go down
! 343: // when the network goes down, and none of them are designed to
! 344: // handle fam dying.
! 345:
! 346: void
! 347: Listener::dirty_ugly_hack()
! 348: {
! 349: #if defined(__FreeBSD__)
! 350: static sockaddr_un sun = { sizeof (sockaddr_un), AF_UNIX, "/tmp/.fam_socket" };
! 351: #else
! 352: static sockaddr_un sun = { AF_UNIX, "/tmp/.fam_socket" };
! 353: #endif
! 354:
! 355: int sock = socket(PF_UNIX, SOCK_STREAM, 0);
! 356: if (sock < 0)
! 357: { Log::perror("socket(PF_UNIX, SOCK_STREAM, 0)");
! 358: exit(1);
! 359: }
! 360:
! 361: struct stat sb;
! 362: if (lstat(sun.sun_path, &sb) == 0 &&
! 363: sb.st_uid == 0 && S_ISSOCK(sb.st_mode))
! 364: {
! 365: if (connect(sock, (sockaddr *) &sun, sizeof sun) == 0)
! 366: {
! 367: // Another fam is listening to /tmp/.fam_socket.
! 368: // Pass our rendezvous fd to the other fam and
! 369: // sleep until that fam exits.
! 370:
! 371: int rights[1] = { rendezvous_fd };
! 372: msghdr msg = { NULL, 0, NULL, 0, (caddr_t) rights, sizeof rights };
! 373: if (sendmsg(sock, &msg, 0) < 0)
! 374: { Log::perror("sendmsg");
! 375: exit(1);
! 376: }
! 377: Log::debug("fam (process %d) enslaved to master fam", getpid());
! 378: char data[1];
! 379: int nb = read(sock, &data, sizeof data);
! 380: assert(nb == 0);
! 381: exit(0);
! 382: }
! 383: else
! 384: Log::debug("could not enslave myself: %m");
! 385: }
! 386:
! 387: // We were unable to connect to another fam.
! 388: // We'll create our own dirty ugly hack socket and accept connections.
! 389:
! 390: (void) unlink(sun.sun_path);
! 391: if (bind(sock, (sockaddr *) &sun, sizeof sun) != 0)
! 392: { Log::perror("bind");
! 393: exit(1);
! 394: }
! 395:
! 396: if (chmod(sun.sun_path, 0700) != 0)
! 397: { Log::perror("chmod");
! 398: exit(1);
! 399: }
! 400:
! 401: if (listen(sock, 1) != 0)
! 402: { Log::perror("listen");
! 403: exit(1);
! 404: }
! 405:
! 406: (void) Scheduler::install_read_handler(sock, accept_ugly_hack, this);
! 407: _ugly_sock = sock;
! 408: Log::debug("fam (process %d) is master fam.", getpid());
! 409: }
! 410:
! 411: void
! 412: Listener::accept_ugly_hack(int ugly, void *closure)
! 413: {
! 414: Listener *listener = (Listener *) closure;
! 415: assert(ugly == listener->_ugly_sock);
! 416:
! 417: // Accept a new ugly connection.
! 418:
! 419: struct sockaddr_un sun;
! 420: CONFIG_SOCKLEN_T sunlen = sizeof sun;
! 421: int sock = accept(ugly, (struct sockaddr *)(&sun), &sunlen);
! 422: if (sock < 0)
! 423: { Log::perror("accept");
! 424: return;
! 425: }
! 426:
! 427: (void) Scheduler::install_read_handler(sock, read_ugly_hack, listener);
! 428: }
! 429:
! 430: void
! 431: Listener::read_ugly_hack(int sock, void *closure)
! 432: {
! 433: Listener *listener = (Listener *) closure;
! 434:
! 435: int rights[1] = { -1 };
! 436: msghdr msg = { NULL, 0, NULL, 0, (caddr_t) rights, sizeof rights };
! 437: if (recvmsg(sock, &msg, 0) >= 0)
! 438: {
! 439: Log::debug("master fam (process %d) has new slave", getpid());
! 440:
! 441: assert(rights[0] >= 0);
! 442: listener->set_rendezvous_fd(rights[0]);
! 443:
! 444: // Turn the inactivity timeout all the way down. We want to
! 445: // clean up this dirty ugly hack as soon as possible after
! 446: // the last Activity is destroyed.
! 447: Activity::timeout(0);
! 448:
! 449: // Forget socket -- it will be closed on exit.
! 450: // Slave is blocked waiting for socket to close, so slave
! 451: // will exit when master exits.
! 452:
! 453: (void) Scheduler::remove_read_handler(sock);
! 454: }
! 455: else
! 456: { Log::perror("recvmsg");
! 457: (void) Scheduler::remove_read_handler(sock);
! 458: (void) close(sock);
! 459: }
! 460: }
! 461:
! 462: NegotiatingClient::NegotiatingClient(int fd, uid_t u, struct sockaddr_un *sunp)
! 463: : sock(fd), uid(u)
! 464: {
! 465: sun.sun_family = AF_UNIX;
! 466: strcpy(sun.sun_path, sunp->sun_path);
! 467: }
! 468:
! 469: static void
! 470: cleanup_negotiation(void *closure)
! 471: {
! 472: NegotiatingClient *nc = (NegotiatingClient *)closure;
! 473:
! 474: Log::debug("cleaning up incomplete negotiation for client %d", nc->sock);
! 475:
! 476: // Remove client from list
! 477: negotiating_clients.remove(nc->sock);
! 478:
! 479: // Remove the read handler & close the socket
! 480: Scheduler::remove_read_handler(nc->sock);
! 481: close(nc->sock);
! 482: nc->sock = -1;
! 483:
! 484: // Remove the temp file
! 485: uid_t preveuid = geteuid();
! 486: if (preveuid) seteuid(0);
! 487: seteuid(nc->uid);
! 488: unlink(nc->sun.sun_path);
! 489: if (nc->uid) seteuid(0);
! 490: seteuid(preveuid);
! 491:
! 492: delete nc;
! 493: }
! 494:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>