/* opieauto.c: The opieauto program. %%% copyright-cmetz-96 This software is Copyright 1996-2001 by Craig Metz, All Rights Reserved. The Inner Net License Version 3 applies to this software. You should have received a copy of the license with this software. If you didn't get a copy, you may request one from <license@inner.net>. History: Created by cmetz for OPIE 2.4 based on previously released test code. Use opiestrncpy(). */ #include "opie_cfg.h" #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #if HAVE_SYS_TIME_H #include <sys/time.h> #endif /* HAVE_SYS_TIME_H */ #include <stdio.h> #include <errno.h> #if HAVE_STRING_H #include <string.h> #endif /* HAVE_STRING_H */ #include <getopt.h> #if HAVE_STDLIB_H #include <stdlib.h> #endif /* HAVE_STDLIB_H */ #if HAVE_UNISTD_H #include <unistd.h> #endif /* HAVE_UNISTD_H */ #include <sys/stat.h> #include "opie.h" #ifndef max #define max(x, y) (((x) > (y)) ? (x) : (y)) #endif /* max */ int window = 10; char *myname = NULL; uid_t myuid = 0; #define MAXCLIENTS 2 int parents, s[MAXCLIENTS + 1]; char cmd[1+1+1+1+4+1+OPIE_SEED_MAX+1+4+1+4+1+4+1+4+1]; struct cachedotp { struct cachedotp *next; int algorithm, base, current; struct opie_otpkey basekey; char seed[OPIE_SEED_MAX+1]; }; struct cachedotp *head = NULL; char *algids[] = { NULL, NULL, NULL, "sha1", "md4", "md5" }; void baile(x) { fprintf(stderr, "%s: %s: %s(%d)\n", myname, x, strerror(errno), errno); exit(1); } void bail(x) { fprintf(stderr, "%s: %s\n", myname, x); exit(1); } void zerocache(void) { struct cachedotp *c = head, *c2; while(c) { c2 = c->next; memset(c, 0, sizeof(struct cachedotp)); c = c2; }; }; int doreq(int fd) { int algorithm, sequence, i; char *seed = NULL, *response = NULL; if (((cmd[0] != 'S') && (cmd[0] != 's')) || (cmd[1] != '=') || (cmd[2] != ' ')) { #if DEBUG fprintf(stderr, "%s: got bogus command: %s\n", myname, cmd); #endif /* DEBUG */ goto error; }; { char *c; if (((algorithm = strtoul(&cmd[3], &c, 10)) < 3) || (algorithm > 5) || (*c != ' ')) { #if DEBUG fprintf(stderr, "%s: got bogus algorithm: %s\n", myname, cmd); #endif /* DEBUG */ goto error; }; if (((sequence = strtoul(c + 1, &c, 10)) <= OPIE_SEQUENCE_RESTRICT) || (sequence > OPIE_SEQUENCE_MAX)) { #if DEBUG fprintf(stderr, "%s: got bogus sequence: %s\n", myname, cmd); #endif /* DEBUG */ goto error; }; if (cmd[0] == 'S') { if (!(c = strchr(seed = c + 1, ' '))) { #if DEBUG fprintf(stderr, "%s: got bogus seed: %s\n", myname, cmd); #endif /* DEBUG */ goto error; }; *c = 0; if (!(c = strchr(response = c + 1, '\n'))) { #if DEBUG fprintf(stderr, "%s: got bogus response: %s\n", myname, cmd); #endif /* DEBUG */ goto error; }; *c = 0; } else { if (!(c = strchr(seed = c + 1, '\n'))) { #if DEBUG fprintf(stderr, "%s: got bogus seed: %s\n", myname, cmd); #endif /* DEBUG */ goto error; }; *c = 0; }; }; #if DEBUG fprintf(stderr, "got cmd=%c, algorithm=%d sequence=%d seed=+%s+ response=+%s+ on fd %d\n", cmd[0], algorithm, sequence, seed, response, fd); #endif /* DEBUG */ seed = strdup(seed); if (sequence < 10) { #if DEBUG fprintf(stderr, "sequence < 10; can't do it\n"); #endif /* DEBUG */ sprintf(cmd, "%c- %d %d %s\n", cmd[0], algorithm, sequence, seed); }; { struct cachedotp **c; for (c = &head; *c && (strcmp((*c)->seed, seed) || ((*c)->algorithm != algorithm)); c = &((*c)->next)); if (!(*c)) { if (cmd[0] == 's') { #if DEBUG fprintf(stderr, "(seed, algorithm) not found for s command\n"); #endif /* DEBUG */ sprintf(cmd, "s- %d %d %s\n", algorithm, sequence, seed); goto out; } if (!(*c = malloc(sizeof(struct cachedotp)))) baile("malloc"); memset(*c, 0, sizeof(struct cachedotp)); (*c)->algorithm = algorithm; opiestrncpy((*c)->seed, seed, OPIE_SEED_MAX); }; if (cmd[0] == 'S') { (*c)->base = max(sequence - window + 1, OPIE_SEQUENCE_RESTRICT); (*c)->current = sequence; if (!opieatob8(&(*c)->basekey, response)) goto error; sprintf(cmd, "S+ %d %d %s\n", algorithm, sequence, (*c)->seed); } else { if (sequence != ((*c)->current - 1)) { #if DEBUG fprintf(stderr, "out of sequence: sequence=%d, base=%d, current=%d\n", sequence, (*c)->base, (*c)->current); #endif /* DEBUG */ sprintf(cmd, "s- %d %d %s\n", algorithm, sequence, (*c)->seed); goto out; }; if (sequence < (*c)->base) { #if DEBUG fprintf(stderr, "attempt to generate below base: sequence=%d, base=%d, current=%d\n", sequence, (*c)->base, (*c)->current); #endif /* DEBUG */ sprintf(cmd, "s- %d %d %s\n", algorithm, sequence, (*c)->seed); goto out; }; (*c)->current = sequence; i = sequence - (*c)->base; { struct opie_otpkey key; char buffer[16+1]; key = (*c)->basekey; while(i--) opiehash(&key, algorithm); opiebtoa8(buffer, &key); sprintf(cmd, "s+ %d %d %s %s\n", algorithm, sequence, (*c)->seed, buffer); }; }; printf("%c otp-%s %d %s (%d/%d)\n", cmd[0], algids[algorithm], sequence, (*c)->seed, sequence - (*c)->base, window); fflush(stdout); if (sequence == (*c)->base) { struct cachedotp *c2 = *c; *c = (*c)->next; memset(c2, 0, sizeof(struct cachedotp)); free(c2); }; }; out: write(fd, cmd, i = strlen(cmd)); free(seed); return 0; error: fprintf(stderr, "Invalid command on fd %d\n", fd); if (seed) free(seed); return -1; } static void usage() { fprintf(stderr, "usage: %s [-v] [-h] [-q] [-n <number of OTPs>]\n", myname); exit(1); } int main(int argc, char **argv) { int i; struct stat st; char *sockpath; if (myname = strrchr(argv[0], '/')) myname++; else myname = argv[0]; while((i = getopt(argc, argv, "w:hv")) != EOF) { switch(i) { case 'v': opieversion(); case 'w': if (!(window = atoi(optarg))) { fprintf(stderr, "%s: invalid number of OTPs: %s\n", myname, optarg); exit(1); }; break; default: usage(); } }; { uid_t myeuid; if (!(myuid = getuid()) || !(myeuid = geteuid()) || (myuid != myeuid)) bail("this program must not be run with superuser priveleges or setuid."); }; if (atexit(zerocache) < 0) baile("atexit"); { struct sockaddr_un sun; memset(&sun, 0, sizeof(struct sockaddr_un)); sun.sun_family = AF_UNIX; { char *c; char *c2 = "/.opieauto"; if (!(c = getenv("HOME"))) bail("getenv(HOME) failed -- no HOME variable?"); if (strlen(c) > (sizeof(sun.sun_path) - strlen(c2) - 1)) bail("your HOME is too long"); strcpy(sun.sun_path, c); strcat(sun.sun_path, c2); sockpath = strdup(sun.sun_path); }; if ((parents = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) baile("socket"); if (unlink(sockpath) && (errno != ENOENT)) baile("unlink"); if (umask(0177) < 0) baile("umask"); if (bind(parents, (struct sockaddr *)&sun, sizeof(struct sockaddr_un))) baile("bind"); if (stat(sockpath, &st) < 0) baile("stat"); if ((st.st_uid != myuid) || (!S_ISSOCK(st.st_mode)) || ((st.st_mode & 07777) != 0600)) bail("socket permissions and/or ownership were not correctly created."); if (listen(parents, 1) < 0) baile("listen"); }; { fd_set fds, rfds, efds; int maxfd = parents; int i, j; FD_ZERO(&fds); FD_SET(parents, &fds); while(1) { memcpy(&rfds, &fds, sizeof(fd_set)); if (select(maxfd + 1, &rfds, NULL, NULL, NULL) < 0) baile("select"); for (i = 0; s[i]; i++) { if (!FD_ISSET(s[i], &rfds)) continue; if (((j = read(s[i], cmd, sizeof(cmd)-1)) <= 0) || ((cmd[j] = 0) || doreq(s[i]))) { close(s[i]); FD_CLR(s[i], &fds); if (s[i] == maxfd) maxfd--; for (j = i; s[j]; s[j] = s[j + 1], j++); FD_SET(parents, &fds); i--; continue; }; }; if (FD_ISSET(parents, &rfds)) { for (i = 0; s[i]; i++) if (i > MAXCLIENTS) bail("this message never printed"); if (stat(sockpath, &st) < 0) baile("stat"); if ((st.st_uid != myuid) || (!S_ISSOCK(st.st_mode)) || ((st.st_mode & 07777) != 0600)) bail("socket permissions and/or ownership has been messed with."); if ((s[i] = accept(parents, NULL, 0)) < 0) baile("accept"); FD_SET(s[i], &fds); if (s[i] > maxfd) maxfd = s[i]; sprintf(cmd, "C+ %d\n", window); if (write(s[i], cmd, j = strlen(cmd)) != j) baile("write"); if (++i == MAXCLIENTS) FD_CLR(parents, &fds); } } } }