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 database used to contain encrypted IDs. Now we don't encrypt
138 them in the database, although there are still some encrypted IDs
141 The plain text ID number was encrypted via EncryptID() resulting
142 in the form that would appear in the Moira database. This is
143 concatinated to the plain text ID so that the ID string contains plain
144 text ID followed by a null followed by the encrypted ID. Other
145 information such as the username or password is appended. The whole
146 thing is then DES encrypted using the encrypted ID as the source of
149 This routine tries each ID in the database that belongs
150 to someone with this user's first and last name and tries to
151 decrypt the packet with this information. If it succeeds, it returns
152 zero and initializes all the fields of the formatted packet structure
153 that depend on the encrypted information. */
155 des_cblock key; /* The key for DES en/decryption */
156 des_key_schedule sched; /* En/decryption schedule */
157 static char decrypt[BUFSIZ]; /* Buffer to hold decrypted information */
158 long decrypt_len; /* Length of decypted ID information */
159 char recrypt[14]; /* Buffer to hold re-encrypted information */
160 static char hashid[14]; /* Buffer to hold one-way encrypted ID */
161 char idnumber[BUFSIZ]; /* Buffer to hold plain-text ID */
162 char *temp; /* A temporary string pointer */
163 int len; /* Keeps track of length left in packet */
164 int status = SUCCESS; /* Error status */
167 com_err(whoami, 0, "Entering parse_encrypted");
170 /* Make the decrypted information length the same as the encrypted
171 information length. Both are integral multples of eight bytes
172 because of the DES encryption routines. */
173 decrypt_len = (long)message->encrypted_len;
175 /* Get key from the possibly one-way encrypted ID in the Moira database */
176 if (data->mit_id[0] >= '0' && data->mit_id[0] <= '9') {
179 EncryptID(buf, data->mit_id, message->first, message->last);
180 des_string_to_key(buf, key);
182 des_string_to_key(data->mit_id, key);
184 /* Get schedule from key */
185 des_key_sched(key, sched);
186 /* Decrypt information from packet using this key. Since decrypt_len
187 is an integral multiple of eight bytes, it will probably be null-
189 des_pcbc_encrypt(message->encrypted, decrypt, decrypt_len,
190 sched, key, DES_DECRYPT);
192 /* Extract the plain text and encrypted ID fields from the decrypted
193 packet information. */
194 /* Since the decrypted information starts with the plain-text ID
195 followed by a null, if the decryption worked, this will only
196 copy the plain text part of the decrypted information. It is
197 important that strncpy be used because if we are not using the
198 correct key, there is no guarantee that a null will occur
199 anywhere in the string. */
200 (void) strncpy(idnumber,decrypt,(int)decrypt_len);
201 /* Check that the idnumber of a mismatched decryption doesn't overflow
204 if (strlen(idnumber) != 9) {
206 com_err(whoami, 0, "idnumber wrong size, probable user mismatch\n");
210 /* Point temp to the end of the plain text ID number. */
211 temp = decrypt + strlen(idnumber) + 1;
212 /* Find out how much more packet there is. */
213 len = message->encrypted_len - (temp - decrypt);
214 /* Copy the next CRYPT_LEN bytes of the decrypted information into
215 hashid if there are CRYPT_LEN more bytes to copy. There will be
216 if we have the right key. */
217 (void) strncpy(hashid, temp, min(len, CRYPT_LEN));
218 /* Point temp to the end of the encrypted ID field */
219 temp += strlen(hashid) + 1;
220 /* Find out how much more room there is. */
221 len = message->encrypted_len - (temp - decrypt);
223 /* Now compare encrypted ID and clear text ID for a match. */
224 if (strcmp(hashid, data->mit_id) &&
225 strcmp(idnumber, data->mit_id))
228 if (status == SUCCESS)
230 /* We made it. Now we can finish initializing message. */
231 /* Point leftover to whatever is left over! */
232 message->leftover = temp;
233 message->leftover_len = len;
234 /* Since we know we have the right user, fill in the information
235 from the Moira database. */
236 message->db.reg_status = data->reg_status;
237 (void) strncpy(message->db.uid,data->uid, sizeof(message->db.uid));
238 (void) strncpy(message->db.mit_id,data->mit_id,
239 sizeof(message->db.mit_id));
240 (void) strncpy(message->db.login,data->login, sizeof(message->db.login));
245 com_err(whoami, status, " in parse_encrypted");
247 com_err(whoami, status, "parse_encrypted succeeded");
253 int db_callproc(argc,argv,queue)
254 int argc; /* Number of arguments returned by Moira */
255 char *argv[]; /* Arguments returned by Moira */
256 struct save_queue *queue; /* Queue to save information in */
257 /* This function is called by mr_query after each tuple found. It is
258 used by find_user to cache information about each user found. */
260 struct db_data *data; /* Structure to store the information in */
261 int status = SUCCESS; /* Error status */
264 com_err(whoami, 0, "Entering db_callproc.");
271 "Wrong number of arguments returned from get_user_by_name.");
276 /* extract the needed information from the results of the Moira query */
277 data = (struct db_data *)malloc(sizeof(struct db_data));
278 data->reg_status = atoi(argv[U_STATE]);
279 (void) strncpy(data->login,argv[U_NAME],sizeof(data->login));
280 (void) strncpy(data->mit_id,argv[U_MITID],sizeof(data->mit_id));
281 (void) strncpy(data->uid,argv[U_UID],sizeof(data->uid));
283 fprintf(stderr,"Found in database:\n");
284 fprintf(stderr," Registration status: %d\n",data->reg_status);
285 fprintf(stderr," login: %s\n",data->login);
286 fprintf(stderr," MIT ID: %s\n",data->mit_id);
287 fprintf(stderr," uid: %s\n",data->uid);
289 sq_save_data(queue,data);
295 int find_user(message)
296 struct msg *message; /* Formatted packet structure */
297 /* This routine verifies that a user is allowed to register by finding
298 him/her in the Moira database. It returns the status of the Moira
299 query that it calls. */
301 #define GUBN_ARGS 2 /* Arguements needed by get_user_by_name */
302 char *q_name; /* Name of query */
303 int q_argc; /* Number of arguments for query */
304 char *q_argv[GUBN_ARGS]; /* Arguments to query */
305 int status = SUCCESS; /* Query return status */
307 struct save_queue *queue; /* Queue to hold Moira data */
308 struct db_data *data; /* Structure for data for one tuple */
309 short verified = FALSE; /* Have we verified the user? */
311 /* Zero the mit_id field in the formatted packet structure. This
312 being zeroed means that no user was found. */
313 bzero(message->db.mit_id,sizeof(message->db.mit_id));
315 if (status == SUCCESS)
317 /* Get ready to make an Moira query */
318 q_name = "get_user_by_name";
319 q_argc = GUBN_ARGS; /* #defined in this routine */
320 q_argv[0] = message->first;
321 q_argv[1] = message->last;
323 /* Create queue to hold information */
327 status = mr_query(q_name,q_argc,q_argv,db_callproc,(char *)queue);
330 fprintf(stderr," %d returned by get_user_by_name\n",status);
333 if (status == MR_SUCCESS)
335 /* Traverse the list, freeing data as we go. If sq_get_data()
336 returns zero if there is no more data on the queue. */
337 while (sq_get_data(queue,&data))
340 /* parse_encrypted returns zero on success */
341 verified = (parse_encrypted(message,data) == SUCCESS);
346 /* Destroy the queue */
351 fprintf(stderr,"Returned from find_user\n");
352 fprintf(stderr," MIT ID: %s\n", message->db.mit_id);
353 fprintf(stderr," Registration status: %d\n",message->db.reg_status);
354 fprintf(stderr," uid: %s\n",message->db.uid);
355 fprintf(stderr," login: %s\n",message->db.login);
356 fprintf(stderr," Status from query: %d\n",status);
362 int verify_user(message,retval)
365 /* This routine determines whether a user is in the databse and returns
366 his state so that other routines can figure out whether he is the
367 correct state for various transactions. */
370 int status = SUCCESS; /* Return status */
372 /* Log that we are about to veryify user */
373 com_err(whoami, 0, "verifying user %s %s",message->first,message->last);
375 /* Figure out what user (if any) can be found based on the
376 encrypted information in the packet. (See the comment on
377 parse_encrypted().) */
379 status = find_user(message);
381 /* If Moira coudn't find the user */
382 if (status == MR_NO_MATCH)
383 status = UREG_USER_NOT_FOUND;
384 else if (status == MR_SUCCESS)
386 /* If the information sent over in the packet did not point to a
387 valid user, the mit_id field in the formatted packet structure
389 if (message->db.mit_id[0] == NULL)
390 status = UREG_USER_NOT_FOUND;
391 /* If the user was found but the registration has already started,
392 use this as the status */
395 switch (message->db.reg_status)
397 case US_NO_LOGIN_YET:
401 status = UREG_ALREADY_REGISTERED;
404 status = UREG_NO_PASSWD_YET;
407 status = UREG_DELETED;
410 status = UREG_NOT_ALLOWED;
413 status = UREG_ENROLLED;
415 case US_ENROLL_NOT_ALLOWED:
416 status = UREG_ENROLL_NOT_ALLOWED;
418 case US_HALF_ENROLLED:
419 status = UREG_HALF_ENROLLED;
422 status = UREG_MISC_ERROR;
423 critical_alert(FAIL_INST,"Bad user state %d for login %s.",
424 message->db.reg_status, message->db.login);
427 /* Set retval to the login name so that the client can use
428 it in the error message it will give the user. */
429 (void) strcpy(retval,message->db.login);
434 com_err(whoami, status, " returned from verify_user");
436 com_err(whoami, 0, "User verified");
443 unsigned int status = SUCCESS; /* Return status */
444 static char krbrealm[REALM_SZ]; /* kerberos realm name */
445 static char hostbuf[BUFSIZ], *host; /* local hostname in principal fmt */
446 static int inited = 0;
450 com_err(whoami, 0, "Entering ureg_kadm_init");
455 bzero(krbrealm, sizeof(krbrealm));
456 if (status = krb_get_lrealm(krbrealm, 1)) {
457 status += krb_err_base;
458 com_err(whoami, status, " fetching kerberos realm");
461 if (gethostname(hostbuf, sizeof(hostbuf)) < 0)
462 com_err(whoami, errno, "getting local hostname");
463 host = canonicalize_hostname(strsave(hostbuf));
464 for (p = host; *p && *p != '.'; p++)
470 /* Get keys for interacting with Kerberos admin server. */
471 /* principal, instance, realm, service, service instance, life, file */
472 if (status = krb_get_svc_in_tkt(MOIRA_SNAME, host, krbrealm, PWSERV_NAME,
473 KADM_SINST, 1, KEYFILE))
474 status += krb_err_base;
476 if (status != SUCCESS)
477 com_err(whoami, status, " while get admin tickets");
480 com_err(whoami, status, "Succeeded in getting admin tickets");
484 if (status == SUCCESS) {
485 if ((status = kadm_init_link(PWSERV_NAME, KADM_SINST, krbrealm)) !=
487 com_err(whoami, status, " while initializing kadmin connection");
494 int null_callproc(argc,argv,message)
498 /* This routine is a null callback that should be used for queries that
499 do not return tuples. If it ever gets called, something is wrong. */
501 critical_alert(FAIL_INST,"Something returned from an update query.");
506 * This routine reserves a principal in kerberos by setting up a
507 * principal with a random initial key.
509 int reserve_krb(login)
512 int status = SUCCESS;
515 u_long *lkey = (u_long *)key;
518 com_err(whoami, 0, "Entering reserve_krb");
521 if ((status = ureg_kadm_init()) == SUCCESS) {
522 bzero((char *)&new, sizeof(new));
523 SET_FIELD(KADM_DESKEY, new.fields);
524 SET_FIELD(KADM_NAME, new.fields);
526 (void) des_random_key(key);
527 new.key_low = htonl(lkey[0]);
528 new.key_high = htonl(lkey[1]);
529 strcpy(new.name, login);
531 com_err(whoami, 0, "Creating kerberos principal for %s", login);
532 status = kadm_add(&new);
533 if (status != KADM_SUCCESS)
534 com_err(whoami, status, " while reserving principal");
536 bzero((char *)&new, sizeof(new));
545 * This routine reserves a principal in kerberos by setting up a
546 * principal with a random initial key.
548 int setpass_krb(login, password)
552 int status = SUCCESS;
555 u_long *lkey = (u_long *)key;
557 if ((status = ureg_kadm_init()) == SUCCESS) {
558 bzero((char *)&new, sizeof(new));
559 SET_FIELD(KADM_DESKEY, new.fields);
560 SET_FIELD(KADM_NAME, new.fields);
562 (void) des_string_to_key(password, key);
563 new.key_low = htonl(lkey[0]);
564 new.key_high = htonl(lkey[1]);
565 strcpy(new.name, login);
567 com_err(whoami, 0, "Setting password for %s", login);
568 /* First arguement is not used if user has modify privileges */
569 if ((status = kadm_mod(&new, &new)) != KADM_SUCCESS) {
570 if (status == KADM_NOENTRY) {
572 "kerberos principal doesn't exist; creating");
573 if ((status = kadm_add(&new)) != KADM_SUCCESS)
574 com_err(whoami, status,
575 " while creating kerberos principal");
578 com_err(whoami, status, " while setting password");
586 int reserve_user(message,retval)
590 int q_argc; /* Number of arguments to query */
591 char *q_argv[3]; /* Arguments to Moira query */
592 char *q_name; /* Name of Moira query */
593 int status = SUCCESS; /* General purpose error status */
594 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
595 char *login; /* The login name the user wants */
596 register int i; /* A counter */
598 /* Log that we are about to reserve a user. */
599 com_err(whoami, 0, "reserving user %s %s",
600 message->first, message->last);
602 /* Check to make sure that we can verify this user. */
603 if ((status = verify_user(message,retval)) == SUCCESS)
605 /* Get the requested login name from leftover packet information. */
606 login = message->leftover;
608 /* Check the login name for validity. The login name is currently
609 is allowed to contain lowercase letters in any position and
610 and numbers and underscore characters in any position but the
612 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
613 status = UREG_INVALID_UNAME;
615 if (status == SUCCESS)
616 if ((login[0] == '_') || isdigit(login[0]))
617 status = UREG_INVALID_UNAME;
619 if (status == SUCCESS)
621 for (i = 0; i < strlen(login); i++)
622 if (!islower(login[i]) && !isdigit(login[i]) &&
625 status = UREG_INVALID_UNAME;
629 if (status == SUCCESS)
631 /* Now that we have a valid user with a valid login... */
633 /* First, try to reserve the user in Moira. */
634 (void) sprintf(fstype_buf,"%d",MR_FS_STUDENT);
635 q_name = "register_user";
636 q_argv[0] = message->db.uid;
638 q_argv[2] = fstype_buf;
640 status = mr_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
647 status = UREG_LOGIN_USED;
650 status = UREG_MISC_ERROR;
653 critical_alert(FAIL_INST,"%s returned from register_user.",
654 error_message(status));
655 status = UREG_MISC_ERROR;
660 if (status == SUCCESS)
663 * Moira login was successfully created; try to reserve kerberos
666 * If this routine fails, store the login in the retval so
667 * that it can be used in the client-side error message.
669 if ((status = reserve_krb(login)) != SUCCESS)
670 (void) strcpy(retval, login);
674 com_err(whoami, status, " returned from reserve_user");
676 com_err(whoami, 0, "User reserved");
682 int set_final_status(message)
684 /* This routine updates a user's registration status to fully
688 char *q_name; /* Name of Moira query */
689 int q_argc; /* Number of arguments for Moira query */
690 char *q_argv[2]; /* Arguments to get user by uid */
691 char state[7]; /* Can hold a 16 bit integer */
692 int status; /* Error status */
694 if (message->request == UREG_SET_PASSWORD)
695 (void) sprintf(state,"%d",US_REGISTERED);
696 else if (message->db.reg_status == US_NO_LOGIN_YET)
697 (void) sprintf(state,"%d",US_ENROLLED);
699 (void) sprintf(state,"%d",US_ENROLL_NOT_ALLOWED);
701 login = message->db.login;
702 com_err(whoami, 0, "Setting final status for %s to %s", login, state);
704 q_name = "update_user_status";
708 if ((status = mr_query(q_name, q_argc, q_argv, null_callproc,
709 (char *)0)) != MR_SUCCESS) {
710 if (status == MR_DEADLOCK)
711 status = UREG_MISC_ERROR;
713 critical_alert(FAIL_INST,"%s returned from update_user_status.",
714 error_message(status));
717 com_err(whoami, status, " returned from set_final_status");
719 com_err(whoami, 0, "Final status set");
724 int set_password(message,retval)
727 /* This routine is used to set the initial password for the new user. */
729 int status = SUCCESS; /* Return status */
730 char *passwd; /* User's password */
732 com_err(whoami, 0, "setting password %s %s",
733 message->first, message->last);
735 status = verify_user(message,retval);
737 /* Don't set the password unless the registration status of the user
738 is that he exists and has no password. */
739 if (status == SUCCESS)
740 status = UREG_NO_LOGIN_YET;
741 if (((int)message->request == UREG_SET_PASSWORD &&
742 status == UREG_NO_PASSWD_YET) ||
743 ((int)message->request == UREG_GET_KRB &&
744 status == UREG_HALF_ENROLLED))
746 /* User is in proper state for this transaction. */
748 passwd = message->leftover;
751 if ((status = setpass_krb(message->db.login, passwd)) != SUCCESS)
752 /* If failure, allow login name to be used in client
754 (void) strcpy(retval,message->db.login);
756 /* Otherwise, mark user as finished. */
757 status = set_final_status(message);
761 com_err(whoami, status, " returned from set_passwd");
763 com_err(whoami, 0, "Password set");
769 int getuserinfo(argc, argv, qargv)
774 int status = SUCCESS;
777 critical_alert(FAIL_INST,
778 "Wrong number of args returned from get_user_by_uid");
781 qargv[U_NAME] = strsave(argv[U_NAME]);
782 qargv[U_UID+1] = strsave(argv[U_UID]);
783 qargv[U_SHELL+1] = strsave(argv[U_SHELL]);
784 qargv[U_LAST+1] = strsave(argv[U_LAST]);
785 qargv[U_FIRST+1] = strsave(argv[U_FIRST]);
786 qargv[U_MIDDLE+1] = strsave(argv[U_MIDDLE]);
787 qargv[U_STATE+1] = strsave(argv[U_STATE]);
788 qargv[U_MITID+1] = strsave(argv[U_MITID]);
789 qargv[U_CLASS+1] = strsave(argv[U_CLASS]);
790 qargv[U_MODTIME+1] = NULL;
796 int set_identity(message,retval)
800 int q_argc; /* Number of arguments to query */
801 char *q_argv[U_END]; /* Arguments to Moira query */
802 char *q_name; /* Name of Moira query */
803 int status = SUCCESS; /* General purpose error status */
804 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
805 char *login; /* The login name the user wants */
806 register int i; /* A counter */
808 /* Log that we are about to reserve a user. */
809 com_err(whoami, 0, "setting identity %s %s",
810 message->first, message->last);
812 /* Check to make sure that we can verify this user. */
813 status = verify_user(message,retval);
814 if (status == SUCCESS || status == UREG_NOT_ALLOWED)
817 /* Get the requested login name from leftover packet information. */
818 login = message->leftover;
820 /* Check the login name for validity. The login name is currently
821 is allowed to contain lowercase letters in any position and
822 and numbers and underscore characters in any position but the
824 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
825 status = UREG_INVALID_UNAME;
827 if (status == SUCCESS)
828 if ((login[0] == '_') || isdigit(login[0]))
829 status = UREG_INVALID_UNAME;
830 if (status == SUCCESS)
832 for (i = 0; i < strlen(login); i++)
833 if (!islower(login[i]) && !isdigit(login[i]) &&
836 status = UREG_INVALID_UNAME;
840 if (status == SUCCESS)
842 /* Now that we have a valid user with a valid login... */
844 q_argv[0] = message->db.uid;
845 status = mr_query("get_user_by_uid", 1, q_argv, getuserinfo, q_argv);
846 if (status != SUCCESS) {
847 com_err(whoami, status, " while getting user info");
850 q_argv[U_NAME+1] = login;
851 q_argv[U_STATE+1] = "7";
852 status = mr_query("update_user", U_MODTIME+1, q_argv,
853 null_callproc, NULL);
860 status = UREG_LOGIN_USED;
863 status = UREG_MISC_ERROR;
866 critical_alert(FAIL_INST,"%s returned from update_user.",
867 error_message(status));
868 status = UREG_MISC_ERROR;
872 if (status == SUCCESS)
874 /* Moira login was successfully created; try to reserve kerberos
876 /* If this routine fails, store the login in the retval so
877 that it can be used in the client-side error message. */
878 if ((status = reserve_krb(login)) != SUCCESS)
879 (void) strcpy(retval, login);
883 com_err(whoami, status, " returned from set_identity");
885 com_err(whoami, 0, "Identity set");
891 void reg_com_err_hook(whoami, code, fmt, pvar)
898 fputs(whoami, stderr);
902 fputs(error_message(code), stderr);
905 _doprnt(fmt, pvar, stderr);