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 int check_username_available(char *username);
46 void fixname(char *name);
47 int register_user(int uid, char *username);
48 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar);
51 reg_client *cl = NULL;
52 enum { RS_RUNNING, RS_SLEEPING, RS_EXITING } state = RS_RUNNING;
54 int main(int argc, char **argv)
56 int listener, nfds, i, clientid = 0;
57 fd_set readfds, xreadfds;
59 int nclients, clientssize;
67 whoami = strrchr(argv[0], '/');
68 whoami = whoami ? whoami + 1 : argv[0];
70 set_com_err_hook(mr_com_err);
75 com_err(whoami, errno, "reading RSA key");
80 com_err(whoami, errno, "reading HMAC key");
84 /* Read error messages */
87 com_err(whoami, errno, "reading error messages");
91 /* Connect to database */
92 EXEC SQL CONNECT :db IDENTIFIED BY :db;
96 int bufsize = 256, msglength = 0;
98 sqlglm(err_msg, &bufsize, &msglength);
99 err_msg[msglength] = 0;
100 com_err(whoami, 0, "SQL error connecting to DBMS:\n%s", err_msg);
104 /* Get my hostname */
106 h = gethostbyname(uts.nodename);
109 com_err(whoami, 0, "Couldn't resolve hostname %s", uts.nodename);
112 hostname = lowercase(xstrdup(h->h_name));
113 shorthostname = xstrdup(hostname);
114 if (strchr(shorthostname, '.'))
115 *strchr(shorthostname, '.') = '\0';
117 /* Initialize kerberos */
118 status = init_kerberos();
121 com_err(whoami, status, "initializing kerberos library");
125 /* Set up listening socket. */
126 listener = mr_listen("moira_ureg");
129 com_err(whoami, errno, "couldn't create listening socket");
133 FD_SET(listener, &xreadfds);
136 /* Initialize client array. */
139 clients = malloc(clientssize * sizeof(reg_client));
142 com_err(whoami, errno, "creating client array");
146 /* Set up signal handlers */
148 sigemptyset(&sa.sa_mask);
149 sa.sa_handler = sigshut;
150 sigaction(SIGTERM, &sa, NULL);
151 sigaction(SIGINT, &sa, NULL);
152 sigaction(SIGHUP, &sa, NULL);
153 sa.sa_handler = SIG_IGN;
154 sigaction(SIGPIPE, &sa, NULL);
156 com_err(whoami, 0, "started (pid %d)", getpid());
157 com_err(whoami, 0, rcsid);
160 while (state != RS_EXITING)
162 if (state == RS_RUNNING && stat(MOIRA_MOTD_FILE, &st) == 0)
165 com_err(whoami, 0, "found motd. reg_svr is sleeping");
167 else if (state == RS_SLEEPING && stat(MOIRA_MOTD_FILE, &st) == -1)
170 com_err(whoami, 0, "motd gone. reg_svr is running");
173 memcpy(&readfds, &xreadfds, sizeof(readfds));
174 if (select(nfds, &readfds, NULL, NULL, NULL) == -1)
177 com_err(whoami, errno, "in select");
181 if (FD_ISSET(listener, &readfds))
183 int newconn, addrlen = sizeof(struct sockaddr_in);
184 struct sockaddr_in addr;
186 newconn = accept(listener, (struct sockaddr *)&addr, &addrlen);
188 com_err(whoami, errno, "accepting new connection");
192 if (nclients > clientssize)
194 clientssize = 2 * clientssize;
195 clients = xrealloc(clients, clientssize *
199 cl = &clients[nclients - 1];
200 memset(cl, 0, sizeof(reg_client));
202 cl->lastmod = time(NULL);
203 cl->clientid = ++clientid;
204 cl->random = init_rand(cl);
205 FD_SET(newconn, &xreadfds);
210 "New connection from %s port %d (now %d client%s)",
211 inet_ntoa(addr.sin_addr), (int)ntohs(addr.sin_port),
212 nclients, nclients != 1 ? "s" : "");
216 for (i = 0; i < nclients; i++)
219 if (FD_ISSET(cl->fd, &readfds))
221 cl->lastmod = time(NULL);
224 /* We're just starting */
228 com_err(whoami, errno, "allocating read buffer");
229 reply(cl, INTERNAL_ERROR, "INIT", "c", NULL,
238 /* We haven't read the length byte yet... */
239 cl->nread += read(cl->fd, cl->buf + cl->nread,
243 cl->nmax = cl->buf[1] * 256 + cl->buf[2] + 3;
244 cl->buf = realloc(cl->buf, cl->nmax + 3);
247 com_err(whoami, errno, "reallocating read buffer");
248 reply(cl, INTERNAL_ERROR, "INIT", "c", NULL,
253 else if (cl->nread == 0)
255 /* client has closed connection. setting
256 lastmod will cause it to be reaped */
262 /* We know how long the packet is supposed to be */
263 cl->nread += read(cl->fd, cl->buf + cl->nread,
264 cl->nmax - cl->nread);
265 if (cl->nread == cl->nmax)
267 parse_packet(cl, cl->buf[0], cl->nread - 3, cl->buf + 3,
268 state == RS_SLEEPING);
276 if (cl->lastmod < time(NULL) - TIMEOUT)
278 com_err(whoami, 0, "Closed connection. (now %d client%s)",
279 nclients - 1, nclients != 2 ? "s" : "");
282 FD_CLR(cl->fd, &xreadfds);
286 free(cl->suggestions);
288 clients[i] = clients[--nclients];
294 com_err(whoami, 0, "Exiting.");
297 void RIFO(reg_client *rc, int argc, char **argv)
299 EXEC SQL BEGIN DECLARE SECTION;
300 char *ufirst, *umiddle, *ulast, *id;
301 char login[USERS_LOGIN_SIZE], first[USERS_FIRST_SIZE];
302 char middle[USERS_MIDDLE_SIZE], last[USERS_LAST_SIZE];
303 char fullname[USERS_FIRST_SIZE + USERS_MIDDLE_SIZE + USERS_LAST_SIZE];
304 char class[USERS_TYPE_SIZE], pin[USERS_PIN_SIZE];
305 int uid, status, secure, sqlstatus, count;
306 EXEC SQL END DECLARE SECTION;
308 if (rc->uid || argc != 4)
310 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
319 EXEC SQL SELECT count(login) INTO :count FROM users WHERE clearid = :id;
321 /* "ORDER BY status" so that if there's both a matching state 0 entry
322 and a matching state 3 entry, we'll get the former. */
323 EXEC SQL DECLARE csr_id CURSOR FOR
324 SELECT login, unix_uid, status, secure, pin, first, middle, last, type
325 FROM users WHERE clearid = :id ORDER BY status;
326 EXEC SQL OPEN csr_id;
329 EXEC SQL FETCH csr_id INTO :login, :uid, :status,
330 :secure, :pin, :first, :middle, :last, :class;
340 /* It's possible they have both a deleted account and a status 8
341 * account. We can't compensate for that in the ORDER BY clause
342 * above, so check here. If they have more than one entry and the
343 * first one we get is deleted, skip it.
345 if (status == US_DELETED && count > 1)
348 /* Check names, allowing for the possibility that Moira and the
349 user might have them split up differently. eg, Mary/Ann/Singleton
350 vs. Mary Ann/Singleton. */
351 if (strcasecmp(last, ulast) && strncasecmp(last, ulast, strlen(last)) &&
352 strncasecmp(last, ulast, strlen(ulast)))
354 if (strlen(last) > 3 && strlen(ulast) < 3)
356 if (strcasecmp(first, ufirst) &&
357 strncasecmp(first, ufirst, strlen(first)) &&
358 strncasecmp(first, ufirst, strlen(ufirst)))
360 if (strlen(first) > 3 && strlen(ufirst) < 3)
362 if (!*ufirst && !*ulast)
365 /* Ignore the middle name since Moira doesn't have those reliably */
368 sqlstatus = sqlca.sqlcode;
369 EXEC SQL CLOSE csr_id;
373 reply(rc, NOT_FOUND_IN_DATABASE, "GETN", "d", NULL);
381 case US_ENROLL_NOT_ALLOWED:
382 case US_REGISTERED_KERBEROS_ONLY:
383 reply(rc, ALREADY_REGISTERED, "INIT", "c", NULL, login);
387 reply(rc, ACCOUNT_DELETED, "INIT", "c", NULL, login);
391 reply(rc, NOT_ELIGIBLE, "INIT", "c", NULL);
398 rc->user_status = status;
400 sprintf(fullname, "%s %s%s%s", first, middle, *middle ? " " : "", last);
401 if (!strcmp(class, "MITS"))
402 strcpy(class, "STAFF");
408 com_err(whoami, errno, "in RIFO");
409 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
415 rc->reserved_username = 1;
416 rc->username = strdup(login);
419 com_err(whoami, errno, "in RIFO");
420 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
426 rc->suggestions = find_usernames(first, middle, last);
427 if (!rc->suggestions && errno)
429 com_err(whoami, errno, "in RIFO");
430 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(errno));
438 reply(rc, FOUND, "GETI", "c", NULL, fullname, class);
440 reply(rc, FOUND, "GETW", "c", NULL, fullname, class);
442 else if (!rc->username)
443 reply(rc, FOUND, "GETL", "c", rc->suggestions, fullname, class);
446 if (rc->user_status == US_NO_LOGIN_YET ||
447 rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
449 status = check_kerberos(login);
450 if (status == MR_SUCCESS &&
451 rc->user_status != US_NO_LOGIN_YET_KERBEROS_ONLY)
452 status = register_user(rc->uid, login);
453 if (status == MR_IN_USE)
455 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
459 else if (status == MR_DOWN)
461 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
464 else if (status != MR_SUCCESS)
466 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL,
467 error_message(status));
471 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
475 void SWRD(reg_client *rc, int argc, char **argv)
480 if (!rc->id || argc != 6)
482 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
486 getwordlist(rc->id, words);
487 for (i = 0; i < 6; i++)
489 if (strcasecmp(strtrim(argv[i]), words[i]))
494 reply(rc, BAD_SIX_WORDS, "GETW", "d", NULL);
501 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
503 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
506 void SPIN(reg_client *rc, int argc, char **argv)
508 EXEC SQL BEGIN DECLARE SECTION;
509 char pin[USERS_PIN_SIZE];
510 EXEC SQL END DECLARE SECTION;
512 if (!rc->id || argc != 1)
514 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
518 EXEC SQL SELECT pin INTO :pin FROM users WHERE clearid = :rc->id
519 AND status = :rc->user_status;
521 if (strcmp(argv[0], pin) != 0)
523 reply(rc, BAD_PIN, "GETI", "d", NULL);
530 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
533 register_user(rc->uid, rc->username);
534 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
538 void CLGN(reg_client *rc, int argc, char **argv)
544 if (!rc->uid || rc->id || rc->username || argc != 1)
546 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
552 /* make sure someone's not trying to overrun reply */
553 if (strlen(login) > 100)
555 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
560 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
561 (login[0] == '_') || isdigit(login[0]))
563 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
564 3, USERS_LOGIN_SIZE - 1);
568 for (i = 0; i < strlen(login); i++)
570 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
572 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
573 3, USERS_LOGIN_SIZE - 1);
578 status = check_kerberos(login);
579 if (status == MR_SUCCESS)
581 status = check_username_available(login);
582 if (status == MR_SUCCESS)
584 reply(rc, USERNAME_AVAILABLE, "LOGC", "c", login, login);
589 if (status == MR_IN_USE)
591 if (rc->reserved_username)
593 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
597 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
600 else if (status == MR_DOWN)
602 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
605 else if (status != MR_SUCCESS)
607 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
612 void LOGN(reg_client *rc, int argc, char **argv)
618 if (!rc->uid || rc->id || rc->username || argc != 1)
620 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
626 /* make sure someone's not trying to overrun reply */
627 if (strlen(login) > 100)
629 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
634 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
635 (login[0] == '_') || isdigit(login[0]))
637 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
638 3, USERS_LOGIN_SIZE - 1);
642 for (i = 0; i < strlen(login); i++)
644 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
646 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
647 3, USERS_LOGIN_SIZE - 1);
652 status = check_kerberos(login);
653 if (status == MR_SUCCESS)
655 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
656 EXEC SQL UPDATE users SET login = :login WHERE unix_uid = :rc->uid;
658 status = register_user(rc->uid, login);
660 if (status == MR_IN_USE)
662 if (rc->reserved_username)
664 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
668 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
671 else if (status == MR_DOWN)
673 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
676 else if (status != MR_SUCCESS)
678 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
682 rc->username = strdup(login);
685 com_err(whoami, errno, "in LOGN");
686 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
689 reply(rc, USERNAME_OK, "GETP", "c", NULL, login);
693 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^@ - ^O */
694 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^P - ^_ */
695 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* SPACE - / */
696 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, /* 0 - ? */
697 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* : - O */
698 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, /* P - _ */
699 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* ` - o */
700 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, /* p - ^? */
701 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
702 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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,
711 void PSWD(reg_client *rc, int argc, char **argv)
714 char *password = argv[0], *p;
715 EXEC SQL BEGIN DECLARE SECTION;
716 char *login = rc->username;
717 EXEC SQL END DECLARE SECTION;
719 if (!rc->username || rc->id || argc != 1)
721 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
725 /* password quality checking */
726 if (strlen(password) < 4)
728 reply(rc, PASSWORD_SHORT, "GETP", "c", NULL);
732 if (strlen(password) < 7)
734 for (p = password + 1; *p; p++)
736 if (ctypes[*p] != ctypes[*(p - 1)])
741 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
746 if (!strcasecmp(password, "GykoR-66") ||
747 !strcasecmp(password, "slaRooBey") ||
748 !strcasecmp(password, "krang-its") ||
749 !strcasecmp(password, "2HotPeetzas") ||
750 !strcasecmp(password, "ItzAGurl"))
752 reply(rc, PASSWORD_SAMPLE, "GETP", "c", NULL);
756 status = register_kerberos(rc->username, password);
757 if (status == MR_QUALITY)
759 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
762 else if (status == MR_IN_USE)
764 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
770 com_err(whoami, status, "registering username with Kerberos");
771 reply(rc, KADM_ERROR, "INIT", "c", NULL, error_message(status));
775 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
776 EXEC SQL UPDATE users SET status = 9 WHERE login = :login;
778 EXEC SQL UPDATE users SET status = 1 WHERE login = :login;
781 reply(rc, DONE, "INIT", "c", NULL, rc->username);
784 void QUIT(reg_client *rc, int argc, char **argv)
788 /* Register a user in Moira */
789 int register_user(int uid, char *username)
791 EXEC SQL BEGIN DECLARE SECTION;
792 char pin[USERS_PIN_SIZE];
793 EXEC SQL END DECLARE SECTION;
794 char uidbuf[10], *qargv[3], *motd = NULL;
797 status = mr_connect(hostname);
801 status = mr_motd(&motd);
808 status = krb_get_svc_in_tkt(REG_SVR_PRINCIPAL, REG_SVR_INSTANCE,
809 krb_realmofhost(hostname), MOIRA_SNAME,
810 shorthostname, 3, KEYFILE);
812 status += ERROR_TABLE_BASE_krb;
814 status = mr_krb5_auth("reg_svr");
817 com_err(whoami, status, "authenticating to moira");
822 EXEC SQL SELECT pin INTO :pin FROM users WHERE unix_uid = :uid;
824 sprintf(uidbuf, "%d", uid);
828 /* HACK: If user has a PIN set, they're from Sloan.
829 * Give them Exchange poboxes.
832 qargv[2] = "EXCHANGE";
836 status = mr_query("register_user", 3, qargv, NULL, NULL);
842 /* Find some typical available usernames */
844 char *uname_patterns[] = {
846 "fmllllll", /* jmdoe... (last name truncated) */
847 "flllllll", /* jdoe.... ("") */
848 "llllllll", /* doe..... ("") */
855 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
857 char *find_usernames(char *first, char *middle, char *last)
859 EXEC SQL BEGIN DECLARE SECTION;
860 char username[2 * USERS_LOGIN_SIZE];
862 EXEC SQL END DECLARE SECTION;
864 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
870 for (pat = 0; pat < num_patterns; pat++)
876 for (pp = uname_patterns[pat]; *pp; pp++)
886 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
887 up += sprintf(up, "%s", first);
905 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
906 up += sprintf(up, "%s", last);
914 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
917 EXEC SQL SELECT COUNT(login) INTO :count FROM users
918 WHERE login = :username;
926 EXEC SQL SELECT COUNT(name) INTO :count FROM list
927 WHERE name = :username;
936 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
937 WHERE label = :username;
949 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
952 strcat(unames, ", ");
953 strcat(unames, username);
957 unames = strdup(username);
967 /* unames will be NULL if we couldn't suggest a username. Clear
968 errno so the caller can distinguish this from an error case. */
973 /* This does the database-side checks to make sure a username is
976 int check_username_available(char *username)
980 EXEC SQL SELECT COUNT(login) INTO :count FROM users
981 WHERE login = :username;
987 EXEC SQL SELECT COUNT(name) INTO :count FROM list
988 WHERE name = :username;
994 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
995 WHERE label = :username;
1004 void fixname(char *name)
1008 for (s = d = name; *s; s++)
1016 void *xmalloc(size_t bytes)
1018 void *buf = malloc(bytes);
1023 com_err(whoami, errno, "in xmalloc");
1027 void *xrealloc(void *ptr, size_t bytes)
1029 void *buf = realloc(ptr, bytes);
1034 com_err(whoami, errno, "in xrealloc");
1038 char *xstrdup(char *str)
1040 char *buf = strdup(str);
1045 com_err(whoami, errno, "in xstrdup");
1049 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
1053 fputs(whoami, stderr);
1055 fprintf(stderr, "[#%d]", cl->clientid);
1056 fputs(": ", stderr);
1059 fputs(error_message(code), stderr);
1063 vfprintf(stderr, fmt, pvar);
1067 void sigshut(int sig)