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];
304 int uid, status, secure, sqlstatus;
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, 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, :first, :middle, :last, :class;
336 /* Check names, allowing for the possibility that Moira and the
337 user might have them split up differently. eg, Mary/Ann/Singleton
338 vs. Mary Ann/Singleton. */
339 if (strcasecmp(last, ulast) && strncasecmp(last, ulast, strlen(last)) &&
340 strncasecmp(last, ulast, strlen(ulast)))
342 if (strlen(last) > 3 && strlen(ulast) < 3)
344 if (strcasecmp(first, ufirst) &&
345 strncasecmp(first, ufirst, strlen(first)) &&
346 strncasecmp(first, ufirst, strlen(ufirst)))
348 if (strlen(first) > 3 && strlen(ufirst) < 3)
350 /* Ignore the middle name since Moira doesn't have those reliably */
353 sqlstatus = sqlca.sqlcode;
354 EXEC SQL CLOSE csr_id;
358 reply(rc, NOT_FOUND_IN_DATABASE, "GETN", "d", NULL);
366 case US_ENROLL_NOT_ALLOWED:
367 reply(rc, ALREADY_REGISTERED, "INIT", "c", NULL, login);
371 reply(rc, ACCOUNT_DELETED, "INIT", "c", NULL, login);
375 reply(rc, NOT_ELIGIBLE, "INIT", "c", NULL);
383 sprintf(fullname, "%s %s%s%s", first, middle, *middle ? " " : "", last);
384 if (!strcmp(class, "MITS"))
385 strcpy(class, "STAFF");
391 com_err(whoami, errno, "in RIFO");
392 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
398 rc->reserved_username = 1;
399 rc->username = strdup(login);
402 com_err(whoami, errno, "in RIFO");
403 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
409 rc->suggestions = find_usernames(first, middle, last);
410 if (!rc->suggestions && errno)
412 com_err(whoami, errno, "in RIFO");
413 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(errno));
419 reply(rc, FOUND, "GETW", "c", NULL, fullname, class);
420 else if (!rc->username)
421 reply(rc, FOUND, "GETL", "c", rc->suggestions, fullname, class);
424 if (status == US_NO_LOGIN_YET)
426 status = check_kerberos(login);
427 if (status == MR_SUCCESS)
428 status = register_user(rc->uid, login);
429 if (status == MR_IN_USE)
431 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
435 else if (status == MR_DOWN)
437 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
440 else if (status != MR_SUCCESS)
442 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL,
443 error_message(status));
447 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
451 void SWRD(reg_client *rc, int argc, char **argv)
456 if (!rc->id || argc != 6)
458 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
462 getwordlist(rc->id, words);
463 for (i = 0; i < 6; i++)
465 if (strcasecmp(strtrim(argv[i]), words[i]))
470 reply(rc, BAD_SIX_WORDS, "GETW", "d", NULL);
477 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
479 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
482 void LOGN(reg_client *rc, int argc, char **argv)
488 if (!rc->uid || rc->id || rc->username || argc != 1)
490 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
496 /* make sure someone's not trying to overrun reply */
497 if (strlen(login) > 100)
499 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
504 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
505 (login[0] == '_') || isdigit(login[0]))
507 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
508 3, USERS_LOGIN_SIZE - 1);
512 for (i = 0; i < strlen(login); i++)
514 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
516 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
517 3, USERS_LOGIN_SIZE - 1);
522 status = check_kerberos(login);
523 if (status == MR_SUCCESS)
524 status = register_user(rc->uid, login);
526 if (status == MR_IN_USE)
528 if (rc->reserved_username)
530 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
534 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
537 else if (status == MR_DOWN)
539 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
542 else if (status != MR_SUCCESS)
544 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
548 rc->username = strdup(login);
551 com_err(whoami, errno, "in LOGN");
552 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
555 reply(rc, USERNAME_OK, "GETP", "c", NULL, login);
559 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^@ - ^O */
560 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^P - ^_ */
561 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* SPACE - / */
562 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, /* 0 - ? */
563 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* : - O */
564 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, /* P - _ */
565 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* ` - o */
566 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, /* p - ^? */
567 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
568 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
569 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
570 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
571 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
572 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
573 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
574 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
577 void PSWD(reg_client *rc, int argc, char **argv)
580 char *password = argv[0], *p;
581 EXEC SQL BEGIN DECLARE SECTION;
582 char *login = rc->username;
583 EXEC SQL END DECLARE SECTION;
585 if (!rc->username || rc->id || argc != 1)
587 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
591 /* password quality checking */
592 if (strlen(password) < 4)
594 reply(rc, PASSWORD_SHORT, "GETP", "c", NULL);
598 if (strlen(password) < 7)
600 for (p = password + 1; *p; p++)
602 if (ctypes[*p] != ctypes[*(p - 1)])
607 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
612 if (!strcasecmp(password, "GykoR-66") ||
613 !strcasecmp(password, "slaRooBey") ||
614 !strcasecmp(password, "krang-its") ||
615 !strcasecmp(password, "2HotPeetzas") ||
616 !strcasecmp(password, "ItzAGurl"))
618 reply(rc, PASSWORD_SAMPLE, "GETP", "c", NULL);
622 status = register_kerberos(rc->username, password);
623 if (status == MR_QUALITY)
625 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
628 else if (status == MR_IN_USE)
630 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
636 com_err(whoami, status, "registering username with Kerberos");
637 reply(rc, KADM_ERROR, "INIT", "c", NULL, error_message(status));
640 EXEC SQL UPDATE users SET status = 1 WHERE login = :login;
643 reply(rc, DONE, "INIT", "c", NULL, rc->username);
646 void QUIT(reg_client *rc, int argc, char **argv)
650 /* Register a user in Moira */
651 int register_user(int uid, char *username)
653 char uidbuf[10], *qargv[3], *motd = NULL;
656 status = mr_connect(hostname);
660 status = mr_motd(&motd);
667 status = krb_get_svc_in_tkt(REG_SVR_PRINCIPAL, REG_SVR_INSTANCE,
668 krb_realmofhost(hostname), MOIRA_SNAME,
669 shorthostname, 1, KEYFILE);
671 status += ERROR_TABLE_BASE_krb;
673 status = mr_auth("reg_svr");
676 com_err(whoami, status, "authenticating to moira");
681 sprintf(uidbuf, "%d", uid);
685 status = mr_query("register_user", 3, qargv, NULL, NULL);
691 /* Find some typical available usernames */
693 char *uname_patterns[] = {
695 "fmllllll", /* jmdoe... (last name truncated) */
696 "flllllll", /* jdoe.... ("") */
697 "llllllll", /* doe..... ("") */
704 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
706 char *find_usernames(char *first, char *middle, char *last)
708 EXEC SQL BEGIN DECLARE SECTION;
709 char username[2 * USERS_LOGIN_SIZE];
711 EXEC SQL END DECLARE SECTION;
713 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
719 for (pat = 0; pat < num_patterns; pat++)
725 for (pp = uname_patterns[pat]; *pp; pp++)
735 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
736 up += sprintf(up, "%s", first);
754 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
755 up += sprintf(up, "%s", last);
763 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
766 EXEC SQL SELECT COUNT(login) INTO :count FROM users
767 WHERE login = :username;
775 EXEC SQL SELECT COUNT(name) INTO :count FROM list
776 WHERE name = :username;
785 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
786 WHERE label = :username;
798 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
801 strcat(unames, ", ");
802 strcat(unames, username);
806 unames = strdup(username);
816 /* unames will be NULL if we couldn't suggest a username. Clear
817 errno so the caller can distinguish this from an error case. */
822 void fixname(char *name)
826 for (s = d = name; *s; s++)
834 void *xmalloc(size_t bytes)
836 void *buf = malloc(bytes);
841 com_err(whoami, errno, "in xmalloc");
845 void *xrealloc(void *ptr, size_t bytes)
847 void *buf = realloc(ptr, bytes);
852 com_err(whoami, errno, "in xrealloc");
856 char *xstrdup(char *str)
858 char *buf = strdup(str);
863 com_err(whoami, errno, "in xstrdup");
867 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
871 fputs(whoami, stderr);
873 fprintf(stderr, "[#%d]", cl->clientid);
877 fputs(error_message(code), stderr);
881 vfprintf(stderr, fmt, pvar);
885 void sigshut(int sig)