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, string_id;
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 reply(rc, ALREADY_REGISTERED, "INIT", "c", NULL, login);
372 reply(rc, ACCOUNT_DELETED, "INIT", "c", NULL, login);
376 reply(rc, NOT_ELIGIBLE, "INIT", "c", NULL);
384 sprintf(fullname, "%s %s%s%s", first, middle, *middle ? " " : "", last);
385 if (!strcmp(class, "MITS"))
386 strcpy(class, "STAFF");
392 com_err(whoami, errno, "in RIFO");
393 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
399 rc->reserved_username = 1;
400 rc->username = strdup(login);
403 com_err(whoami, errno, "in RIFO");
404 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
410 rc->suggestions = find_usernames(first, middle, last);
411 if (!rc->suggestions && errno)
413 com_err(whoami, errno, "in RIFO");
414 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(errno));
422 reply(rc, FOUND, "GETI", "c", NULL, fullname, class);
424 reply(rc, FOUND, "GETW", "c", NULL, fullname, class);
426 else if (!rc->username)
427 reply(rc, FOUND, "GETL", "c", rc->suggestions, fullname, class);
430 if (status == US_NO_LOGIN_YET)
432 status = check_kerberos(login);
433 if (status == MR_SUCCESS)
434 if (!strcmp(class, "LINCOLN"))
436 EXEC SQL SELECT string_id INTO :string_id FROM strings
437 WHERE string = 'LINCOLN: no pobox or filesys';
438 EXEC SQL UPDATE users SET comments = :string_id
439 WHERE login = :login;
442 status = register_user(rc->uid, login);
443 if (status == MR_IN_USE)
445 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
449 else if (status == MR_DOWN)
451 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
454 else if (status != MR_SUCCESS)
456 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL,
457 error_message(status));
461 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
465 void SWRD(reg_client *rc, int argc, char **argv)
470 if (!rc->id || argc != 6)
472 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
476 getwordlist(rc->id, words);
477 for (i = 0; i < 6; i++)
479 if (strcasecmp(strtrim(argv[i]), words[i]))
484 reply(rc, BAD_SIX_WORDS, "GETW", "d", NULL);
491 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
493 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
496 void SPIN(reg_client *rc, int argc, char **argv)
498 EXEC SQL BEGIN DECLARE SECTION;
499 char pin[USERS_PIN_SIZE];
500 EXEC SQL END DECLARE SECTION;
502 if (!rc->id || argc != 1)
504 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
508 EXEC SQL SELECT pin INTO :pin FROM users WHERE clearid = :rc->id;
510 if (strcmp(argv[0], pin) != 0)
512 reply(rc, BAD_PIN, "GETI", "d", NULL);
519 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
521 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
524 void LOGN(reg_client *rc, int argc, char **argv)
530 if (!rc->uid || rc->id || rc->username || argc != 1)
532 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
538 /* make sure someone's not trying to overrun reply */
539 if (strlen(login) > 100)
541 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
546 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
547 (login[0] == '_') || isdigit(login[0]))
549 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
550 3, USERS_LOGIN_SIZE - 1);
554 for (i = 0; i < strlen(login); i++)
556 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
558 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
559 3, USERS_LOGIN_SIZE - 1);
564 status = check_kerberos(login);
565 if (status == MR_SUCCESS)
566 status = register_user(rc->uid, login);
568 if (status == MR_IN_USE)
570 if (rc->reserved_username)
572 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
576 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
579 else if (status == MR_DOWN)
581 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
584 else if (status != MR_SUCCESS)
586 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
590 rc->username = strdup(login);
593 com_err(whoami, errno, "in LOGN");
594 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
597 reply(rc, USERNAME_OK, "GETP", "c", NULL, login);
601 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^@ - ^O */
602 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^P - ^_ */
603 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* SPACE - / */
604 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, /* 0 - ? */
605 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* : - O */
606 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, /* P - _ */
607 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* ` - o */
608 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, /* p - ^? */
609 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
610 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
611 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
612 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
613 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
614 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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,
619 void PSWD(reg_client *rc, int argc, char **argv)
622 char *password = argv[0], *p;
623 EXEC SQL BEGIN DECLARE SECTION;
624 char *login = rc->username;
625 EXEC SQL END DECLARE SECTION;
627 if (!rc->username || rc->id || argc != 1)
629 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
633 /* password quality checking */
634 if (strlen(password) < 4)
636 reply(rc, PASSWORD_SHORT, "GETP", "c", NULL);
640 if (strlen(password) < 7)
642 for (p = password + 1; *p; p++)
644 if (ctypes[*p] != ctypes[*(p - 1)])
649 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
654 if (!strcasecmp(password, "GykoR-66") ||
655 !strcasecmp(password, "slaRooBey") ||
656 !strcasecmp(password, "krang-its") ||
657 !strcasecmp(password, "2HotPeetzas") ||
658 !strcasecmp(password, "ItzAGurl"))
660 reply(rc, PASSWORD_SAMPLE, "GETP", "c", NULL);
664 status = register_kerberos(rc->username, password);
665 if (status == MR_QUALITY)
667 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
670 else if (status == MR_IN_USE)
672 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
678 com_err(whoami, status, "registering username with Kerberos");
679 reply(rc, KADM_ERROR, "INIT", "c", NULL, error_message(status));
682 EXEC SQL UPDATE users SET status = 1 WHERE login = :login;
685 reply(rc, DONE, "INIT", "c", NULL, rc->username);
688 void QUIT(reg_client *rc, int argc, char **argv)
692 /* Register a user in Moira */
693 int register_user(int uid, char *username)
695 char uidbuf[10], *qargv[3], *motd = NULL;
698 status = mr_connect(hostname);
702 status = mr_motd(&motd);
709 status = krb_get_svc_in_tkt(REG_SVR_PRINCIPAL, REG_SVR_INSTANCE,
710 krb_realmofhost(hostname), MOIRA_SNAME,
711 shorthostname, 1, KEYFILE);
713 status += ERROR_TABLE_BASE_krb;
715 status = mr_auth("reg_svr");
718 com_err(whoami, status, "authenticating to moira");
723 sprintf(uidbuf, "%d", uid);
727 status = mr_query("register_user", 3, qargv, NULL, NULL);
733 /* Find some typical available usernames */
735 char *uname_patterns[] = {
737 "fmllllll", /* jmdoe... (last name truncated) */
738 "flllllll", /* jdoe.... ("") */
739 "llllllll", /* doe..... ("") */
746 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
748 char *find_usernames(char *first, char *middle, char *last)
750 EXEC SQL BEGIN DECLARE SECTION;
751 char username[2 * USERS_LOGIN_SIZE];
753 EXEC SQL END DECLARE SECTION;
755 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
761 for (pat = 0; pat < num_patterns; pat++)
767 for (pp = uname_patterns[pat]; *pp; pp++)
777 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
778 up += sprintf(up, "%s", first);
796 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
797 up += sprintf(up, "%s", last);
805 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
808 EXEC SQL SELECT COUNT(login) INTO :count FROM users
809 WHERE login = :username;
817 EXEC SQL SELECT COUNT(name) INTO :count FROM list
818 WHERE name = :username;
827 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
828 WHERE label = :username;
840 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
843 strcat(unames, ", ");
844 strcat(unames, username);
848 unames = strdup(username);
858 /* unames will be NULL if we couldn't suggest a username. Clear
859 errno so the caller can distinguish this from an error case. */
864 void fixname(char *name)
868 for (s = d = name; *s; s++)
876 void *xmalloc(size_t bytes)
878 void *buf = malloc(bytes);
883 com_err(whoami, errno, "in xmalloc");
887 void *xrealloc(void *ptr, size_t bytes)
889 void *buf = realloc(ptr, bytes);
894 com_err(whoami, errno, "in xrealloc");
898 char *xstrdup(char *str)
900 char *buf = strdup(str);
905 com_err(whoami, errno, "in xstrdup");
909 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
913 fputs(whoami, stderr);
915 fprintf(stderr, "[#%d]", cl->clientid);
919 fputs(error_message(code), stderr);
923 vfprintf(stderr, fmt, pvar);
927 void sigshut(int sig)