3 * Server for user registration with Moira and Kerberos.
5 * Copyright (C) 1987-1998 by the Massachusetts Institute of Technology
6 * For copying and distribution information, please see the file
10 #include <mit-copyright.h>
12 #include <mr_private.h>
13 #include <moira_schema.h>
14 #include <moira_site.h>
17 #include <sys/types.h>
18 #include <sys/socket.h>
21 #include <sys/utsname.h>
23 #include <netinet/in.h>
38 EXEC SQL INCLUDE sqlca;
42 char *whoami, *hostname, *shorthostname;
44 char *find_usernames(char *first, char *middle, char *last);
45 void fixname(char *name);
46 int register_user(int uid, char *username);
47 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar);
50 reg_client *cl = NULL;
51 enum { RS_RUNNING, RS_SLEEPING, RS_EXITING } state = RS_RUNNING;
53 int main(int argc, char **argv)
55 int listener, nfds, i, clientid = 0;
56 fd_set readfds, xreadfds;
58 int nclients, clientssize;
66 whoami = strrchr(argv[0], '/');
67 whoami = whoami ? whoami + 1 : argv[0];
69 set_com_err_hook(mr_com_err);
74 com_err(whoami, errno, "reading RSA key");
79 com_err(whoami, errno, "reading HMAC key");
83 /* Read error messages */
86 com_err(whoami, errno, "reading error messages");
90 /* Connect to database */
91 EXEC SQL CONNECT :db IDENTIFIED BY :db;
95 int bufsize = 256, msglength = 0;
97 sqlglm(err_msg, &bufsize, &msglength);
98 err_msg[msglength] = 0;
99 com_err(whoami, 0, "SQL error connecting to DBMS:\n%s", err_msg);
103 /* Get my hostname */
105 h = gethostbyname(uts.nodename);
108 com_err(whoami, 0, "Couldn't resolve hostname %s", uts.nodename);
111 hostname = lowercase(xstrdup(h->h_name));
112 shorthostname = xstrdup(hostname);
113 if (strchr(shorthostname, '.'))
114 *strchr(shorthostname, '.') = '\0';
116 /* Initialize kerberos */
117 status = init_kerberos();
120 com_err(whoami, status, "initializing kerberos library");
124 /* Set up listening socket. */
125 listener = mr_listen("moira_ureg");
128 com_err(whoami, errno, "couldn't create listening socket");
132 FD_SET(listener, &xreadfds);
135 /* Initialize client array. */
138 clients = malloc(clientssize * sizeof(reg_client));
141 com_err(whoami, errno, "creating client array");
145 /* Set up signal handlers */
147 sigemptyset(&sa.sa_mask);
148 sa.sa_handler = sigshut;
149 sigaction(SIGTERM, &sa, NULL);
150 sigaction(SIGINT, &sa, NULL);
151 sigaction(SIGHUP, &sa, NULL);
152 sa.sa_handler = SIG_IGN;
153 sigaction(SIGPIPE, &sa, NULL);
155 com_err(whoami, 0, "started (pid %d)", getpid());
156 com_err(whoami, 0, rcsid);
159 while (state != RS_EXITING)
161 if (state == RS_RUNNING && stat(MOIRA_MOTD_FILE, &st) == 0)
164 com_err(whoami, 0, "found motd. reg_svr is sleeping");
166 else if (state == RS_SLEEPING && stat(MOIRA_MOTD_FILE, &st) == -1)
169 com_err(whoami, 0, "motd gone. reg_svr is running");
172 memcpy(&readfds, &xreadfds, sizeof(readfds));
173 if (select(nfds, &readfds, NULL, NULL, NULL) == -1)
176 com_err(whoami, errno, "in select");
180 if (FD_ISSET(listener, &readfds))
182 int newconn, addrlen = sizeof(struct sockaddr_in);
183 struct sockaddr_in addr;
185 newconn = accept(listener, (struct sockaddr *)&addr, &addrlen);
187 com_err(whoami, errno, "accepting new connection");
191 if (nclients > clientssize)
193 clientssize = 2 * clientssize;
194 clients = xrealloc(clients, clientssize *
198 cl = &clients[nclients - 1];
199 memset(cl, 0, sizeof(reg_client));
201 cl->lastmod = time(NULL);
202 cl->clientid = ++clientid;
203 cl->random = init_rand(cl);
204 FD_SET(newconn, &xreadfds);
209 "New connection from %s port %d (now %d client%s)",
210 inet_ntoa(addr.sin_addr), (int)ntohs(addr.sin_port),
211 nclients, nclients != 1 ? "s" : "");
215 for (i = 0; i < nclients; i++)
218 if (FD_ISSET(cl->fd, &readfds))
220 cl->lastmod = time(NULL);
223 /* We're just starting */
227 com_err(whoami, errno, "allocating read buffer");
228 reply(cl, INTERNAL_ERROR, "INIT", "c", NULL,
237 /* We haven't read the length byte yet... */
238 cl->nread += read(cl->fd, cl->buf + cl->nread,
242 cl->nmax = cl->buf[1] * 256 + cl->buf[2] + 3;
243 cl->buf = realloc(cl->buf, cl->nmax + 3);
246 com_err(whoami, errno, "reallocating read buffer");
247 reply(cl, INTERNAL_ERROR, "INIT", "c", NULL,
252 else if (cl->nread == 0)
254 /* client has closed connection. setting
255 lastmod will cause it to be reaped */
261 /* We know how long the packet is supposed to be */
262 cl->nread += read(cl->fd, cl->buf + cl->nread,
263 cl->nmax - cl->nread);
264 if (cl->nread == cl->nmax)
266 parse_packet(cl, cl->buf[0], cl->nread - 3, cl->buf + 3,
267 state == RS_SLEEPING);
275 if (cl->lastmod < time(NULL) - TIMEOUT)
277 com_err(whoami, 0, "Closed connection. (now %d client%s)",
278 nclients - 1, nclients != 2 ? "s" : "");
281 FD_CLR(cl->fd, &xreadfds);
285 free(cl->suggestions);
287 clients[i] = clients[--nclients];
293 com_err(whoami, 0, "Exiting.");
296 void RIFO(reg_client *rc, int argc, char **argv)
298 EXEC SQL BEGIN DECLARE SECTION;
299 char *ufirst, *umiddle, *ulast, *id;
300 char login[USERS_LOGIN_SIZE], first[USERS_FIRST_SIZE];
301 char middle[USERS_MIDDLE_SIZE], last[USERS_LAST_SIZE];
302 char fullname[USERS_FIRST_SIZE + USERS_MIDDLE_SIZE + USERS_LAST_SIZE];
303 char class[USERS_TYPE_SIZE], pin[USERS_PIN_SIZE];
304 int uid, status, secure, sqlstatus, count;
305 EXEC SQL END DECLARE SECTION;
307 if (rc->uid || argc != 4)
309 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
318 EXEC SQL SELECT count(login) INTO :count FROM users WHERE clearid = :id;
320 /* "ORDER BY status" so that if there's both a matching state 0 entry
321 and a matching state 3 entry, we'll get the former. */
322 EXEC SQL DECLARE csr_id CURSOR FOR
323 SELECT login, unix_uid, status, secure, pin, first, middle, last, type
324 FROM users WHERE clearid = :id ORDER BY status;
325 EXEC SQL OPEN csr_id;
328 EXEC SQL FETCH csr_id INTO :login, :uid, :status,
329 :secure, :pin, :first, :middle, :last, :class;
339 /* It's possible they have both a deleted account and a status 8
340 * account. We can't compensate for that in the ORDER BY clause
341 * above, so check here. If they have more than one entry and the
342 * first one we get is deleted, skip it.
344 if (status == US_DELETED && count > 1)
347 /* Check names, allowing for the possibility that Moira and the
348 user might have them split up differently. eg, Mary/Ann/Singleton
349 vs. Mary Ann/Singleton. */
350 if (strcasecmp(last, ulast) && strncasecmp(last, ulast, strlen(last)) &&
351 strncasecmp(last, ulast, strlen(ulast)))
353 if (strlen(last) > 3 && strlen(ulast) < 3)
355 if (strcasecmp(first, ufirst) &&
356 strncasecmp(first, ufirst, strlen(first)) &&
357 strncasecmp(first, ufirst, strlen(ufirst)))
359 if (strlen(first) > 3 && strlen(ufirst) < 3)
361 if (!*ufirst && !*ulast)
364 /* Ignore the middle name since Moira doesn't have those reliably */
367 sqlstatus = sqlca.sqlcode;
368 EXEC SQL CLOSE csr_id;
372 reply(rc, NOT_FOUND_IN_DATABASE, "GETN", "d", NULL);
380 case US_ENROLL_NOT_ALLOWED:
381 case US_REGISTERED_KERBEROS_ONLY:
382 reply(rc, ALREADY_REGISTERED, "INIT", "c", NULL, login);
386 reply(rc, ACCOUNT_DELETED, "INIT", "c", NULL, login);
390 reply(rc, NOT_ELIGIBLE, "INIT", "c", NULL);
397 rc->user_status = status;
399 sprintf(fullname, "%s %s%s%s", first, middle, *middle ? " " : "", last);
400 if (!strcmp(class, "MITS"))
401 strcpy(class, "STAFF");
407 com_err(whoami, errno, "in RIFO");
408 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
414 rc->reserved_username = 1;
415 rc->username = strdup(login);
418 com_err(whoami, errno, "in RIFO");
419 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
425 rc->suggestions = find_usernames(first, middle, last);
426 if (!rc->suggestions && errno)
428 com_err(whoami, errno, "in RIFO");
429 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(errno));
437 reply(rc, FOUND, "GETI", "c", NULL, fullname, class);
439 reply(rc, FOUND, "GETW", "c", NULL, fullname, class);
441 else if (!rc->username)
442 reply(rc, FOUND, "GETL", "c", rc->suggestions, fullname, class);
445 if (rc->user_status == US_NO_LOGIN_YET ||
446 rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
448 status = check_kerberos(login);
449 if (status == MR_SUCCESS &&
450 rc->user_status != US_NO_LOGIN_YET_KERBEROS_ONLY)
451 status = register_user(rc->uid, login);
452 if (status == MR_IN_USE)
454 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
458 else if (status == MR_DOWN)
460 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
463 else if (status != MR_SUCCESS)
465 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL,
466 error_message(status));
470 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
474 void SWRD(reg_client *rc, int argc, char **argv)
479 if (!rc->id || argc != 6)
481 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
485 getwordlist(rc->id, words);
486 for (i = 0; i < 6; i++)
488 if (strcasecmp(strtrim(argv[i]), words[i]))
493 reply(rc, BAD_SIX_WORDS, "GETW", "d", NULL);
500 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
502 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
505 void SPIN(reg_client *rc, int argc, char **argv)
507 EXEC SQL BEGIN DECLARE SECTION;
508 char pin[USERS_PIN_SIZE];
509 EXEC SQL END DECLARE SECTION;
511 if (!rc->id || argc != 1)
513 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
517 EXEC SQL SELECT pin INTO :pin FROM users WHERE clearid = :rc->id
518 AND status = :rc->user_status;
520 if (strcmp(argv[0], pin) != 0)
522 reply(rc, BAD_PIN, "GETI", "d", NULL);
529 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
532 register_user(rc->uid, rc->username);
533 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
537 void LOGN(reg_client *rc, int argc, char **argv)
543 if (!rc->uid || rc->id || rc->username || argc != 1)
545 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
551 /* make sure someone's not trying to overrun reply */
552 if (strlen(login) > 100)
554 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
559 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
560 (login[0] == '_') || isdigit(login[0]))
562 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
563 3, USERS_LOGIN_SIZE - 1);
567 for (i = 0; i < strlen(login); i++)
569 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
571 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
572 3, USERS_LOGIN_SIZE - 1);
577 status = check_kerberos(login);
578 if (status == MR_SUCCESS)
580 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
581 EXEC SQL UPDATE users SET login = :login WHERE unix_uid = :rc->uid;
583 status = register_user(rc->uid, login);
585 if (status == MR_IN_USE)
587 if (rc->reserved_username)
589 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
593 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
596 else if (status == MR_DOWN)
598 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
601 else if (status != MR_SUCCESS)
603 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
607 rc->username = strdup(login);
610 com_err(whoami, errno, "in LOGN");
611 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
614 reply(rc, USERNAME_OK, "GETP", "c", NULL, login);
618 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^@ - ^O */
619 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^P - ^_ */
620 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* SPACE - / */
621 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, /* 0 - ? */
622 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* : - O */
623 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, /* P - _ */
624 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* ` - o */
625 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, /* p - ^? */
626 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
627 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
628 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
629 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
630 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
631 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
632 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
633 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
636 void PSWD(reg_client *rc, int argc, char **argv)
639 char *password = argv[0], *p;
640 EXEC SQL BEGIN DECLARE SECTION;
641 char *login = rc->username;
642 EXEC SQL END DECLARE SECTION;
644 if (!rc->username || rc->id || argc != 1)
646 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
650 /* password quality checking */
651 if (strlen(password) < 4)
653 reply(rc, PASSWORD_SHORT, "GETP", "c", NULL);
657 if (strlen(password) < 7)
659 for (p = password + 1; *p; p++)
661 if (ctypes[*p] != ctypes[*(p - 1)])
666 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
671 if (!strcasecmp(password, "GykoR-66") ||
672 !strcasecmp(password, "slaRooBey") ||
673 !strcasecmp(password, "krang-its") ||
674 !strcasecmp(password, "2HotPeetzas") ||
675 !strcasecmp(password, "ItzAGurl"))
677 reply(rc, PASSWORD_SAMPLE, "GETP", "c", NULL);
681 status = register_kerberos(rc->username, password);
682 if (status == MR_QUALITY)
684 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
687 else if (status == MR_IN_USE)
689 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
695 com_err(whoami, status, "registering username with Kerberos");
696 reply(rc, KADM_ERROR, "INIT", "c", NULL, error_message(status));
700 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
701 EXEC SQL UPDATE users SET status = 9 WHERE login = :login;
703 EXEC SQL UPDATE users SET status = 1 WHERE login = :login;
706 reply(rc, DONE, "INIT", "c", NULL, rc->username);
709 void QUIT(reg_client *rc, int argc, char **argv)
713 /* Register a user in Moira */
714 int register_user(int uid, char *username)
716 char uidbuf[10], *qargv[3], *motd = NULL;
719 status = mr_connect(hostname);
723 status = mr_motd(&motd);
730 status = krb_get_svc_in_tkt(REG_SVR_PRINCIPAL, REG_SVR_INSTANCE,
731 krb_realmofhost(hostname), MOIRA_SNAME,
732 shorthostname, 1, KEYFILE);
734 status += ERROR_TABLE_BASE_krb;
736 status = mr_auth("reg_svr");
739 com_err(whoami, status, "authenticating to moira");
744 sprintf(uidbuf, "%d", uid);
748 status = mr_query("register_user", 3, qargv, NULL, NULL);
754 /* Find some typical available usernames */
756 char *uname_patterns[] = {
758 "fmllllll", /* jmdoe... (last name truncated) */
759 "flllllll", /* jdoe.... ("") */
760 "llllllll", /* doe..... ("") */
767 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
769 char *find_usernames(char *first, char *middle, char *last)
771 EXEC SQL BEGIN DECLARE SECTION;
772 char username[2 * USERS_LOGIN_SIZE];
774 EXEC SQL END DECLARE SECTION;
776 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
782 for (pat = 0; pat < num_patterns; pat++)
788 for (pp = uname_patterns[pat]; *pp; pp++)
798 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
799 up += sprintf(up, "%s", first);
817 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
818 up += sprintf(up, "%s", last);
826 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
829 EXEC SQL SELECT COUNT(login) INTO :count FROM users
830 WHERE login = :username;
838 EXEC SQL SELECT COUNT(name) INTO :count FROM list
839 WHERE name = :username;
848 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
849 WHERE label = :username;
861 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
864 strcat(unames, ", ");
865 strcat(unames, username);
869 unames = strdup(username);
879 /* unames will be NULL if we couldn't suggest a username. Clear
880 errno so the caller can distinguish this from an error case. */
885 void fixname(char *name)
889 for (s = d = name; *s; s++)
897 void *xmalloc(size_t bytes)
899 void *buf = malloc(bytes);
904 com_err(whoami, errno, "in xmalloc");
908 void *xrealloc(void *ptr, size_t bytes)
910 void *buf = realloc(ptr, bytes);
915 com_err(whoami, errno, "in xrealloc");
919 char *xstrdup(char *str)
921 char *buf = strdup(str);
926 com_err(whoami, errno, "in xstrdup");
930 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
934 fputs(whoami, stderr);
936 fprintf(stderr, "[#%d]", cl->clientid);
940 fputs(error_message(code), stderr);
944 vfprintf(stderr, fmt, pvar);
948 void sigshut(int sig)