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 char uidbuf[10], *qargv[3], *motd = NULL;
794 status = mr_connect(hostname);
798 status = mr_motd(&motd);
805 status = krb_get_svc_in_tkt(REG_SVR_PRINCIPAL, REG_SVR_INSTANCE,
806 krb_realmofhost(hostname), MOIRA_SNAME,
807 shorthostname, 3, KEYFILE);
809 status += ERROR_TABLE_BASE_krb;
811 status = mr_krb5_auth("reg_svr");
814 com_err(whoami, status, "authenticating to moira");
819 sprintf(uidbuf, "%d", uid);
823 status = mr_query("register_user", 3, qargv, NULL, NULL);
829 /* Find some typical available usernames */
831 char *uname_patterns[] = {
833 "fmllllll", /* jmdoe... (last name truncated) */
834 "flllllll", /* jdoe.... ("") */
835 "llllllll", /* doe..... ("") */
842 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
844 char *find_usernames(char *first, char *middle, char *last)
846 EXEC SQL BEGIN DECLARE SECTION;
847 char username[2 * USERS_LOGIN_SIZE];
849 EXEC SQL END DECLARE SECTION;
851 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
857 for (pat = 0; pat < num_patterns; pat++)
863 for (pp = uname_patterns[pat]; *pp; pp++)
873 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
874 up += sprintf(up, "%s", first);
892 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
893 up += sprintf(up, "%s", last);
901 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
904 EXEC SQL SELECT COUNT(login) INTO :count FROM users
905 WHERE login = :username;
913 EXEC SQL SELECT COUNT(name) INTO :count FROM list
914 WHERE name = :username;
923 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
924 WHERE label = :username;
936 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
939 strcat(unames, ", ");
940 strcat(unames, username);
944 unames = strdup(username);
954 /* unames will be NULL if we couldn't suggest a username. Clear
955 errno so the caller can distinguish this from an error case. */
960 /* This does the database-side checks to make sure a username is
963 int check_username_available(char *username)
967 EXEC SQL SELECT COUNT(login) INTO :count FROM users
968 WHERE login = :username;
974 EXEC SQL SELECT COUNT(name) INTO :count FROM list
975 WHERE name = :username;
981 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
982 WHERE label = :username;
991 void fixname(char *name)
995 for (s = d = name; *s; s++)
1003 void *xmalloc(size_t bytes)
1005 void *buf = malloc(bytes);
1010 com_err(whoami, errno, "in xmalloc");
1014 void *xrealloc(void *ptr, size_t bytes)
1016 void *buf = realloc(ptr, bytes);
1021 com_err(whoami, errno, "in xrealloc");
1025 char *xstrdup(char *str)
1027 char *buf = strdup(str);
1032 com_err(whoami, errno, "in xstrdup");
1036 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
1040 fputs(whoami, stderr);
1042 fprintf(stderr, "[#%d]", cl->clientid);
1043 fputs(": ", stderr);
1046 fputs(error_message(code), stderr);
1050 vfprintf(stderr, fmt, pvar);
1054 void sigshut(int sig)