genlastlog.c 8.64 KB
Newer Older
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1 2
/*
 * EMULAB-COPYRIGHT
3
 * Copyright (c) 2000-2011 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
4 5 6
 * All rights reserved.
 */

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * This odd program is used to generate last login information on a per
 * user and per node basis. That is, for each user we want the last time
 * they logged in anyplace, and for each node we want the last time anyone
 * logged into it. The latter is obviously more useful for scheduling
 * purposes. 
 *
 * We get this information from all of the syslog entries that are reported in
 * by all the nodes when people ssh login. We have set up each experimental
 * node to report auth.info to @users.emulab.net. Note that the start flags
 * to syslogd (on users) must be changed to allows these incoming UDP packets.
 * In /etc/rc.conf:
 *
 *	syslogd_flags="-a 155.101.132.0/22"
 *
 * This program parses that file and inserts a bunch of entries into the DB.
23
 * See the update comands below.
24
 *
25 26
 * The entry in users:/etc/syslog.conf to capture the syslog data coming
 * from the nodes:
27
 *
28
 *	auth.info			/var/log/logins
29
 *
30 31 32 33 34 35
 * while on each experimental node:
 * 
 *	auth.info			@users.emulab.net
 *
 * Of course, you need to make sure /var/log/logins is cleaned periodically,
 * so put an entry in /etc/newsyslog.conf:
36 37 38
 *
 *	/var/log/logins           640  7     500 *     Z
 *
39 40 41 42
 * To prevent information loss that can occurr between the primary file
 * and the first roll file (logins.0.gz), the program actually reads the
 * first roll file (it is gzipped of course). This causes us to do some
 * extra work, but thats okay since we really do not want to lose that data.
43
 */
44

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <utmp.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <setjmp.h>
#include <sys/fcntl.h>
#include <sys/param.h>
#include <sys/syslog.h>
#include <stdarg.h>
#include <assert.h>
#include <netdb.h>
#include <sys/socket.h>
#include <mysql/mysql.h>
#include <zlib.h>
63
#include "tbdb.h"
64 65 66 67 68 69 70 71 72 73 74

/*
 * This is the NFS mountpoint where users:/var is mounted.
 */
#ifndef USERSVAR
#define USERSVAR	"/usr/testbed/usersvar"
#endif

#define LOGINS		"log/logins"
#define SSHD		"sshd"

75 76 77 78
#ifndef LOG_TESTBED
#define LOG_TESTBED	LOG_USER
#endif

79 80 81 82 83 84 85 86 87 88
static char		*progname;
static int		debug = 0;
static int		doit(gzFile *infp);
static char		opshostname[MAXHOSTNAMELEN];
static jmp_buf		deadline;
static int		deadfl;

static void
usage(void)
{
89
	fprintf(stderr, "Usage: %s [-a]\n", progname);
90 91 92 93 94 95 96 97 98 99 100 101 102 103
	exit(-1);
}

static void
dead()
{
	deadfl = 1;
	longjmp(deadline, 1);
}

int
main(int argc, char **argv)
{
	gzFile	       *infp;
104
	char		buf[BUFSIZ], *bp, **aliases;
105
	struct hostent  *he;
106 107 108
	int		ch, errors = 0;
	int		backcount = 0;

109 110
	progname = argv[0];

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
	while ((ch = getopt(argc, argv, "a:")) != -1)
		switch(ch) {
		case 'a':
			backcount = atoi(optarg);
			break;
			
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
126 127
		usage();

128
	openlog("genlastlog", LOG_PID, LOG_TESTBED);
129 130
	syslog(LOG_NOTICE, "genlastlog starting");

131 132 133 134 135
	if (!dbinit()) {
		syslog(LOG_ERR, "Could not connect to DB!");
		exit(1);
	}

136 137
	/*
	 * We need the canonical hostname for the usersnode so that we can
138
	 * put those logins in another table.
139 140 141 142 143 144
	 */
	if ((he = gethostbyname(USERNODE)) == NULL) {
		syslog(LOG_ERR, "gethostname %s: %s",
		       USERNODE, hstrerror(h_errno));
		exit(-1);
	}
145 146
	strncpy(opshostname, he->h_name, sizeof(opshostname));

147 148 149
	if (bp = strchr(opshostname, '.')) 
		*bp = 0;

150 151
	while (backcount) {
		sprintf(buf, "%s/%s.%d.gz", USERSVAR, LOGINS, backcount);
152

153 154 155 156 157 158 159 160 161 162 163 164 165 166
		/*
		 * Use setjmp and timer to prevent NFS lockup.
		 */
		if (setjmp(deadline) == 0) {
			alarm(15);

			if ((infp = gzopen(buf, "r")) == NULL) {
				syslog(LOG_ERR, "Opening %s: %m", buf);
				errors++;
			}
			else {
				doit(infp);
				gzclose(infp);
			}
167
		}
168 169
		backcount--;
		alarm(0);
170 171 172 173 174 175 176 177 178
	}
	
	sprintf(buf, "%s/%s", USERSVAR, LOGINS);

	if (setjmp(deadline) == 0) {
		alarm(15);

		if ((infp = gzopen(buf, "r")) == NULL) {
			syslog(LOG_ERR, "Opening %s: %m", buf);
179
			errors++;
180 181 182 183 184 185 186 187 188
		}
		else {
			doit(infp);
			gzclose(infp);
		}
	}
	alarm(0);

	syslog(LOG_NOTICE, "genlastlog ending");
189
	exit(errors);
190 191 192 193 194 195 196 197
	
}

static int
doit(gzFile *infp)
{
	int		i, skip = 0;
	time_t		curtime, ll_time;
198 199
	char		*user, node[TBDB_FLEN_NODEID * 2], prog[128];
	char		buf[BUFSIZ], *bp, uid_idx[128], tmp[BUFSIZ];
200
	struct tm	tm;
201 202 203
	MYSQL_RES	*dbres;
	MYSQL_ROW	dbrow;
	
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
	while (1) {
		if (gzgets(infp, buf, BUFSIZ) == NULL)
			break;

		/*
		 * If the line does not contain a newline, then we skip it
		 * and try to sync up again. We consider ourselves synced
		 * when the buffer contains a newline in it.
		 */
		if (buf[strlen(buf) - 1] != '\n') {
			skip = 1;
			continue;
		}
		if (skip) {
			skip = 0;
			continue;
220
 		}
221 222 223 224 225 226 227 228 229 230 231 232

		/*
		 * Thank dog for strptime! Convert the syslog timestamp
		 * into a tm, and then into regular unix time.
		 */
		time(&curtime);
		localtime_r(&curtime, &tm);
		if ((bp = strptime(buf, "%b %e %T", &tm)) == NULL) {
			continue;
		}
		ll_time = mktime(&tm);

233 234 235 236 237 238 239 240 241 242 243
		/*
		 * If the constructed time is in the future, then we have
		 * year off by one (cause we are possibly looking at files
		 * created in the previous year). Set the year back by one,
		 * and redo.
		 */
		if (ll_time > curtime) {
			tm.tm_year--;
			ll_time = mktime(&tm);
		}

244 245 246 247 248 249 250 251 252 253 254 255
		/*
		 * Scanf the next part, which looks like:
		 *
		 *	node progname[pid]:
		 *
		 * Ensure we match the proper number of items.
		 */
		bzero(node, sizeof(node));
		if ((sscanf(bp, "%s %s:", node, prog) != 2))
			continue;

		/*
256
		 * Only sshd matters to us.
257
		 */
258
		if (strncmp(prog, SSHD, strlen(SSHD)))
259 260 261 262 263 264 265 266
			continue;

		/*
		 * Okay, these kinds of strings matter.
		 *
		 *	FreeBSD:	"Accepted rsa for USER" 
		 *	Linux 6.2:	"log: RSA authentication for USER"
		 *	Linux 7.1:	"session opened for user USER"
267 268 269
		 *      (several ssh2): "Accepted publickey for USER"
		 *      (several ssh2): "Accepted password for USER"
		 *      (several ssh2): "Accepted keyboard-interactive for USER"
270 271 272 273
		 */
#define L1	"Accepted rsa for "
#define L2	"session opened for user "
#define L3	"log: RSA authentication for "
274 275
#define L4	"Accepted publickey for "
#define L5	"Accepted password for "
276
#define L6	"Accepted keyboard-interactive for "
277
		
278 279 280 281 282
		/* Skip to end of program[pid]: and trailing space */
		bp = strchr(bp, ':');
		bp += 2;

		if (strncmp(bp, L1, strlen(L1)) == 0) {
283
		  /*fprintf(stdout,"Hit L1: ");*/
284 285 286
			bp += strlen(L1);
		}
		else if (strncmp(bp, L2, strlen(L2)) == 0) {
287
		  /*fprintf(stdout,"Hit L2: ");*/
288 289 290
			bp += strlen(L2);
		}
		else if (strncmp(bp, L3, strlen(L3)) == 0) {
291
		  /*fprintf(stdout,"Hit L3: ");*/
292 293
			bp += strlen(L3);
		}
294 295 296 297 298 299 300 301
		else if (strncmp(bp, L4, strlen(L4)) == 0) {
		  /*fprintf(stdout,"Hit L4: ");*/
			bp += strlen(L4);
		}
		else if (strncmp(bp, L5, strlen(L5)) == 0) {
		  /*fprintf(stdout,"Hit L5: ");*/
			bp += strlen(L5);
		}
302 303 304 305
		else if (strncmp(bp, L6, strlen(L6)) == 0) {
		  /*fprintf(stdout,"Hit L6: ");*/
			bp += strlen(L6);
		}
306 307 308 309 310 311 312 313 314
		else {
			continue;
		}

		/*
		 * The login name is the next token.
		 */
		if (! (user = strsep(&bp, " ")))
			continue;
315
		/*fprintf(stdout,"%s on %s\n",user,node);*/
316 317 318 319 320

		/* We do not care about ROOT logins. */
		if (strcasecmp(user, "ROOT") == 0)
			continue;

321 322
		dbres = mydb_query("select uid_idx from users where uid='%s' "
				   "and status!='archived' and status!='nonlocal'",
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
				   1, user);

		if (!dbres) {
			syslog(LOG_ERR, "DB error getting user %s", user);
			continue;
		}

		if (!mysql_num_rows(dbres)) {
			syslog(LOG_INFO, "No DB record for user %s", user);
			mysql_free_result(dbres);
			continue;
		}
		dbrow = mysql_fetch_row(dbres);
		strncpy(uid_idx, dbrow[0], sizeof(uid_idx));
		mysql_free_result(dbres);

		/*
		 * Safety first. 
		 */
		mydb_escape_string(tmp, uid_idx, strlen(uid_idx));
		strcpy(uid_idx, tmp);
		mydb_escape_string(tmp, node, strlen(node));
		strcpy(node, tmp);

347
		if (mydb_update("replace into uidnodelastlogin "
348 349
				"(uid, uid_idx, node_id, date, time) "
				"values ('%s', '%s', '%s', "
350 351
				"        FROM_UNIXTIME(%ld, '%%Y-%%m-%%d'), "
				"        FROM_UNIXTIME(%ld, '%%T')) ",
352
				user, uid_idx, node, ll_time, ll_time) == 0)
353 354
			break;

355
		if (strncmp(node, opshostname, strlen(node)) == 0 ||
356 357
		    strncmp(node, "ops", strlen(node)) == 0) {
			if (mydb_update("replace into userslastlogin "
358 359
					"(uid, uid_idx, date, time) "
					"values ('%s', '%s', "
360 361
					"  FROM_UNIXTIME(%ld, '%%Y-%%m-%%d'), "
					"  FROM_UNIXTIME(%ld, '%%T')) ",
362
					user, uid_idx, ll_time, ll_time) == 0)
363 364 365 366
				break;
		}
		else {
			if (mydb_update("replace into nodeuidlastlogin "
367 368
					"(node_id, uid_idx, uid, date, time) "
					"values ('%s', '%s', '%s', "
369 370
					"  FROM_UNIXTIME(%ld, '%%Y-%%m-%%d'), "
					"  FROM_UNIXTIME(%ld, '%%T')) ",
371
					node, uid_idx, user, ll_time, ll_time) == 0)
372 373
				break;
		}
374 375 376
	}
	return 0;
}