[BACK]Return to TCP_Client.c++ CVS log [TXT][DIR] Up to [Development] / fam / fam

File: [Development] / fam / fam / TCP_Client.c++ (download)

Revision 1.1.1.1 (vendor branch), Thu Apr 24 19:08:27 2003 UTC (14 years, 6 months ago) by trev
Branch: sgi-fam, MAIN
CVS Tags: fam-2-6-10, HEAD
Changes since 1.1: +0 -0 lines

Initial FAM CVS repository build..

-- Trev


//  Copyright (C) 1999 Silicon Graphics, Inc.  All Rights Reserved.
//  
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of version 2 of the GNU General Public License as
//  published by the Free Software Foundation.
//
//  This program is distributed in the hope that it would be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  Further, any
//  license provided herein, whether implied or otherwise, is limited to
//  this program in accordance with the express provisions of the GNU
//  General Public License.  Patent licenses, if any, provided herein do not
//  apply to combinations of this program with other product or programs, or
//  any other product whatsoever.  This program is distributed without any
//  warranty that the program is delivered free of the rightful claim of any
//  third person by way of infringement or the like.  See the GNU General
//  Public License for more details.
//
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write the Free Software Foundation, Inc., 59
//  Temple Place - Suite 330, Boston MA 02111-1307, USA.

#include "TCP_Client.h"

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "Cred.h"
#include "Event.h"
#include "Interest.h"
#include "Log.h"
#include "Scanner.h"
#include "Listener.h"

//////////////////////////////////////////////////////////////////////////////
//  Construction/destruction

TCP_Client::TCP_Client(in_addr host, int fd, Cred &cr)
    : MxClient(host), cred(cr), my_scanner(NULL),
      conn(fd, input_handler, unblock_handler, this),
      insecure_compat_suggested(false)
{
    assert(fd >= 0);

    // Set client's name.

    char namebuf[20];
    sprintf(namebuf, "client %d", fd);
    name(namebuf);
    Log::debug("new connection from %s", name());
}

TCP_Client::~TCP_Client()
{
}

//////////////////////////////////////////////////////////////////////////////
//  Input

bool
TCP_Client::input_handler(const char *msg, unsigned nbytes, void *closure)
{
    TCP_Client *client = (TCP_Client *) closure;
    if (msg) {
	return client->input_msg(msg, nbytes);
    }
    else
    {   Log::debug("lost connection from %s", client->name());
	delete client;			// NULL msg means connection closed.
        return true;
    }
}

bool
TCP_Client::input_msg(const char *msg, int size)
{
    // Parse the first message.
    // The first message is:
    //	    opcode, request number, uid, gid, file name.

    // If there's an error parsing the message, return false to cause
    // the connection to the misbehaving client to be closed.
    
    bool got_N_with_groups = false;
    char *p = (char *) msg, *q;
    char opcode = *p++;
    Request reqnum = strtol(p, &q, 10);
    if (p == q)
    {
	Log::error("bad message (no request id)");
        return false;
    }
    p = q;
    uid_t uid = strtol(p, &q, 10);
    if (p == q)
    {
	Log::error("bad message (no uid)");
        return false;
    }
    p = q;
    gid_t gid = strtol(p, &q, 10);
    if (p == q)
    {
	Log::error("bad message (no gid)");
        return false;
    }
    p = q;

    // Advance past the space
    p++;
    
    char filename[PATH_MAX + 1];
    int i;
    for (i = 0; *p; i++)
    {   if (i >= PATH_MAX)
	{   Log::error("%s path name too long (%d chars)", name(), i);
	    return false;
	}
	filename[i] = *p++;
    }
    filename[i] = '\0';
    if (i > 0 && filename[i - 1] == '\n')
	filename[i - 1] = '\0';		// strip the trailing newline
    p++;

    // Parse the second message, if any.
    // The second message is:
    //	    ngroups, group, group, group ...

    //  This is a raging kludge.  got_N_with_groups is how a client says
    //  "create a UNIX domain socket for local communication with me."
    //  You guessed that, right?
    //
    //  The reason it's done this way, rather than by having fam listen
    //  for connections from local clients on a unix domain socket in addition
    //  to the internet domain socket, is that when fam is started by inetd,
    //  the first client would have to connect on the internet domain socket
    //  anyway to make inetd exec fam.
    if ((opcode == 'N') && (p < msg + size - 1)) got_N_with_groups = true;

    //  If no Cred is set on the connection, that means we trust the uid
    //  & gids supplied in the message.
    Cred msg_cred = cred;
    if (!msg_cred.is_valid())
    {
        gid_t *grouplist = &gid;
        int ngroups = 1;
        if (p < msg + size - 1)
        {
            ngroups += strtol(p, &p, 10);
            int maxgroups = sysconf(_SC_NGROUPS_MAX);

            if (ngroups > maxgroups)
            {
                Log::info("message contained %i groups, but group list was"
                          " truncated to %i because of _SC_NGROUPS_MAX",
                          ngroups, maxgroups);
                ngroups = maxgroups;
            }
            grouplist = new gid_t[ngroups];
            grouplist[0] = gid;
	    for (int i = 1; i < ngroups; i++)
	    {
	        grouplist[i] = strtol(p, &q, 10);
	        if (p == q)
                {
	            Log::error("bad message (%d additional groups expected, %d found)", ngroups - 1, i);
                    ngroups = i;
                    break;
                }
	        p = q;
	    }
        }

        Cred c(uid, ngroups, grouplist, conn.get_fd());
        msg_cred = c;
        if (grouplist != &gid) delete [] grouplist;
    }

    // Process the message.

    switch (opcode)
    {
    case 'W':				// Monitor File
    {
	Log::debug("%s said: request %d monitor file \"%s\"",
		   name(), reqnum, filename);
	monitor_file(reqnum, filename, msg_cred);
	break;
    }
    case 'M':				// Monitor Directory
	Log::debug("%s said: request %d monitor dir \"%s\"",
		   name(), reqnum, filename);
	monitor_dir(reqnum, filename, msg_cred);
	break;

    case 'C':				// Cancel
	Log::debug("%s said: cancel request %d", name(), reqnum);
	cancel(reqnum);
	break;

    case 'S':				// Suspend
	Log::debug("%s said: suspend request %d", name(), reqnum);
	MxClient::suspend(reqnum);
	break;

    case 'U':				// Resume
	Log::debug("%s said: resume request %d", name(), reqnum);
	MxClient::resume(reqnum);
	break;

    case 'N':				// Client Name
	Log::debug("%s said: %s is %s, and %s a unix domain socket",
                   name(), name(), filename,
                   (got_N_with_groups ? "wants" : "doesn't want"));
	if (*filename && strcmp(filename, "test"))
	    name(filename);		// set this client's name

        //  Check to see whether this client wants to switch to a unix
        //  domain socket.  Create it owned by the uid the client says
        //  they're running as.
        if (got_N_with_groups) Listener::create_local_client(*this, uid);

	break;

    //
    //  Ignore these obsolete messages.
    //
    case 'D':
    case 'V':
    case 'E':
	break;

    default:
	Log::error("%s said unknown request '%c' ('\\%3o')",
		   name(), opcode, opcode & 0377);
        return false;
    }

    return true;
}

//////////////////////////////////////////////////////////////////////////////
//  Output

void
TCP_Client::unblock_handler(void *closure)
{
    TCP_Client *client = (TCP_Client *) closure;

    //  Continue scanner, if any.

    Scanner *scanner = client->my_scanner;
    if (scanner)
    {	if (scanner->done())
	    client->my_scanner = NULL;
	else
	    return;
    }

    //  After scanner has run, scan more interests.

    Interest *ip;
    while (client->ready_for_events() && (ip = client->to_be_scanned.first()))
    {   client->to_be_scanned.remove(ip);
	ip->scan();
    }

    //  Enable input if all enqueued work is done.

    if (client->ready_for_events())
	client->conn.ready_for_input(true);
}

bool
TCP_Client::ready_for_events()
{
    return conn.ready_for_output();
}

void
TCP_Client::enqueue_for_scan(Interest *ip)
{
    if (!to_be_scanned.size())
	conn.ready_for_input(false);
    to_be_scanned.insert(ip);
}

void
TCP_Client::dequeue_from_scan(Interest *ip)
{
    to_be_scanned.remove(ip);
    if (!to_be_scanned.size())
	conn.ready_for_input(true);
}

void
TCP_Client::enqueue_scanner(Scanner *sp)
{
    assert(!my_scanner);
    my_scanner = sp;
    conn.ready_for_input(false);
}

void
TCP_Client::post_event(const Event& event, Request request, const char *path)
{
    conn.send_event(event, request, path);
    Log::debug("sent event to %s: request %d \"%s\" %s",
	       name(), request, path, event.name());
}

//////////////////////////////////////////////////////////////////////////////
//  Random kludges

//  If we're not running in insecure_compatibility mode,
//  and we haven't already logged a message for this connection,
//  put a message in the syslog saying "this client was denied...
//  try insecure_compat mode."
void
TCP_Client::suggest_insecure_compat(const char *path)
{
    if (insecure_compat_suggested) return;

    //  if cred isn't valid it could be a remote connection.
    if ((cred.is_valid()) &&
        (!Cred::insecure_compat_enabled()) &&
        (cred.uid() == Cred::get_untrusted_uid()))
    {
        //  XXX add a config option to turn off this message!
        Log::log(Log::INFO, "Client \"%s\", whose requests are being serviced "
                 "as uid %d, was denied access on %s.  If this client might "
                 "be running as uid %d because it failed authentication, you "
                 "might disable authentication.  See the fam(1M) man page.",
                 name(), cred.uid(), path, cred.uid());
        insecure_compat_suggested = true;
    }
}