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;
305 EXEC SQL END DECLARE SECTION;
307 if (rc->uid || argc != 4)
309 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
318 /* "ORDER BY status" so that if there's both a matching state 0 entry
319 and a matching state 3 entry, we'll get the former. */
320 EXEC SQL DECLARE csr_id CURSOR FOR
321 SELECT login, unix_uid, status, secure, pin, first, middle, last, type
322 FROM users WHERE clearid = :id ORDER BY status;
323 EXEC SQL OPEN csr_id;
326 EXEC SQL FETCH csr_id INTO :login, :uid, :status,
327 :secure, :pin, :first, :middle, :last, :class;
337 /* Check names, allowing for the possibility that Moira and the
338 user might have them split up differently. eg, Mary/Ann/Singleton
339 vs. Mary Ann/Singleton. */
340 if (strcasecmp(last, ulast) && strncasecmp(last, ulast, strlen(last)) &&
341 strncasecmp(last, ulast, strlen(ulast)))
343 if (strlen(last) > 3 && strlen(ulast) < 3)
345 if (strcasecmp(first, ufirst) &&
346 strncasecmp(first, ufirst, strlen(first)) &&
347 strncasecmp(first, ufirst, strlen(ufirst)))
349 if (strlen(first) > 3 && strlen(ufirst) < 3)
351 /* Ignore the middle name since Moira doesn't have those reliably */
354 sqlstatus = sqlca.sqlcode;
355 EXEC SQL CLOSE csr_id;
359 reply(rc, NOT_FOUND_IN_DATABASE, "GETN", "d", NULL);
367 case US_ENROLL_NOT_ALLOWED:
368 case US_REGISTERED_KERBEROS_ONLY:
369 reply(rc, ALREADY_REGISTERED, "INIT", "c", NULL, login);
373 reply(rc, ACCOUNT_DELETED, "INIT", "c", NULL, login);
377 reply(rc, NOT_ELIGIBLE, "INIT", "c", NULL);
384 rc->user_status = status;
386 sprintf(fullname, "%s %s%s%s", first, middle, *middle ? " " : "", last);
387 if (!strcmp(class, "MITS"))
388 strcpy(class, "STAFF");
394 com_err(whoami, errno, "in RIFO");
395 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
401 rc->reserved_username = 1;
402 rc->username = strdup(login);
405 com_err(whoami, errno, "in RIFO");
406 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
412 rc->suggestions = find_usernames(first, middle, last);
413 if (!rc->suggestions && errno)
415 com_err(whoami, errno, "in RIFO");
416 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(errno));
424 reply(rc, FOUND, "GETI", "c", NULL, fullname, class);
426 reply(rc, FOUND, "GETW", "c", NULL, fullname, class);
428 else if (!rc->username)
429 reply(rc, FOUND, "GETL", "c", rc->suggestions, fullname, class);
432 if (rc->user_status == US_NO_LOGIN_YET ||
433 rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
435 status = check_kerberos(login);
436 if (status == MR_SUCCESS &&
437 rc->user_status != US_NO_LOGIN_YET_KERBEROS_ONLY)
438 status = register_user(rc->uid, login);
439 if (status == MR_IN_USE)
441 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
445 else if (status == MR_DOWN)
447 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
450 else if (status != MR_SUCCESS)
452 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL,
453 error_message(status));
457 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
461 void SWRD(reg_client *rc, int argc, char **argv)
466 if (!rc->id || argc != 6)
468 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
472 getwordlist(rc->id, words);
473 for (i = 0; i < 6; i++)
475 if (strcasecmp(strtrim(argv[i]), words[i]))
480 reply(rc, BAD_SIX_WORDS, "GETW", "d", NULL);
487 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
489 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
492 void SPIN(reg_client *rc, int argc, char **argv)
494 EXEC SQL BEGIN DECLARE SECTION;
495 char pin[USERS_PIN_SIZE];
496 EXEC SQL END DECLARE SECTION;
498 if (!rc->id || argc != 1)
500 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
504 /* "ORDER BY status" so that if there's both a matching state 0 entry
505 and a matching state 3 entry, we'll get the former. */
506 EXEC SQL SELECT pin INTO :pin FROM users WHERE clearid = :rc->id
509 if (strcmp(argv[0], pin) != 0)
511 reply(rc, BAD_PIN, "GETI", "d", NULL);
518 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
521 register_user(rc->uid, rc->username);
522 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
526 void LOGN(reg_client *rc, int argc, char **argv)
532 if (!rc->uid || rc->id || rc->username || argc != 1)
534 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
540 /* make sure someone's not trying to overrun reply */
541 if (strlen(login) > 100)
543 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
548 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
549 (login[0] == '_') || isdigit(login[0]))
551 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
552 3, USERS_LOGIN_SIZE - 1);
556 for (i = 0; i < strlen(login); i++)
558 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
560 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
561 3, USERS_LOGIN_SIZE - 1);
566 status = check_kerberos(login);
567 if (status == MR_SUCCESS)
569 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
570 EXEC SQL UPDATE users SET login = :login WHERE unix_uid = :rc->uid;
572 status = register_user(rc->uid, login);
574 if (status == MR_IN_USE)
576 if (rc->reserved_username)
578 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
582 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
585 else if (status == MR_DOWN)
587 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
590 else if (status != MR_SUCCESS)
592 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
596 rc->username = strdup(login);
599 com_err(whoami, errno, "in LOGN");
600 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
603 reply(rc, USERNAME_OK, "GETP", "c", NULL, login);
607 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^@ - ^O */
608 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^P - ^_ */
609 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* SPACE - / */
610 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, /* 0 - ? */
611 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* : - O */
612 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, /* P - _ */
613 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* ` - o */
614 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, /* p - ^? */
615 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
616 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
617 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
618 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
619 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
620 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
621 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
622 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
625 void PSWD(reg_client *rc, int argc, char **argv)
628 char *password = argv[0], *p;
629 EXEC SQL BEGIN DECLARE SECTION;
630 char *login = rc->username;
631 EXEC SQL END DECLARE SECTION;
633 if (!rc->username || rc->id || argc != 1)
635 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
639 /* password quality checking */
640 if (strlen(password) < 4)
642 reply(rc, PASSWORD_SHORT, "GETP", "c", NULL);
646 if (strlen(password) < 7)
648 for (p = password + 1; *p; p++)
650 if (ctypes[*p] != ctypes[*(p - 1)])
655 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
660 if (!strcasecmp(password, "GykoR-66") ||
661 !strcasecmp(password, "slaRooBey") ||
662 !strcasecmp(password, "krang-its") ||
663 !strcasecmp(password, "2HotPeetzas") ||
664 !strcasecmp(password, "ItzAGurl"))
666 reply(rc, PASSWORD_SAMPLE, "GETP", "c", NULL);
670 status = register_kerberos(rc->username, password);
671 if (status == MR_QUALITY)
673 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
676 else if (status == MR_IN_USE)
678 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
684 com_err(whoami, status, "registering username with Kerberos");
685 reply(rc, KADM_ERROR, "INIT", "c", NULL, error_message(status));
689 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
690 EXEC SQL UPDATE users SET status = 9 WHERE login = :login;
692 EXEC SQL UPDATE users SET status = 1 WHERE login = :login;
695 reply(rc, DONE, "INIT", "c", NULL, rc->username);
698 void QUIT(reg_client *rc, int argc, char **argv)
702 /* Register a user in Moira */
703 int register_user(int uid, char *username)
705 char uidbuf[10], *qargv[3], *motd = NULL;
708 status = mr_connect(hostname);
712 status = mr_motd(&motd);
719 status = krb_get_svc_in_tkt(REG_SVR_PRINCIPAL, REG_SVR_INSTANCE,
720 krb_realmofhost(hostname), MOIRA_SNAME,
721 shorthostname, 1, KEYFILE);
723 status += ERROR_TABLE_BASE_krb;
725 status = mr_auth("reg_svr");
728 com_err(whoami, status, "authenticating to moira");
733 sprintf(uidbuf, "%d", uid);
737 status = mr_query("register_user", 3, qargv, NULL, NULL);
743 /* Find some typical available usernames */
745 char *uname_patterns[] = {
747 "fmllllll", /* jmdoe... (last name truncated) */
748 "flllllll", /* jdoe.... ("") */
749 "llllllll", /* doe..... ("") */
756 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
758 char *find_usernames(char *first, char *middle, char *last)
760 EXEC SQL BEGIN DECLARE SECTION;
761 char username[2 * USERS_LOGIN_SIZE];
763 EXEC SQL END DECLARE SECTION;
765 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
771 for (pat = 0; pat < num_patterns; pat++)
777 for (pp = uname_patterns[pat]; *pp; pp++)
787 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
788 up += sprintf(up, "%s", first);
806 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
807 up += sprintf(up, "%s", last);
815 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
818 EXEC SQL SELECT COUNT(login) INTO :count FROM users
819 WHERE login = :username;
827 EXEC SQL SELECT COUNT(name) INTO :count FROM list
828 WHERE name = :username;
837 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
838 WHERE label = :username;
850 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
853 strcat(unames, ", ");
854 strcat(unames, username);
858 unames = strdup(username);
868 /* unames will be NULL if we couldn't suggest a username. Clear
869 errno so the caller can distinguish this from an error case. */
874 void fixname(char *name)
878 for (s = d = name; *s; s++)
886 void *xmalloc(size_t bytes)
888 void *buf = malloc(bytes);
893 com_err(whoami, errno, "in xmalloc");
897 void *xrealloc(void *ptr, size_t bytes)
899 void *buf = realloc(ptr, bytes);
904 com_err(whoami, errno, "in xrealloc");
908 char *xstrdup(char *str)
910 char *buf = strdup(str);
915 com_err(whoami, errno, "in xstrdup");
919 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
923 fputs(whoami, stderr);
925 fprintf(stderr, "[#%d]", cl->clientid);
929 fputs(error_message(code), stderr);
933 vfprintf(stderr, fmt, pvar);
937 void sigshut(int sig)