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 /* Ignore the middle name since Moira doesn't have those reliably */
364 sqlstatus = sqlca.sqlcode;
365 EXEC SQL CLOSE csr_id;
369 reply(rc, NOT_FOUND_IN_DATABASE, "GETN", "d", NULL);
377 case US_ENROLL_NOT_ALLOWED:
378 case US_REGISTERED_KERBEROS_ONLY:
379 reply(rc, ALREADY_REGISTERED, "INIT", "c", NULL, login);
383 reply(rc, ACCOUNT_DELETED, "INIT", "c", NULL, login);
387 reply(rc, NOT_ELIGIBLE, "INIT", "c", NULL);
394 rc->user_status = status;
396 sprintf(fullname, "%s %s%s%s", first, middle, *middle ? " " : "", last);
397 if (!strcmp(class, "MITS"))
398 strcpy(class, "STAFF");
404 com_err(whoami, errno, "in RIFO");
405 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
411 rc->reserved_username = 1;
412 rc->username = strdup(login);
415 com_err(whoami, errno, "in RIFO");
416 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
422 rc->suggestions = find_usernames(first, middle, last);
423 if (!rc->suggestions && errno)
425 com_err(whoami, errno, "in RIFO");
426 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(errno));
434 reply(rc, FOUND, "GETI", "c", NULL, fullname, class);
436 reply(rc, FOUND, "GETW", "c", NULL, fullname, class);
438 else if (!rc->username)
439 reply(rc, FOUND, "GETL", "c", rc->suggestions, fullname, class);
442 if (rc->user_status == US_NO_LOGIN_YET ||
443 rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
445 status = check_kerberos(login);
446 if (status == MR_SUCCESS &&
447 rc->user_status != US_NO_LOGIN_YET_KERBEROS_ONLY)
448 status = register_user(rc->uid, login);
449 if (status == MR_IN_USE)
451 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
455 else if (status == MR_DOWN)
457 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
460 else if (status != MR_SUCCESS)
462 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL,
463 error_message(status));
467 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
471 void SWRD(reg_client *rc, int argc, char **argv)
476 if (!rc->id || argc != 6)
478 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
482 getwordlist(rc->id, words);
483 for (i = 0; i < 6; i++)
485 if (strcasecmp(strtrim(argv[i]), words[i]))
490 reply(rc, BAD_SIX_WORDS, "GETW", "d", NULL);
497 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
499 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
502 void SPIN(reg_client *rc, int argc, char **argv)
504 EXEC SQL BEGIN DECLARE SECTION;
505 char pin[USERS_PIN_SIZE];
506 EXEC SQL END DECLARE SECTION;
508 if (!rc->id || argc != 1)
510 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
514 EXEC SQL SELECT pin INTO :pin FROM users WHERE clearid = :rc->id
515 AND status = :rc->user_status;
517 if (strcmp(argv[0], pin) != 0)
519 reply(rc, BAD_PIN, "GETI", "d", NULL);
526 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
529 register_user(rc->uid, rc->username);
530 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
534 void LOGN(reg_client *rc, int argc, char **argv)
540 if (!rc->uid || rc->id || rc->username || argc != 1)
542 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
548 /* make sure someone's not trying to overrun reply */
549 if (strlen(login) > 100)
551 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
556 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
557 (login[0] == '_') || isdigit(login[0]))
559 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
560 3, USERS_LOGIN_SIZE - 1);
564 for (i = 0; i < strlen(login); i++)
566 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
568 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
569 3, USERS_LOGIN_SIZE - 1);
574 status = check_kerberos(login);
575 if (status == MR_SUCCESS)
577 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
578 EXEC SQL UPDATE users SET login = :login WHERE unix_uid = :rc->uid;
580 status = register_user(rc->uid, login);
582 if (status == MR_IN_USE)
584 if (rc->reserved_username)
586 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
590 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
593 else if (status == MR_DOWN)
595 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
598 else if (status != MR_SUCCESS)
600 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
604 rc->username = strdup(login);
607 com_err(whoami, errno, "in LOGN");
608 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
611 reply(rc, USERNAME_OK, "GETP", "c", NULL, login);
615 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^@ - ^O */
616 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^P - ^_ */
617 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* SPACE - / */
618 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, /* 0 - ? */
619 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* : - O */
620 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, /* P - _ */
621 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* ` - o */
622 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, /* p - ^? */
623 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
624 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
625 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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,
633 void PSWD(reg_client *rc, int argc, char **argv)
636 char *password = argv[0], *p;
637 EXEC SQL BEGIN DECLARE SECTION;
638 char *login = rc->username;
639 EXEC SQL END DECLARE SECTION;
641 if (!rc->username || rc->id || argc != 1)
643 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
647 /* password quality checking */
648 if (strlen(password) < 4)
650 reply(rc, PASSWORD_SHORT, "GETP", "c", NULL);
654 if (strlen(password) < 7)
656 for (p = password + 1; *p; p++)
658 if (ctypes[*p] != ctypes[*(p - 1)])
663 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
668 if (!strcasecmp(password, "GykoR-66") ||
669 !strcasecmp(password, "slaRooBey") ||
670 !strcasecmp(password, "krang-its") ||
671 !strcasecmp(password, "2HotPeetzas") ||
672 !strcasecmp(password, "ItzAGurl"))
674 reply(rc, PASSWORD_SAMPLE, "GETP", "c", NULL);
678 status = register_kerberos(rc->username, password);
679 if (status == MR_QUALITY)
681 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
684 else if (status == MR_IN_USE)
686 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
692 com_err(whoami, status, "registering username with Kerberos");
693 reply(rc, KADM_ERROR, "INIT", "c", NULL, error_message(status));
697 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
698 EXEC SQL UPDATE users SET status = 9 WHERE login = :login;
700 EXEC SQL UPDATE users SET status = 1 WHERE login = :login;
703 reply(rc, DONE, "INIT", "c", NULL, rc->username);
706 void QUIT(reg_client *rc, int argc, char **argv)
710 /* Register a user in Moira */
711 int register_user(int uid, char *username)
713 char uidbuf[10], *qargv[3], *motd = NULL;
716 status = mr_connect(hostname);
720 status = mr_motd(&motd);
727 status = krb_get_svc_in_tkt(REG_SVR_PRINCIPAL, REG_SVR_INSTANCE,
728 krb_realmofhost(hostname), MOIRA_SNAME,
729 shorthostname, 1, KEYFILE);
731 status += ERROR_TABLE_BASE_krb;
733 status = mr_auth("reg_svr");
736 com_err(whoami, status, "authenticating to moira");
741 sprintf(uidbuf, "%d", uid);
745 status = mr_query("register_user", 3, qargv, NULL, NULL);
751 /* Find some typical available usernames */
753 char *uname_patterns[] = {
755 "fmllllll", /* jmdoe... (last name truncated) */
756 "flllllll", /* jdoe.... ("") */
757 "llllllll", /* doe..... ("") */
764 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
766 char *find_usernames(char *first, char *middle, char *last)
768 EXEC SQL BEGIN DECLARE SECTION;
769 char username[2 * USERS_LOGIN_SIZE];
771 EXEC SQL END DECLARE SECTION;
773 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
779 for (pat = 0; pat < num_patterns; pat++)
785 for (pp = uname_patterns[pat]; *pp; pp++)
795 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
796 up += sprintf(up, "%s", first);
814 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
815 up += sprintf(up, "%s", last);
823 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
826 EXEC SQL SELECT COUNT(login) INTO :count FROM users
827 WHERE login = :username;
835 EXEC SQL SELECT COUNT(name) INTO :count FROM list
836 WHERE name = :username;
845 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
846 WHERE label = :username;
858 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
861 strcat(unames, ", ");
862 strcat(unames, username);
866 unames = strdup(username);
876 /* unames will be NULL if we couldn't suggest a username. Clear
877 errno so the caller can distinguish this from an error case. */
882 void fixname(char *name)
886 for (s = d = name; *s; s++)
894 void *xmalloc(size_t bytes)
896 void *buf = malloc(bytes);
901 com_err(whoami, errno, "in xmalloc");
905 void *xrealloc(void *ptr, size_t bytes)
907 void *buf = realloc(ptr, bytes);
912 com_err(whoami, errno, "in xrealloc");
916 char *xstrdup(char *str)
918 char *buf = strdup(str);
923 com_err(whoami, errno, "in xstrdup");
927 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
931 fputs(whoami, stderr);
933 fprintf(stderr, "[#%d]", cl->clientid);
937 fputs(error_message(code), stderr);
941 vfprintf(stderr, fmt, pvar);
945 void sigshut(int sig)