To me, it has been a glaring, yawning gulf in the lineup of pam modules that there hasn't been a module to automatically log in a user according to their IP, or TTY. I appreciate that there are security issues involved with this, but in my situation it's necessary to do this to set up single-purpose workstations. That is, terminals that when turned on, start up and run particular programs, and logout when that program exits. There are endless practical applications for this - information booths, data entry terminals, and so on, where you want a machine to have a particular single function, and you particularly care who operates it. I beilieve that this is important, vital even, but clearly not many other people feel the same way, or there would already be a solution available. So, much as I was reluctant to do this, I've done something myself - shamelessly cobbled together from various other modules. And guess what ? It works ! It does what I want it to do. But I thought that I should bounce this of this mailing list to see if persons more knowledgable than myself can assist in improving this. I'm aware that the code is rough, in particular, I was uncertain about how to finish and grant access. As I say, what is here works, but maybe it can be improved. The idea is : a configuration file, hard coded as /etc/autologin.conf as follows: machine1.test.net boris ttyS1 natasha There are two "fields" to the file. The first is either an IP or TTY, the second entry is the user to be logged in as. /etc/pam.d/login has as it's first line: auth sufficient /lib/security/pam_autologin.so pam_autologin checks that the config file exists, that it is owned by user and group root, and has permissions of 0600. If so, if either the tty or the IP matches the first field, the connecting terminal is logged in as the user in the second field. If pam_autologin logs the user in, this is logged, if not, control passes silently to the other modules in the /etc/pam.d/login stack. The key function is static int _pam_check_autologin(pam_handle_t *pamh, int flags, int argc, const char **argv) Of which there is : const char *user; I'd welcome any constructive comments on how this could be improved, but there are three issues in particular that I am conscious of: **** As shown, the config file uses DNS names, following the available item "retval = pam_get_item (pamh, PAM_RHOST, (const void **)¤t);", but really I would have preferred to use IPs, to avoid the extra step of name lookups. Anyone know how to do this ? **** "retval = pam_set_item(pamh, PAM_USER, user);" does not return PAM_SUCCESS, but appears to set the user in any case. Is there a better way of doing this ? **** Oddly, I was also having problems with "user_pwd = getpwnam(user);", which appeared to be trashing my user pointer. Why it should do this, I have no idea, there is something flakey and not quite right here, but I can't see what it is. Any ideas ? Regards to all, TimJ. Source code is as follows - PAM_AUTOLOGIN.C : #define _BSD_SOURCE #ifdef linux #include <endian.h> #endif #ifdef NEED_FSUID_H #include <sys/fsuid.h> #endif /* NEED_FSUID_H */ #include <sys/types.h> #include <sys/uio.h> #include <sys/stat.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/param.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> /* This is supposed(?) to contain the following */ int innetgr(const char *, const char *, const char *,const char *); #include <stdio.h> #include <errno.h> #include <sys/time.h> #include <arpa/inet.h> #ifndef MAXDNAME #define MAXDNAME 256 #endif #include <stdarg.h> #include <ctype.h> #include <net/if.h> #ifdef linux # include <linux/sockios.h> # ifndef __USE_MISC # define __USE_MISC # include <sys/fsuid.h> # endif /* __USE_MISC */ #endif #include <pwd.h> #include <grp.h> #include <sys/file.h> #include <sys/signal.h> #include <sys/stat.h> #include <syslog.h> #ifndef _AUTOLOGIN_CONF #define _AUTOLOGIN_CONF "/etc/autologin.conf" #endif /* _AUTOLOGIN_CONF */ #define PAM_SM_AUTH /* only defines this management group */ #include <security/pam_modules.h> #include <security/_pam_macros.h> /* to the best of my knowledge, all modern UNIX boxes have 32 bit integers */ #define U32 unsigned int #define TTY_PREFIX "/dev/" /* logging */ static void _pam_log(int err, const char *format, ...) { va_list args; va_start(args, format); openlog("pam_autologin", LOG_CONS|LOG_PID, LOG_AUTH); vsyslog(err, format, args); va_end(args); closelog(); } /* * Obtain the name of the remote host. Currently, this is simply by * requesting the contents of the PAM_RHOST item. */ static int pam_get_rhost(pam_handle_t *pamh, const char **rhost , const char *prompt) { int retval; const char *current; retval = pam_get_item (pamh, PAM_RHOST, (const void **)¤t); if (retval != PAM_SUCCESS) return retval; if (current == NULL) return PAM_AUTH_ERR; *rhost = current; return retval; /* pass on any error from conversation */ } static int pam_get_tty(pam_handle_t *pamh, const char **utty , const char *prompt) { int retval; const char *current; retval = pam_get_item(pamh, PAM_TTY, (const void **)¤t); if (retval != PAM_SUCCESS) return retval; if (current == NULL) return PAM_AUTH_ERR; /* The PAM_TTY item may be prefixed with "/dev/" - skip that */ if (strncmp(TTY_PREFIX, current, sizeof(TTY_PREFIX)-1) == 0) current += sizeof(TTY_PREFIX)-1; *utty = current; return retval; /* pass on any error from conversation */ } /* * Returns 1 for blank lines (or only comment lines) and 0 otherwise */ static int __isempty(char *p) { while (*p && isspace(*p)) { ++p; } return (*p == '\0' || *p == '#') ? 1:0 ; } static int check_cfg_sec(const char *pathname) /* check that a configuration file is secure */ { struct stat statbuf; int retval = 0; /* Config File must be : * Owned by User Root, Group Root * Have perms 0600 * Not be a Symbolic Lynk */ if (lstat(pathname, &statbuf) < 0) { _pam_log(LOG_WARNING, "Configuration File Not Available"); retval = 1; } else { if ((statbuf.st_mode & S_IFLNK) == S_IFLNK) { _pam_log(LOG_WARNING, "Configuration File is Symbolic Link"); retval = 1; } if (statbuf.st_mode & ~(S_IFREG | S_IREAD | S_IWRITE)) { _pam_log(LOG_WARNING, "Configuration File must have no more than 0600 permissions"); retval = 1; } if (statbuf.st_uid != 0) if (statbuf.st_gid != 0) { _pam_log(LOG_WARNING, "Configuration File is Symbolic Link"); retval = 1; } } return(retval); } static int read_autologin_conf(const char **rhost, const char **utty, const char **user) { register char *p; FILE *hostf; int fndflag; /* char *user;*/ char buf[MAXHOSTNAMELEN + 128]; /* host + login */ buf[sizeof (buf)-1] = '\0'; /* terminate line */ if (check_cfg_sec(_AUTOLOGIN_CONF) == 0) { hostf = fopen (_AUTOLOGIN_CONF, "r"); if (hostf) { while (fgets(buf, sizeof(buf), hostf) != NULL) /* hostf file line */ p = buf; /* Skip empty or comment lines */ if (__isempty(p)) { continue; } /* Skip lines that are too long. */ if (strchr(p, '\n') == NULL) { int ch = getc(hostf); while (ch != '\n' && ch != EOF) ch = getc(hostf); continue; } fndflag=1; if (rhost != NULL) fndflag=strncmp(buf, *rhost, sizeof(rhost)); if (fndflag != 0) if (utty != NULL) fndflag = strncmp(buf, *utty, sizeof(utty)); if (fndflag == 0) { for (;*p && !isspace(*p); ++p); /* <nul> terminate hostname and skip spaces */ for (*p++='\0'; *p && isspace(*p); ++p); *user = p; /* this is the user's name */ printf("\n1. Name found by read_autologin_conf = %s \n", p); getchar(); while (*p && !isspace(*p)) ++p;/* find end of user's name */ *p = '\0'; /* <nul> terminate username */ return (1); } } } } return (0); } /* * Internal function to do authentication */ static int _pam_check_autologin(pam_handle_t *pamh, int flags, int argc, const char **argv) { int retval ; const char *rhost, *utty, *user; struct passwd *user_pwd; /* get the remotehost */ retval= pam_get_rhost(pamh, &rhost, NULL); retval = pam_get_tty(pamh, &utty, NULL); if (read_autologin_conf(&rhost, &utty, &user)) { user_pwd = getpwnam(user); if (user_pwd == NULL) { _pam_log(LOG_WARNING, "user '%s' unknown to this system", user); retval = PAM_AUTH_ERR; } else { retval = pam_set_item(pamh, PAM_USER, user); _pam_log(LOG_WARNING, "Access granted to user '%s'" , user); return PAM_SUCCESS; } } return PAM_AUTH_ERR; } /* --- authentication management functions --- */ PAM_EXTERN int pam_sm_authenticate (pam_handle_t *pamh, int flags, int argc, const char **argv) { int retval; if (sizeof(U32) != 4) { _pam_log (LOG_ALERT, "pam_autologin module can\'t work on this hardware " "(yet)"); return PAM_AUTH_ERR; } sethostent(1); retval = _pam_check_autologin(pamh, flags, argc, argv); endhostent(); return retval; } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc, const char **argv) { return PAM_SUCCESS; } /* end of module definition */ #ifdef PAM_STATIC /* static module data */ struct pam_module _pam_rhosts_auth_modstruct = { "pam_rhosts_auth", pam_sm_authenticate, pam_sm_setcred, NULL, NULL, NULL, NULL, }; #endif