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>
40 EXEC SQL INCLUDE sqlca;
44 char *whoami, *hostname, *shorthostname;
46 char *find_usernames(char *first, char *middle, char *last);
47 int check_username_available(char *username);
48 void fixname(char *name);
49 int register_user(int uid, char *username);
50 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar);
53 reg_client *cl = NULL;
54 enum { RS_RUNNING, RS_SLEEPING, RS_EXITING } state = RS_RUNNING;
56 int main(int argc, char **argv)
58 int listener, nfds, i, clientid = 0;
59 fd_set readfds, xreadfds;
61 int nclients, clientssize;
69 whoami = strrchr(argv[0], '/');
70 whoami = whoami ? whoami + 1 : argv[0];
72 set_com_err_hook(mr_com_err);
77 com_err(whoami, errno, "reading RSA key");
82 com_err(whoami, errno, "reading HMAC key");
86 /* Read error messages */
89 com_err(whoami, errno, "reading error messages");
93 /* Connect to database */
94 EXEC SQL CONNECT :db IDENTIFIED BY :db;
98 int bufsize = 256, msglength = 0;
100 sqlglm(err_msg, &bufsize, &msglength);
101 err_msg[msglength] = 0;
102 com_err(whoami, 0, "SQL error connecting to DBMS:\n%s", err_msg);
106 /* Get my hostname */
108 h = gethostbyname(uts.nodename);
111 com_err(whoami, 0, "Couldn't resolve hostname %s", uts.nodename);
114 hostname = lowercase(xstrdup(h->h_name));
115 shorthostname = xstrdup(hostname);
116 if (strchr(shorthostname, '.'))
117 *strchr(shorthostname, '.') = '\0';
119 /* Initialize kerberos */
120 status = init_kerberos();
123 com_err(whoami, status, "initializing kerberos library");
127 /* Set up listening socket. */
128 listener = mr_listen("moira_ureg");
131 com_err(whoami, errno, "couldn't create listening socket");
135 FD_SET(listener, &xreadfds);
138 /* Initialize client array. */
141 clients = malloc(clientssize * sizeof(reg_client));
144 com_err(whoami, errno, "creating client array");
148 /* Set up signal handlers */
150 sigemptyset(&sa.sa_mask);
151 sa.sa_handler = sigshut;
152 sigaction(SIGTERM, &sa, NULL);
153 sigaction(SIGINT, &sa, NULL);
154 sigaction(SIGHUP, &sa, NULL);
155 sa.sa_handler = SIG_IGN;
156 sigaction(SIGPIPE, &sa, NULL);
158 com_err(whoami, 0, "started (pid %d)", getpid());
159 com_err(whoami, 0, rcsid);
162 while (state != RS_EXITING)
164 if (state == RS_RUNNING && stat(MOIRA_MOTD_FILE, &st) == 0)
167 com_err(whoami, 0, "found motd. reg_svr is sleeping");
169 else if (state == RS_SLEEPING && stat(MOIRA_MOTD_FILE, &st) == -1)
172 com_err(whoami, 0, "motd gone. reg_svr is running");
175 memcpy(&readfds, &xreadfds, sizeof(readfds));
176 if (select(nfds, &readfds, NULL, NULL, NULL) == -1)
179 com_err(whoami, errno, "in select");
183 if (FD_ISSET(listener, &readfds))
185 int newconn, addrlen = sizeof(struct sockaddr_in);
186 struct sockaddr_in addr;
188 newconn = accept(listener, (struct sockaddr *)&addr, &addrlen);
190 com_err(whoami, errno, "accepting new connection");
194 if (nclients > clientssize)
196 clientssize = 2 * clientssize;
197 clients = xrealloc(clients, clientssize *
201 cl = &clients[nclients - 1];
202 memset(cl, 0, sizeof(reg_client));
204 cl->lastmod = time(NULL);
205 cl->clientid = ++clientid;
206 cl->random = init_rand(cl);
207 FD_SET(newconn, &xreadfds);
212 "New connection from %s port %d (now %d client%s)",
213 inet_ntoa(addr.sin_addr), (int)ntohs(addr.sin_port),
214 nclients, nclients != 1 ? "s" : "");
218 for (i = 0; i < nclients; i++)
221 if (FD_ISSET(cl->fd, &readfds))
223 cl->lastmod = time(NULL);
226 /* We're just starting */
230 com_err(whoami, errno, "allocating read buffer");
231 reply(cl, INTERNAL_ERROR, "INIT", "c", NULL,
240 /* We haven't read the length byte yet... */
241 cl->nread += read(cl->fd, cl->buf + cl->nread,
245 cl->nmax = cl->buf[1] * 256 + cl->buf[2] + 3;
246 cl->buf = realloc(cl->buf, cl->nmax + 3);
249 com_err(whoami, errno, "reallocating read buffer");
250 reply(cl, INTERNAL_ERROR, "INIT", "c", NULL,
255 else if (cl->nread == 0)
257 /* client has closed connection. setting
258 lastmod will cause it to be reaped */
264 /* We know how long the packet is supposed to be */
265 cl->nread += read(cl->fd, cl->buf + cl->nread,
266 cl->nmax - cl->nread);
267 if (cl->nread == cl->nmax)
269 parse_packet(cl, cl->buf[0], cl->nread - 3, cl->buf + 3,
270 state == RS_SLEEPING);
278 if (cl->lastmod < time(NULL) - TIMEOUT)
280 com_err(whoami, 0, "Closed connection. (now %d client%s)",
281 nclients - 1, nclients != 2 ? "s" : "");
284 FD_CLR(cl->fd, &xreadfds);
288 free(cl->suggestions);
290 clients[i] = clients[--nclients];
296 com_err(whoami, 0, "Exiting.");
299 void RIFO(reg_client *rc, int argc, char **argv)
301 EXEC SQL BEGIN DECLARE SECTION;
302 char *ufirst, *umiddle, *ulast, *id;
303 char login[USERS_LOGIN_SIZE], first[USERS_FIRST_SIZE];
304 char middle[USERS_MIDDLE_SIZE], last[USERS_LAST_SIZE];
305 char fullname[USERS_FIRST_SIZE + USERS_MIDDLE_SIZE + USERS_LAST_SIZE];
306 char class[USERS_TYPE_SIZE], pin[USERS_PIN_SIZE];
307 int uid, status, secure, sqlstatus, count;
308 EXEC SQL END DECLARE SECTION;
310 if (rc->uid || argc != 4)
312 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
321 EXEC SQL SELECT count(login) INTO :count FROM users WHERE clearid = :id;
323 /* "ORDER BY status" so that if there's both a matching state 0 entry
324 and a matching state 3 entry, we'll get the former. */
325 EXEC SQL DECLARE csr_id CURSOR FOR
326 SELECT login, unix_uid, status, secure, pin, first, middle, last, type
327 FROM users WHERE clearid = :id ORDER BY status;
328 EXEC SQL OPEN csr_id;
331 EXEC SQL FETCH csr_id INTO :login, :uid, :status,
332 :secure, :pin, :first, :middle, :last, :class;
342 /* It's possible they have both a deleted account and a status 8
343 * account. We can't compensate for that in the ORDER BY clause
344 * above, so check here. If they have more than one entry and the
345 * first one we get is deleted, skip it.
347 if (status == US_DELETED && count > 1)
350 /* Check names, allowing for the possibility that Moira and the
351 user might have them split up differently. eg, Mary/Ann/Singleton
352 vs. Mary Ann/Singleton. */
353 if (strcasecmp(last, ulast) && strncasecmp(last, ulast, strlen(last)) &&
354 strncasecmp(last, ulast, strlen(ulast)))
356 if (strlen(last) > 3 && strlen(ulast) < 3)
358 if (strcasecmp(first, ufirst) &&
359 strncasecmp(first, ufirst, strlen(first)) &&
360 strncasecmp(first, ufirst, strlen(ufirst)))
362 if (strlen(first) > 3 && strlen(ufirst) < 3)
364 if (!*ufirst && !*ulast)
367 /* Ignore the middle name since Moira doesn't have those reliably */
370 sqlstatus = sqlca.sqlcode;
371 EXEC SQL CLOSE csr_id;
375 reply(rc, NOT_FOUND_IN_DATABASE, "GETN", "d", NULL);
383 case US_ENROLL_NOT_ALLOWED:
384 case US_REGISTERED_KERBEROS_ONLY:
385 reply(rc, ALREADY_REGISTERED, "INIT", "c", NULL, login);
389 reply(rc, ACCOUNT_DELETED, "INIT", "c", NULL, login);
393 reply(rc, NOT_ELIGIBLE, "INIT", "c", NULL);
400 rc->user_status = status;
402 sprintf(fullname, "%s %s%s%s", first, middle, *middle ? " " : "", last);
403 if (!strcmp(class, "MITS"))
404 strcpy(class, "STAFF");
410 com_err(whoami, errno, "in RIFO");
411 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
417 rc->reserved_username = 1;
418 rc->username = strdup(login);
421 com_err(whoami, errno, "in RIFO");
422 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
428 rc->suggestions = find_usernames(first, middle, last);
429 if (!rc->suggestions && errno)
431 com_err(whoami, errno, "in RIFO");
432 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(errno));
440 reply(rc, FOUND, "GETI", "c", NULL, fullname, class);
442 reply(rc, FOUND, "GETW", "c", NULL, fullname, class);
444 else if (!rc->username)
445 reply(rc, FOUND, "GETL", "c", rc->suggestions, fullname, class);
448 if (rc->user_status == US_NO_LOGIN_YET ||
449 rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
451 status = check_kerberos(login);
452 if (status == MR_SUCCESS &&
453 rc->user_status != US_NO_LOGIN_YET_KERBEROS_ONLY)
454 status = register_user(rc->uid, login);
455 if (status == MR_IN_USE)
457 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
461 else if (status == MR_DOWN)
463 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
466 else if (status != MR_SUCCESS)
468 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL,
469 error_message(status));
473 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
477 void SWRD(reg_client *rc, int argc, char **argv)
482 if (!rc->id || argc != 6)
484 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
488 getwordlist(rc->id, words);
489 for (i = 0; i < 6; i++)
491 if (strcasecmp(strtrim(argv[i]), words[i]))
496 reply(rc, BAD_SIX_WORDS, "GETW", "d", NULL);
503 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
505 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
508 void SPIN(reg_client *rc, int argc, char **argv)
510 EXEC SQL BEGIN DECLARE SECTION;
511 char pin[USERS_PIN_SIZE];
512 EXEC SQL END DECLARE SECTION;
514 if (!rc->id || argc != 1)
516 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
520 EXEC SQL SELECT pin INTO :pin FROM users WHERE clearid = :rc->id
521 AND status = :rc->user_status;
523 if (strcmp(argv[0], pin) != 0)
525 reply(rc, BAD_PIN, "GETI", "d", NULL);
532 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
535 register_user(rc->uid, rc->username);
536 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
540 void CLGN(reg_client *rc, int argc, char **argv)
546 if (!rc->uid || rc->id || rc->username || argc != 1)
548 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
554 /* make sure someone's not trying to overrun reply */
555 if (strlen(login) > 100)
557 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
562 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
563 (login[0] == '_') || isdigit(login[0]))
565 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
566 3, USERS_LOGIN_SIZE - 1);
570 for (i = 0; i < strlen(login); i++)
572 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
574 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
575 3, USERS_LOGIN_SIZE - 1);
580 status = check_kerberos(login);
581 if (status == MR_SUCCESS)
583 status = check_username_available(login);
584 if (status == MR_SUCCESS)
586 reply(rc, USERNAME_AVAILABLE, "LOGC", "c", login, login);
591 if (status == MR_IN_USE)
593 if (rc->reserved_username)
595 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
599 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
602 else if (status == MR_DOWN)
604 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
607 else if (status != MR_SUCCESS)
609 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
614 void LOGN(reg_client *rc, int argc, char **argv)
620 if (!rc->uid || rc->id || rc->username || argc != 1)
622 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
628 /* make sure someone's not trying to overrun reply */
629 if (strlen(login) > 100)
631 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
636 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
637 (login[0] == '_') || isdigit(login[0]))
639 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
640 3, USERS_LOGIN_SIZE - 1);
644 for (i = 0; i < strlen(login); i++)
646 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
648 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
649 3, USERS_LOGIN_SIZE - 1);
654 status = check_kerberos(login);
655 if (status == MR_SUCCESS)
657 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
658 EXEC SQL UPDATE users SET login = :login WHERE unix_uid = :rc->uid;
660 status = register_user(rc->uid, login);
662 if (status == MR_IN_USE)
664 if (rc->reserved_username)
666 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
670 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
673 else if (status == MR_DOWN)
675 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
678 else if (status != MR_SUCCESS)
680 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
684 rc->username = strdup(login);
687 com_err(whoami, errno, "in LOGN");
688 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
691 reply(rc, USERNAME_OK, "GETP", "c", NULL, login);
695 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^@ - ^O */
696 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^P - ^_ */
697 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* SPACE - / */
698 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, /* 0 - ? */
699 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* : - O */
700 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, /* P - _ */
701 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* ` - o */
702 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, /* p - ^? */
703 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
704 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
705 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
706 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
707 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
708 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
709 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
710 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
713 void PSWD(reg_client *rc, int argc, char **argv)
716 char *password = argv[0], *p;
717 EXEC SQL BEGIN DECLARE SECTION;
718 char *login = rc->username;
719 EXEC SQL END DECLARE SECTION;
721 if (!rc->username || rc->id || argc != 1)
723 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
727 /* password quality checking */
728 if (strlen(password) < 4)
730 reply(rc, PASSWORD_SHORT, "GETP", "c", NULL);
734 if (strlen(password) < 7)
736 for (p = password + 1; *p; p++)
738 if (ctypes[*p] != ctypes[*(p - 1)])
743 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
748 if (!strcasecmp(password, "GykoR-66") ||
749 !strcasecmp(password, "slaRooBey") ||
750 !strcasecmp(password, "krang-its") ||
751 !strcasecmp(password, "2HotPeetzas") ||
752 !strcasecmp(password, "ItzAGurl"))
754 reply(rc, PASSWORD_SAMPLE, "GETP", "c", NULL);
758 status = register_kerberos(rc->username, password);
759 if (status == MR_QUALITY)
761 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
764 else if (status == MR_IN_USE)
766 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
772 com_err(whoami, status, "registering username with Kerberos");
773 reply(rc, KADM_ERROR, "INIT", "c", NULL, error_message(status));
777 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
778 EXEC SQL UPDATE users SET status = 9 WHERE login = :login;
780 EXEC SQL UPDATE users SET status = 1 WHERE login = :login;
783 reply(rc, DONE, "INIT", "c", NULL, rc->username);
786 void QUIT(reg_client *rc, int argc, char **argv)
790 /* Register a user in Moira */
791 int register_user(int uid, char *username)
793 EXEC SQL BEGIN DECLARE SECTION;
794 char class[USERS_TYPE_SIZE];
795 EXEC SQL END DECLARE SECTION;
796 char uidbuf[10], *qargv[3], *motd = NULL;
799 status = mr_connect(hostname);
803 status = mr_motd(&motd);
810 status = mr_krb5_auth("reg_svr");
813 com_err(whoami, status, "authenticating to moira");
818 EXEC SQL SELECT type INTO :class FROM users WHERE unix_uid = :uid;
820 sprintf(uidbuf, "%d", uid);
824 /* Incoming students should be given Exchange poboxes.
825 * Doesn't work for undergrads in the class of 2100 or higher.
827 if (!strcmp(strtrim(class), "G") || !strncmp(class, "FALL", 4) ||
828 !strncmp(class, "SPRING", 5) || !strncmp(class, "SUMMER", 6) ||
829 !strncmp(class, "20", 2))
831 com_err(whoami, 0, "assigning EXCHANGE pobox to user %s, class %s", username, class);
832 qargv[2] = "EXCHANGE";
836 com_err(whoami, 0, "assigning IMAP pobox to user %s, class %s", username, class);
840 status = mr_query("register_user", 3, qargv, NULL, NULL);
846 /* Find some typical available usernames */
848 char *uname_patterns[] = {
850 "fmllllll", /* jmdoe... (last name truncated) */
851 "flllllll", /* jdoe.... ("") */
852 "llllllll", /* doe..... ("") */
859 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
861 char *find_usernames(char *first, char *middle, char *last)
863 EXEC SQL BEGIN DECLARE SECTION;
864 char username[2 * USERS_LOGIN_SIZE];
866 EXEC SQL END DECLARE SECTION;
868 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
874 for (pat = 0; pat < num_patterns; pat++)
880 for (pp = uname_patterns[pat]; *pp; pp++)
890 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
891 up += sprintf(up, "%s", first);
909 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
910 up += sprintf(up, "%s", last);
918 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
921 EXEC SQL SELECT COUNT(login) INTO :count FROM users
922 WHERE login = :username;
930 EXEC SQL SELECT COUNT(name) INTO :count FROM list
931 WHERE name = :username;
940 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
941 WHERE label = :username;
953 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
956 strcat(unames, ", ");
957 strcat(unames, username);
961 unames = strdup(username);
971 /* unames will be NULL if we couldn't suggest a username. Clear
972 errno so the caller can distinguish this from an error case. */
977 /* This does the database-side checks to make sure a username is
980 int check_username_available(char *username)
984 EXEC SQL SELECT COUNT(login) INTO :count FROM users
985 WHERE login = :username;
991 EXEC SQL SELECT COUNT(name) INTO :count FROM list
992 WHERE name = :username;
998 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
999 WHERE label = :username;
1005 EXEC SQL SELECT COUNT(login) INTO :count FROM userhistory
1006 WHERE login = :username;
1015 void fixname(char *name)
1019 for (s = d = name; *s; s++)
1027 void *xmalloc(size_t bytes)
1029 void *buf = malloc(bytes);
1034 com_err(whoami, errno, "in xmalloc");
1038 void *xrealloc(void *ptr, size_t bytes)
1040 void *buf = realloc(ptr, bytes);
1045 com_err(whoami, errno, "in xrealloc");
1049 char *xstrdup(char *str)
1051 char *buf = strdup(str);
1056 com_err(whoami, errno, "in xstrdup");
1060 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
1064 fputs(whoami, stderr);
1066 fprintf(stderr, "[#%d]", cl->clientid);
1067 fputs(": ", stderr);
1070 fputs(error_message(code), stderr);
1074 vfprintf(stderr, fmt, pvar);
1078 void sigshut(int sig)