6 * Copyright (C) 1987, 1988 by the Massachusetts Institute of Technology
7 * For copying and distribution information, please see the file
10 * Server for user registration with Moira and Kerberos.
12 * This program is a client of the Kerberos admin_server and a
13 * server for the userreg program. It is not a client of the
14 * Moira server as it is linked with libmoiraglue which bypasses
15 * the network protocol.
19 static char *rcsid_reg_svr_c = "$Header$";
22 #include <mit-copyright.h>
26 #include <sys/types.h>
35 #include "moira_site.h"
38 extern char admin_errmsg[];
40 void reg_com_err_hook();
46 struct msg message; /* Storage for parsed packet */
47 int status = SUCCESS; /* Error status */
48 char retval[BUFSIZ]; /* Buffer to hold return message for client */
50 void req_initialize(); /* Initialize request layer */
51 void get_request(); /* Get a request */
52 void report(); /* Respond to a request */
57 /* Error messages sent one line at a time */
60 set_com_err_hook(reg_com_err_hook);
62 /* Initialize com_err error tables */
67 /* Use com_err or output to stderr for all log messages. */
69 com_err(whoami, 0, "*** Debugging messages enabled. ***");
72 /* Set the name of our kerberos ticket file */
73 krb_set_tkt_string("/tmp/tkt_ureg");
75 /* Connect to the Moira server */
76 if ((status = mr_connect(MOIRA_SERVER)) != MR_SUCCESS)
78 com_err(whoami, status, " on mr_connect");
82 /* Authorize, telling the server who you are */
83 if ((status = mr_auth(whoami)) != MR_SUCCESS)
85 com_err(whoami, status, " on mr_auth");
89 journal = fopen(REGJOURNAL, "a");
90 if (journal == NULL) {
91 com_err(whoami, errno, " while opening journal file");
95 /* Allow request layer to initialize */
98 /* Sit around waiting for requests from the client. */
101 get_request(&message);
103 switch((int)message.request)
105 case UREG_VERIFY_USER:
106 status = verify_user(&message,retval);
108 case UREG_RESERVE_LOGIN:
109 status = reserve_user(&message,retval);
111 case UREG_SET_PASSWORD:
113 status = set_password(&message,retval);
116 status = set_identity(&message,retval);
119 status = UREG_UNKNOWN_REQUEST;
120 critical_alert(FAIL_INST,"Unknown request %d from userreg.",
125 /* Report what happened to client */
126 report(status, retval);
130 int parse_encrypted(message,data)
131 struct msg *message; /* Formatted packet */
132 struct db_data *data; /* Data from the Moira database */
133 /* This routine makes sure that the ID from the database matches
134 the ID sent accross in the packet. The information in the packet
135 was created in the following way:
137 The plain text ID number was encrypted via EncryptID() resulting
138 in the form that would appear in the Moira database. This is
139 concatinated to the plain text ID so that the ID string contains plain
140 text ID followed by a null followed by the encrypted ID. Other
141 information such as the username or password is appended. The whole
142 thing is then DES encrypted using the encrypted ID as the source of
145 This routine tries each encrypted ID in the database that belongs
146 to someone with this user's first and last name and tries to
147 decrypt the packet with this information. If it succeeds, it returns
148 zero and initializes all the fields of the formatted packet structure
149 that depend on the encrypted information. */
151 des_cblock key; /* The key for DES en/decryption */
152 des_key_schedule sched; /* En/decryption schedule */
153 static char decrypt[BUFSIZ]; /* Buffer to hold decrypted information */
154 long decrypt_len; /* Length of decypted ID information */
155 char recrypt[14]; /* Buffer to hold re-encrypted information */
156 static char hashid[14]; /* Buffer to hold one-way encrypted ID */
157 char idnumber[BUFSIZ]; /* Buffer to hold plain-text ID */
158 char *temp; /* A temporary string pointer */
159 int len; /* Keeps track of length left in packet */
160 int status = SUCCESS; /* Error status */
163 com_err(whoami, 0, "Entering parse_encrypted");
166 /* Make the decrypted information length the same as the encrypted
167 information length. Both are integral multples of eight bytes
168 because of the DES encryption routines. */
169 decrypt_len = (long)message->encrypted_len;
171 /* Get key from the one-way encrypted ID in the Moira database */
172 des_string_to_key(data->mit_id, key);
173 /* Get schedule from key */
174 des_key_sched(key, sched);
175 /* Decrypt information from packet using this key. Since decrypt_len
176 is an integral multiple of eight bytes, it will probably be null-
178 des_pcbc_encrypt(message->encrypted, decrypt, decrypt_len,
179 sched, key, DES_DECRYPT);
181 /* Extract the plain text and encrypted ID fields from the decrypted
182 packet information. */
183 /* Since the decrypted information starts with the plain-text ID
184 followed by a null, if the decryption worked, this will only
185 copy the plain text part of the decrypted information. It is
186 important that strncpy be used because if we are not using the
187 correct key, there is no guarantee that a null will occur
188 anywhere in the string. */
189 (void) strncpy(idnumber,decrypt,(int)decrypt_len);
190 /* Check that the idnumber of a mismatched decryption doesn't overflow
193 if (strlen(idnumber) != 9) {
195 com_err(whoami, 0, "idnumber wrong size, probable user mismatch\n");
199 /* Point temp to the end of the plain text ID number. */
200 temp = decrypt + strlen(idnumber) + 1;
201 /* Find out how much more packet there is. */
202 len = message->encrypted_len - (temp - decrypt);
203 /* Copy the next CRYPT_LEN bytes of the decrypted information into
204 hashid if there are CRYPT_LEN more bytes to copy. There will be
205 if we have the right key. */
206 (void) strncpy(hashid, temp, min(len, CRYPT_LEN));
207 /* Point temp to the end of the encrypted ID field */
208 temp += strlen(hashid) + 1;
209 /* Find out how much more room there is. */
210 len = message->encrypted_len - (temp - decrypt);
212 /* Now compare encrypted ID's don't match. */
213 if (strcmp(hashid, data->mit_id)) status = FAILURE;
214 if (status == SUCCESS)
216 EncryptID(recrypt, idnumber, message->first, message->last);
217 /* Now compare encrypted plain text to ID from database. */
218 if (strcmp(recrypt, data->mit_id)) status = FAILURE;
221 if (status == SUCCESS)
223 /* We made it. Now we can finish initializing message. */
224 /* Point leftover to whatever is left over! */
225 message->leftover = temp;
226 message->leftover_len = len;
227 /* Since we know we have the right user, fill in the information
228 from the Moira database. */
229 message->db.reg_status = data->reg_status;
230 (void) strncpy(message->db.uid,data->uid, sizeof(message->db.uid));
231 (void) strncpy(message->db.mit_id,data->mit_id,
232 sizeof(message->db.mit_id));
233 (void) strncpy(message->db.login,data->login, sizeof(message->db.login));
238 com_err(whoami, status, " in parse_encrypted");
240 com_err(whoami, status, "parse_encrypted succeeded");
246 int db_callproc(argc,argv,queue)
247 int argc; /* Number of arguments returned by Moira */
248 char *argv[]; /* Arguments returned by Moira */
249 struct save_queue *queue; /* Queue to save information in */
250 /* This function is called by mr_query after each tuple found. It is
251 used by find_user to cache information about each user found. */
253 struct db_data *data; /* Structure to store the information in */
254 int status = SUCCESS; /* Error status */
257 com_err(whoami, 0, "Entering db_callproc.");
264 "Wrong number of arguments returned from get_user_by_name.");
269 /* extract the needed information from the results of the Moira query */
270 data = (struct db_data *)malloc(sizeof(struct db_data));
271 data->reg_status = atoi(argv[U_STATE]);
272 (void) strncpy(data->login,argv[U_NAME],sizeof(data->login));
273 (void) strncpy(data->mit_id,argv[U_MITID],sizeof(data->mit_id));
274 (void) strncpy(data->uid,argv[U_UID],sizeof(data->uid));
276 fprintf(stderr,"Found in database:\n");
277 fprintf(stderr," Registration status: %d\n",data->reg_status);
278 fprintf(stderr," login: %s\n",data->login);
279 fprintf(stderr," MIT ID: %s\n",data->mit_id);
280 fprintf(stderr," uid: %s\n",data->uid);
282 sq_save_data(queue,data);
288 int find_user(message)
289 struct msg *message; /* Formatted packet structure */
290 /* This routine verifies that a user is allowed to register by finding
291 him/her in the Moira database. It returns the status of the Moira
292 query that it calls. */
294 #define GUBN_ARGS 2 /* Arguements needed by get_user_by_name */
295 char *q_name; /* Name of query */
296 int q_argc; /* Number of arguments for query */
297 char *q_argv[GUBN_ARGS]; /* Arguments to query */
298 int status = SUCCESS; /* Query return status */
300 struct save_queue *queue; /* Queue to hold Moira data */
301 struct db_data *data; /* Structure for data for one tuple */
302 short verified = FALSE; /* Have we verified the user? */
304 /* Zero the mit_id field in the formatted packet structure. This
305 being zeroed means that no user was found. */
306 bzero(message->db.mit_id,sizeof(message->db.mit_id));
308 if (status == SUCCESS)
310 /* Get ready to make an Moira query */
311 q_name = "get_user_by_name";
312 q_argc = GUBN_ARGS; /* #defined in this routine */
313 q_argv[0] = message->first;
314 q_argv[1] = message->last;
316 /* Create queue to hold information */
320 status = mr_query(q_name,q_argc,q_argv,db_callproc,(char *)queue);
323 fprintf(stderr," %d returned by get_user_by_name\n",status);
326 if (status == MR_SUCCESS)
328 /* Traverse the list, freeing data as we go. If sq_get_data()
329 returns zero if there is no more data on the queue. */
330 while (sq_get_data(queue,&data))
333 /* parse_encrypted returns zero on success */
334 verified = (parse_encrypted(message,data) == SUCCESS);
339 /* Destroy the queue */
344 fprintf(stderr,"Returned from find_user\n");
345 fprintf(stderr," MIT ID: %s\n", message->db.mit_id);
346 fprintf(stderr," Registration status: %d\n",message->db.reg_status);
347 fprintf(stderr," uid: %s\n",message->db.uid);
348 fprintf(stderr," login: %s\n",message->db.login);
349 fprintf(stderr," Status from query: %d\n",status);
355 int verify_user(message,retval)
358 /* This routine determines whether a user is in the databse and returns
359 his state so that other routines can figure out whether he is the
360 correct state for various transactions. */
363 int status = SUCCESS; /* Return status */
365 /* Log that we are about to veryify user */
366 com_err(whoami, 0, "verifying user %s %s",message->first,message->last);
368 /* Figure out what user (if any) can be found based on the
369 encrypted information in the packet. (See the comment on
370 parse_encrypted().) */
372 status = find_user(message);
374 /* If Moira coudn't find the user */
375 if (status == MR_NO_MATCH)
376 status = UREG_USER_NOT_FOUND;
377 else if (status == MR_SUCCESS)
379 /* If the information sent over in the packet did not point to a
380 valid user, the mit_id field in the formatted packet structure
382 if (message->db.mit_id[0] == NULL)
383 status = UREG_USER_NOT_FOUND;
384 /* If the user was found but the registration has already started,
385 use this as the status */
388 switch (message->db.reg_status)
390 case US_NO_LOGIN_YET:
394 status = UREG_ALREADY_REGISTERED;
397 status = UREG_NO_PASSWD_YET;
400 status = UREG_DELETED;
403 status = UREG_NOT_ALLOWED;
406 status = UREG_ENROLLED;
408 case US_ENROLL_NOT_ALLOWED:
409 status = UREG_ENROLL_NOT_ALLOWED;
411 case US_HALF_ENROLLED:
412 status = UREG_HALF_ENROLLED;
415 status = UREG_MISC_ERROR;
416 critical_alert(FAIL_INST,"Bad user state %d for login %s.",
417 message->db.reg_status, message->db.login);
420 /* Set retval to the login name so that the client can use
421 it in the error message it will give the user. */
422 (void) strcpy(retval,message->db.login);
427 com_err(whoami, status, " returned from verify_user");
429 com_err(whoami, 0, "User verified");
436 unsigned int status = SUCCESS; /* Return status */
437 static char krbrealm[REALM_SZ]; /* kerberos realm name */
438 static char hostbuf[BUFSIZ], *host; /* local hostname in principal fmt */
439 static int inited = 0;
443 com_err(whoami, 0, "Entering ureg_kadm_init");
448 bzero(krbrealm, sizeof(krbrealm));
449 if (status = krb_get_lrealm(krbrealm, 1)) {
450 status += krb_err_base;
451 com_err(whoami, status, " fetching kerberos realm");
454 if (gethostname(hostbuf, sizeof(hostbuf)) < 0)
455 com_err(whoami, errno, "getting local hostname");
456 host = canonicalize_hostname(strsave(hostbuf));
457 for (p = host; *p && *p != '.'; p++)
463 /* Get keys for interacting with Kerberos admin server. */
464 /* principal, instance, realm, service, service instance, life, file */
465 if (status = krb_get_svc_in_tkt(MOIRA_SNAME, host, krbrealm, PWSERV_NAME,
466 KADM_SINST, 1, KEYFILE))
467 status += krb_err_base;
469 if (status != SUCCESS)
470 com_err(whoami, status, " while get admin tickets");
473 com_err(whoami, status, "Succeeded in getting admin tickets");
477 if (status == SUCCESS) {
478 if ((status = kadm_init_link(PWSERV_NAME, KADM_SINST, krbrealm)) !=
480 com_err(whoami, status, " while initializing kadmin connection");
487 int null_callproc(argc,argv,message)
491 /* This routine is a null callback that should be used for queries that
492 do not return tuples. If it ever gets called, something is wrong. */
494 critical_alert(FAIL_INST,"Something returned from an update query.");
499 * This routine reserves a principal in kerberos by setting up a
500 * principal with a random initial key.
502 int reserve_krb(login)
505 int status = SUCCESS;
508 u_long *lkey = (u_long *)key;
511 com_err(whoami, 0, "Entering reserve_krb");
514 if ((status = ureg_kadm_init()) == SUCCESS) {
515 bzero((char *)&new, sizeof(new));
516 SET_FIELD(KADM_DESKEY, new.fields);
517 SET_FIELD(KADM_NAME, new.fields);
519 (void) des_random_key(key);
520 new.key_low = htonl(lkey[0]);
521 new.key_high = htonl(lkey[1]);
522 strcpy(new.name, login);
524 com_err(whoami, 0, "Creating kerberos principal for %s", login);
525 status = kadm_add(&new);
526 if (status != KADM_SUCCESS)
527 com_err(whoami, status, " while reserving principal");
529 bzero((char *)&new, sizeof(new));
538 * This routine reserves a principal in kerberos by setting up a
539 * principal with a random initial key.
541 int setpass_krb(login, password)
545 int status = SUCCESS;
548 u_long *lkey = (u_long *)key;
550 if ((status = ureg_kadm_init()) == SUCCESS) {
551 bzero((char *)&new, sizeof(new));
552 SET_FIELD(KADM_DESKEY, new.fields);
553 SET_FIELD(KADM_NAME, new.fields);
555 (void) des_string_to_key(password, key);
556 new.key_low = htonl(lkey[0]);
557 new.key_high = htonl(lkey[1]);
558 strcpy(new.name, login);
560 com_err(whoami, 0, "Setting password for %s", login);
561 /* First arguement is not used if user has modify privileges */
562 if ((status = kadm_mod(&new, &new)) != KADM_SUCCESS) {
563 if (status == KADM_NOENTRY) {
565 "kerberos principal doesn't exist; creating");
566 if ((status = kadm_add(&new)) != KADM_SUCCESS)
567 com_err(whoami, status,
568 " while creating kerberos principal");
571 com_err(whoami, status, " while setting password");
579 int reserve_user(message,retval)
583 int q_argc; /* Number of arguments to query */
584 char *q_argv[3]; /* Arguments to Moira query */
585 char *q_name; /* Name of Moira query */
586 int status = SUCCESS; /* General purpose error status */
587 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
588 char *login; /* The login name the user wants */
589 register int i; /* A counter */
591 /* Log that we are about to reserve a user. */
592 com_err(whoami, 0, "reserving user %s %s",
593 message->first, message->last);
595 /* Check to make sure that we can verify this user. */
596 if ((status = verify_user(message,retval)) == SUCCESS)
598 /* Get the requested login name from leftover packet information. */
599 login = message->leftover;
601 /* Check the login name for validity. The login name is currently
602 is allowed to contain lowercase letters in any position and
603 and numbers and underscore characters in any position but the
605 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
606 status = UREG_INVALID_UNAME;
608 if (status == SUCCESS)
609 if ((login[0] == '_') || isdigit(login[0]))
610 status = UREG_INVALID_UNAME;
612 if (status == SUCCESS)
614 for (i = 0; i < strlen(login); i++)
615 if (!islower(login[i]) && !isdigit(login[i]) &&
618 status = UREG_INVALID_UNAME;
622 if (status == SUCCESS)
624 /* Now that we have a valid user with a valid login... */
626 /* First, try to reserve the user in Moira. */
627 (void) sprintf(fstype_buf,"%d",MR_FS_STUDENT);
628 q_name = "register_user";
629 q_argv[0] = message->db.uid;
631 q_argv[2] = fstype_buf;
633 status = mr_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
640 status = UREG_LOGIN_USED;
643 status = UREG_MISC_ERROR;
646 critical_alert(FAIL_INST,"%s returned from register_user.",
647 error_message(status));
648 status = UREG_MISC_ERROR;
653 if (status == SUCCESS)
656 * Moira login was successfully created; try to reserve kerberos
659 * If this routine fails, store the login in the retval so
660 * that it can be used in the client-side error message.
662 if ((status = reserve_krb(login)) != SUCCESS)
663 (void) strcpy(retval, login);
667 com_err(whoami, status, " returned from reserve_user");
669 com_err(whoami, 0, "User reserved");
675 int set_final_status(message)
677 /* This routine updates a user's registration status to fully
681 char *q_name; /* Name of Moira query */
682 int q_argc; /* Number of arguments for Moira query */
683 char *q_argv[2]; /* Arguments to get user by uid */
684 char state[7]; /* Can hold a 16 bit integer */
685 int status; /* Error status */
687 if (message->request == UREG_SET_PASSWORD)
688 (void) sprintf(state,"%d",US_REGISTERED);
689 else if (message->db.reg_status == US_NO_LOGIN_YET)
690 (void) sprintf(state,"%d",US_ENROLLED);
692 (void) sprintf(state,"%d",US_ENROLL_NOT_ALLOWED);
694 login = message->db.login;
695 com_err(whoami, 0, "Setting final status for %s to %s", login, state);
697 q_name = "update_user_status";
701 if ((status = mr_query(q_name, q_argc, q_argv, null_callproc,
702 (char *)0)) != MR_SUCCESS) {
703 if (status == MR_DEADLOCK)
704 status = UREG_MISC_ERROR;
706 critical_alert(FAIL_INST,"%s returned from update_user_status.",
707 error_message(status));
710 com_err(whoami, status, " returned from set_final_status");
712 com_err(whoami, 0, "Final status set");
717 int set_password(message,retval)
720 /* This routine is used to set the initial password for the new user. */
722 int status = SUCCESS; /* Return status */
723 char *passwd; /* User's password */
725 com_err(whoami, 0, "setting password %s %s",
726 message->first, message->last);
728 status = verify_user(message,retval);
730 /* Don't set the password unless the registration status of the user
731 is that he exists and has no password. */
732 if (status == SUCCESS)
733 status = UREG_NO_LOGIN_YET;
734 if (((int)message->request == UREG_SET_PASSWORD &&
735 status == UREG_NO_PASSWD_YET) ||
736 ((int)message->request == UREG_GET_KRB &&
737 status == UREG_HALF_ENROLLED))
739 /* User is in proper state for this transaction. */
741 passwd = message->leftover;
744 if ((status = setpass_krb(message->db.login, passwd)) != SUCCESS)
745 /* If failure, allow login name to be used in client
747 (void) strcpy(retval,message->db.login);
749 /* Otherwise, mark user as finished. */
750 status = set_final_status(message);
754 com_err(whoami, status, " returned from set_passwd");
756 com_err(whoami, 0, "Password set");
762 int getuserinfo(argc, argv, qargv)
767 int status = SUCCESS;
770 critical_alert(FAIL_INST,
771 "Wrong number of args returned from get_user_by_uid");
774 qargv[U_NAME] = strsave(argv[U_NAME]);
775 qargv[U_UID+1] = strsave(argv[U_UID]);
776 qargv[U_SHELL+1] = strsave(argv[U_SHELL]);
777 qargv[U_LAST+1] = strsave(argv[U_LAST]);
778 qargv[U_FIRST+1] = strsave(argv[U_FIRST]);
779 qargv[U_MIDDLE+1] = strsave(argv[U_MIDDLE]);
780 qargv[U_STATE+1] = strsave(argv[U_STATE]);
781 qargv[U_MITID+1] = strsave(argv[U_MITID]);
782 qargv[U_CLASS+1] = strsave(argv[U_CLASS]);
783 qargv[U_MODTIME+1] = NULL;
789 int set_identity(message,retval)
793 int q_argc; /* Number of arguments to query */
794 char *q_argv[U_END]; /* Arguments to Moira query */
795 char *q_name; /* Name of Moira query */
796 int status = SUCCESS; /* General purpose error status */
797 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
798 char *login; /* The login name the user wants */
799 register int i; /* A counter */
801 /* Log that we are about to reserve a user. */
802 com_err(whoami, 0, "setting identity %s %s",
803 message->first, message->last);
805 /* Check to make sure that we can verify this user. */
806 status = verify_user(message,retval);
807 if (status == SUCCESS || status == UREG_NOT_ALLOWED)
810 /* Get the requested login name from leftover packet information. */
811 login = message->leftover;
813 /* Check the login name for validity. The login name is currently
814 is allowed to contain lowercase letters in any position and
815 and numbers and underscore characters in any position but the
817 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
818 status = UREG_INVALID_UNAME;
820 if (status == SUCCESS)
821 if ((login[0] == '_') || isdigit(login[0]))
822 status = UREG_INVALID_UNAME;
823 if (status == SUCCESS)
825 for (i = 0; i < strlen(login); i++)
826 if (!islower(login[i]) && !isdigit(login[i]) &&
829 status = UREG_INVALID_UNAME;
833 if (status == SUCCESS)
835 /* Now that we have a valid user with a valid login... */
837 q_argv[0] = message->db.uid;
838 status = mr_query("get_user_by_uid", 1, q_argv, getuserinfo, q_argv);
839 if (status != SUCCESS) {
840 com_err(whoami, status, " while getting user info");
843 q_argv[U_NAME+1] = login;
844 q_argv[U_STATE+1] = "7";
845 status = mr_query("update_user", U_MODTIME+1, q_argv,
846 null_callproc, NULL);
853 status = UREG_LOGIN_USED;
856 status = UREG_MISC_ERROR;
859 critical_alert(FAIL_INST,"%s returned from update_user.",
860 error_message(status));
861 status = UREG_MISC_ERROR;
865 if (status == SUCCESS)
867 /* Moira login was successfully created; try to reserve kerberos
869 /* If this routine fails, store the login in the retval so
870 that it can be used in the client-side error message. */
871 if ((status = reserve_krb(login)) != SUCCESS)
872 (void) strcpy(retval, login);
876 com_err(whoami, status, " returned from set_identity");
878 com_err(whoami, 0, "Identity set");
884 void reg_com_err_hook(whoami, code, fmt, pvar)
891 fputs(whoami, stderr);
895 fputs(error_message(code), stderr);
898 _doprnt(fmt, pvar, stderr);