psyclpc/src/authlocal.c

431 lines
13 KiB
C
Executable File
Raw Blame History

/* authlocal.c 1.0.5 - auto-authenticate users connecting from localhost.
*
* The procedure you want to call from your code is getUID,
* which returns a uid_t, or 0 if authentication failed.
* ---->> Be sure to #define USE_AUTHLOCAL if you want it to work! <<----
*
* Remember that uid's under 100 won't be recognized; this is because
* they're normally system daemon accounts, not real users. If you
* need to change this behavior, change AUTHLOCAL_MIN_UID in authlocal.h
* before compiling.
*
* Copyright (C) 2002 Jeremy Monin <jeremy@shadowlands.org>
* http://shadowlands.org/authlocal/
* 1.0.5- 20020704.1140
* Fixed fencepost error re matching lines on buffer boundary.
* 1.0.4- 20020329.0140
* Added linux 2.4 support, many descriptive comments, efficiency
* improvements, and C-comments, not C++-comments (no //)
* original ver. was 19990907.1515
*/
#include <unistd.h> /* for ssize_t, uid_t */
#include <stdio.h>
#include <stdlib.h> /* for atol */
#include <sys/types.h> /* for open */
#include <sys/stat.h> /* for open */
#include <fcntl.h> /* for open */
#include <string.h> /* for memmove */
#include <sys/socket.h> /* this is */
#include <netinet/in.h> /* all for */
#include <arpa/inet.h> /* inet_addr */
#include "authlocal.h"
/* -- for debug --
#define DEBUG_AUTHLOCAL 1
#define USE_AUTHLOCAL 1
char* tcpstat_fname_str = "/proc/net/tcp";
#define TCPSTAT_FNAME tcpstat_fname_str
-- /for debug -- */
/*
* How it Works:
*
* When connecting to a TCP server from localhost, the server knows
* the arbitrary port# you're connecting from... under linux, the file
* /proc/net/tcp contains data about this socket, including its owner.
* This can be used to automatically, securely authenticate.
* See an example at the bottom of this file.
*
* It does lead to the problem of not _wanting_ to log in as yourself,
* but this is a separate issue. Also, be sure root doesn't log in this way.
* We also should check to be sure the logging-in user has a valid shell,
* but we don't right now.
*
* Tested and successful under linux 2.0.x, 2.2.x, 2.4.x
*
* Be sure to define USE_AUTHLOCAL if you want to use this;
* if this isn't defined, the entire getUID function will ALWAYS
* return 0 (failure). This means you don't need to change any of
* your other code, assuming you have a fallback method for authenticating
* people, which you'll need anyway if you want them to be able to
* get in from other hosts.
*/
/*
authlocal.c version 1.0.x - Automatic authentication of connections
from localhost.
Copyright (C) 2000-2002 Jeremy D. Monin (jeremy@shadowlands.org).
This program 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 2 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 this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef USE_AUTHLOCAL
/* yes, most of the file's in this ifdef. */
/* Only thing that isn't is, if USE_AUTHLOCAL
* isn't defined, the function that gets called
* from outside this module always returns 0 (failure),
* meaning you should fall back to standard username-and-pw-prompt
* authentication.
*/
/* format of /proc/net/tcp lines: (as of linux v2.0-2.4, unsure in other vers)
* We want to get the UID.
* sl addy addy st dunno:d d:dunno uh uid timeout inode ...
* \n n: local:port rem:port nn nnnn:nnn nn:nnnn nnn uid n n ...
* ^^^^^^^^ ^^^^^ in hex; each is 8:4 chars
* the ... after inode were added after 2.2.x.
*/
/* PATSIZE is used in allocating buffer space AND in moving the char ptr;
* it MUST be the correct length (incl trailing \0).
*/
#define PATSIZE (8+1+4+1+8+1+4+1) /* "0100007f:xxxx 0100007f:xxxx\0" */
#define LINEBUFSIZ 4096 /* must be much bigger than line len */
#ifndef TCPSTAT_FNAME
#define TCPSTAT_FNAME "/proc/net/tcp"
#endif
/* check to see if we need to read more chars into the buffer;
* if so, do that and adjust the end-of-buffer-data pointer.
* Either way, return it. Return NULL for EOF.
* On entry, *c points to the current buffer pos (the part we must keep).
* (We pass **c so we can change the current buffer pos pointer in the calling
* procedure.)
* *c must be past the start of the buffer. (*c != buf) Otherwise how can
* we have any room to read new data into?
*/
static char *read_chunk (char *buf, char **c, char *bufend, int inputfd)
{
ssize_t nread;
int inbufloc = *c - buf; /* how far into buffer are we? */
/* Must read some into the buffer, but retain partial lines (retain what's
* between *c and *bufend)... First, move current buffer contents up so we
* have room for more, discarding (overwriting) what's currently before *c
* in the buffer.
*/
if (inbufloc > 0)
{
memmove (buf, *c, LINEBUFSIZ - inbufloc);
*c = buf + (LINEBUFSIZ - inbufloc);
/* *c now points right after the end of the current content;
* but in some cases this will be null-terminated, so we need to
* not retain any \0's in the current content.
*/
--(*c);
while (( *c > buf ) && (**c == '\0'))
--(*c);
if (**c != '\0') ++(*c);
/* *c now points to where new stuff should be added; null-term it.
* we'll adjust *c again soon, but this is where we need it for now.
*/
**c = '\0';
}
/* so add the new stuff, try to fill to end of buffer */
nread = read (inputfd, *c, LINEBUFSIZ - (*c - buf) - 1);
if (nread == -1) {
#ifdef DEBUG_AUTHLOCAL
perror ("read");
#endif
return NULL;
}
if (nread == 0) /* EOF */
return NULL;
/* adjust c to start of buf, adjust end of buf ptr and return it */
nread += (*c - buf);
*(buf + nread) = '\0';
*c = buf;
return (buf + nread);
}
/** Begin reading from a file; return the end-of-buffer-data pointer,
* a pointer (inside buf) to the \0 right after the end of where we read,
* or return NULL if we can't read.
*/
static char *buf_begin_get(char *buf, int readfromfd)
{
ssize_t nread = read (readfromfd, buf, LINEBUFSIZ-1);
if (nread == -1) {
return NULL;
}
*(buf + nread) = '\0';
return (buf + nread);
}
/* returns 0 if something goes wrong and it can't be found.
* (UID 0 really shouldn't be logging in from a network anyway.)
*/
static uid_t getUID_impl (unsigned int /* type? */ localhost_ip,
int /* type? */ userportno, int daemonportno,
int inputfd)
{
char wantthis_in_hex[PATSIZE];
char buf[LINEBUFSIZ];
char *b;
char *bufend;
char *c;
sprintf (wantthis_in_hex, "%08X:%04X %08X:%04X",
localhost_ip, (int) userportno, localhost_ip, (int) daemonportno);
# ifdef DEBUG_AUTHLOCAL
//fprintf (stderr, "authlocal wantthis %s\n", wantthis_in_hex);
# endif
/* search for a line containing this */
bufend = buf_begin_get (buf, inputfd);
if (bufend == NULL) {
return (uid_t) 0; /* couldn't read */
}
/* we have c point at our current place in the buffer we search;
* as we read more, read_chunk will adjust c as the buffer moves.
*/
for ( c = buf;
(bufend != NULL) && (*c != '\0') && (c < bufend);
bufend = read_chunk (buf, &c, bufend, inputfd))
{
/* remember our place, in case c is about to become NULL */
b = c;
# ifdef DEBUG_AUTHLOCAL
// fprintf (stderr, "chunk is <20>%s<>\n", b);
# endif
/* try to get c to point to the string we seek */
c = strstr (c, wantthis_in_hex);
if (c != NULL)
{
/* Found a match to our pattern.
* Now get uid and return it; we must skip ahead from where we
* found our src/dest port pair; to go from the end of our match
* to uid, we find and skip 5 blank areas (incl the one immediately
* after end of match).
* If we run out of string (find \0) before then, we have a match on
* a partial line and need to read more of the file into the buffer.
*/
int bkfld;
/* point c at end of pattern */
c += (PATSIZE - 1);
# ifdef DEBUG_AUTHLOCAL
fprintf (stderr, "authlocal found %s\n", wantthis_in_hex);
# endif
/* we're now at white space. We must skip 5 whitespace areas, and
* the 4 fields (non-whitespace) between them. Right after the 5th
* whitespace area is the uid field. We skip over that too, because
* we want to null-terminate that and then go back to its start.
*/
for (bkfld = 1; bkfld <= 5; ++bkfld)
{
/* inch c forward through whitespace */
while (*c <= ' ')
{
if (*c == '\0')
{
/* we're on a partial line, must read more file into
* the buffer and then retry the search.
*/
c = NULL;
break;
}
++c;
}
if (c == NULL) break;
/* now inch c forward through non-whitespace */
while (*c > ' ')
++c;
}
if (c != NULL)
{
/* we're now right after end of UID; null-terminate it and move back */
*c = '\0'; --c;
/* this is the field data; inch backward to next whitespace */
while (*c > ' ')
--c;
/* now nudge forward and read it! have a nice day,
* we're done.
*/
++c;
/* read it */
return (uid_t) atol(c); /* <-------- return the uid ------ */
} /* end if c-not-null (partial line after match) */
} /* end if c-not-null (no match at all) */
/* not found, grab more (to search through) from file into buffer, but
* we must retain the entire last line or two of the current buffer, in
* case we have just part of that line right now, and it's the right line
* but we don't know it because we don't have the entire line.
*/
c = bufend - 1;
/* find start of final line: */
while ((*c != '\n') && (c != buf))
--c;
if ((c == buf) && (*c != '\n'))
{
/* found no \n in the whole buffer; a very long line.
* the line must go away to make room. (not likely to happen)
*/
c = bufend;
} else {
char *lastlinec = c; /* remember start of final line */
/* find start of next-to-final line: */
while ((*c != '\n') && (c != buf))
--c;
if ((c == buf) && (*c != '\n'))
c = lastlinec; /* can't keep all of the 2 lines: keep 1 */
}
} /* end while-not-eof loop */
return (uid_t) 0; /* <-------- couldn't find it. ---------- */
}
#endif /* the big IFDEF */
/* Finally, here's what we need!
* Returns 0 if something goes wrong and it can't be found.
* (UID 0 really shouldn't be logging in from a network anyway.)
*/
uid_t getUID (unsigned int localhost_ip,
int userport, int daemonport)
{
#ifndef USE_AUTHLOCAL
# ifdef DEBUG_AUTHLOCAL
fprintf (stderr, "authlocal disabled\n");
# endif
return (uid_t) 0;
#else
uid_t retval;
int fd = open(TCPSTAT_FNAME, O_RDONLY);
if (fd == -1) {
#ifdef DEBUG_AUTHLOCAL
fprintf (stderr, "authlocal could not open " TCPSTAT_FNAME "\n");
#endif
return (uid_t) 0;
}
retval = getUID_impl (localhost_ip, userport, daemonport, fd);
# ifdef DEBUG_AUTHLOCAL
fprintf (stderr, "authlocal got uid %d for connection from %d on %d\n",
retval, userport, daemonport);
# endif
close (fd);
if (retval > AUTHLOCAL_MIN_UID)
return retval;
else
return 0;
#endif /* ifdef (USE_AUTHLOCAL) */
}
/* code for usage demonstration purposes. Note that it's commented out.
static struct in_addr localhost_ip;
localhost_ip.s_addr = htonl(0x7f000001);
...
struct sockaddr_in user_address;
int user_fd = accept
( server_fd, (sockaddr *)&user_address, &len );
...
uid_t authenticated = 0;
if (user_address.sin_addr.s_addr == localhost_ip.s_addr ) {
Auto-Authenticate user if logging in from localhost!
authenticated = getUID
( localhost_ip.s_addr, ntohs(user_address.sin_port),
server_portnum );
}
int main(int argc, char **argv)
{
int pnum_cli = 2048;
int pnum_srv = 5000;
int retuid;
if (argc > 1)
{
if (strncasecmp(argv[1], "0x", 2))
pnum_cli = (int) strtol(argv[1], (char **)NULL, 10);
else
pnum_cli = (int) strtol(argv[1], (char **)NULL, 16);
}
if (argc > 2)
{
if (strncasecmp(argv[2], "0x", 2))
pnum_srv = (int) strtol(argv[2], (char **)NULL, 10);
else
pnum_srv = (int) strtol(argv[2], (char **)NULL, 16);
}
#ifdef DEBUG_AUTHLOCAL
if (argc > 3)
tcpstat_fname_str = argv[3];
printf ("file for /proc/net/tcp is %s\n", tcpstat_fname_str);
#endif
printf ("lookin' for a connection from port %d (0x%04X) to %d (0x%04X)\n",
pnum_cli, pnum_cli, pnum_srv, pnum_srv);
retuid = (int) getUID ( inet_addr ("127.0.0.1"), pnum_cli, pnum_srv);
printf ("returned %d\n", retuid);
return (retuid ? 0 : 1);
}
authlocal.c ENDS. */