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 libsmsglue which bypasses
15 * the network protocol.
19 static char *rcsid_reg_svr_c = "$Header$";
22 #include <mit-copyright.h>
26 #include <sys/types.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 = sms_connect(SMS_SERVER)) != SMS_SUCCESS)
78 com_err(whoami, status, " on sms_connect");
82 /* Authorize, telling the server who you are */
83 if ((status = sms_auth(whoami)) != SMS_SUCCESS)
85 com_err(whoami, status, " on sms_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 C_Block key; /* The key for DES en/decryption */
152 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 string_to_key(data->mit_id, key);
173 /* Get schedule from key */
174 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 pcbc_encrypt(message->encrypted,decrypt, decrypt_len, sched, key, DECRYPT);
180 /* Extract the plain text and encrypted ID fields from the decrypted
181 packet information. */
182 /* Since the decrypted information starts with the plain-text ID
183 followed by a null, if the decryption worked, this will only
184 copy the plain text part of the decrypted information. It is
185 important that strncpy be used because if we are not using the
186 correct key, there is no guarantee that a null will occur
187 anywhere in the string. */
188 (void) strncpy(idnumber,decrypt,(int)decrypt_len);
189 /* Point temp to the end of the plain text ID number. */
190 temp = decrypt + strlen(idnumber) + 1;
191 /* Find out how much more packet there is. */
192 len = message->encrypted_len - (temp - decrypt);
193 /* Copy the next CRYPT_LEN bytes of the decrypted information into
194 hashid if there are CRYPT_LEN more bytes to copy. There will be
195 if we have the right key. */
196 (void) strncpy(hashid, temp, min(len, CRYPT_LEN));
197 /* Point temp to the end of the encrypted ID field */
198 temp += strlen(hashid) + 1;
199 /* Find out how much more room there is. */
200 len = message->encrypted_len - (temp - decrypt);
202 /* Now compare encrypted ID's don't match. */
203 if (strcmp(hashid, data->mit_id)) status = FAILURE;
204 if (status == SUCCESS)
206 EncryptID(recrypt, idnumber, message->first, message->last);
207 /* Now compare encrypted plain text to ID from database. */
208 if (strcmp(recrypt, data->mit_id)) status = FAILURE;
211 if (status == SUCCESS)
213 /* We made it. Now we can finish initializing message. */
214 /* Point leftover to whatever is left over! */
215 message->leftover = temp;
216 message->leftover_len = len;
217 /* Since we know we have the right user, fill in the information
218 from the Moira database. */
219 message->db.reg_status = data->reg_status;
220 (void) strncpy(message->db.uid,data->uid, sizeof(message->db.uid));
221 (void) strncpy(message->db.mit_id,data->mit_id,
222 sizeof(message->db.mit_id));
223 (void) strncpy(message->db.login,data->login, sizeof(message->db.login));
228 com_err(whoami, status, " in parse_encrypted");
230 com_err(whoami, status, "parse_encrypted succeeded");
236 int db_callproc(argc,argv,queue)
237 int argc; /* Number of arguments returned by Moira */
238 char *argv[]; /* Arguments returned by Moira */
239 struct save_queue *queue; /* Queue to save information in */
240 /* This function is called by sms_query after each tuple found. It is
241 used by find_user to cache information about each user found. */
243 struct db_data *data; /* Structure to store the information in */
244 int status = SUCCESS; /* Error status */
247 com_err(whoami, 0, "Entering db_callproc.");
254 "Wrong number of arguments returned from get_user_by_name.");
259 /* extract the needed information from the results of the Moira query */
260 data = (struct db_data *)malloc(sizeof(struct db_data));
261 data->reg_status = atoi(argv[U_STATE]);
262 (void) strncpy(data->login,argv[U_NAME],sizeof(data->login));
263 (void) strncpy(data->mit_id,argv[U_MITID],sizeof(data->mit_id));
264 (void) strncpy(data->uid,argv[U_UID],sizeof(data->uid));
266 fprintf(stderr,"Found in database:\n");
267 fprintf(stderr," Registration status: %d\n",data->reg_status);
268 fprintf(stderr," login: %s\n",data->login);
269 fprintf(stderr," MIT ID: %s\n",data->mit_id);
270 fprintf(stderr," uid: %s\n",data->uid);
272 sq_save_data(queue,data);
278 int find_user(message)
279 struct msg *message; /* Formatted packet structure */
280 /* This routine verifies that a user is allowed to register by finding
281 him/her in the Moira database. It returns the status of the Moira
282 query that it calls. */
284 #define GUBN_ARGS 2 /* Arguements needed by get_user_by_name */
285 char *q_name; /* Name of query */
286 int q_argc; /* Number of arguments for query */
287 char *q_argv[GUBN_ARGS]; /* Arguments to query */
288 int status = SUCCESS; /* Query return status */
290 struct save_queue *queue; /* Queue to hold Moira data */
291 struct db_data *data; /* Structure for data for one tuple */
292 short verified = FALSE; /* Have we verified the user? */
294 /* Zero the mit_id field in the formatted packet structure. This
295 being zeroed means that no user was found. */
296 bzero(message->db.mit_id,sizeof(message->db.mit_id));
298 if (status == SUCCESS)
300 /* Get ready to make an Moira query */
301 q_name = "get_user_by_name";
302 q_argc = GUBN_ARGS; /* #defined in this routine */
303 q_argv[0] = message->first;
304 q_argv[1] = message->last;
306 /* Create queue to hold information */
310 status = sms_query(q_name,q_argc,q_argv,db_callproc,(char *)queue);
313 fprintf(stderr," %d returned by get_user_by_name\n",status);
316 if (status == SMS_SUCCESS)
318 /* Traverse the list, freeing data as we go. If sq_get_data()
319 returns zero if there is no more data on the queue. */
320 while (sq_get_data(queue,&data))
323 /* parse_encrypted returns zero on success */
324 verified = (parse_encrypted(message,data) == SUCCESS);
329 /* Destroy the queue */
334 fprintf(stderr,"Returned from find_user\n");
335 fprintf(stderr," MIT ID: %s\n", message->db.mit_id);
336 fprintf(stderr," Registration status: %d\n",message->db.reg_status);
337 fprintf(stderr," uid: %s\n",message->db.uid);
338 fprintf(stderr," login: %s\n",message->db.login);
339 fprintf(stderr," Status from query: %d\n",status);
345 int verify_user(message,retval)
348 /* This routine determines whether a user is in the databse and returns
349 his state so that other routines can figure out whether he is the
350 correct state for various transactions. */
353 int status = SUCCESS; /* Return status */
355 /* Log that we are about to veryify user */
356 com_err(whoami, 0, "verifying user %s %s",message->first,message->last);
358 /* Figure out what user (if any) can be found based on the
359 encrypted information in the packet. (See the comment on
360 parse_encrypted().) */
362 status = find_user(message);
364 /* If Moira coudn't find the user */
365 if (status == SMS_NO_MATCH)
366 status = UREG_USER_NOT_FOUND;
367 else if (status == SMS_SUCCESS)
369 /* If the information sent over in the packet did not point to a
370 valid user, the mit_id field in the formatted packet structure
372 if (message->db.mit_id[0] == NULL)
373 status = UREG_USER_NOT_FOUND;
374 /* If the user was found but the registration has already started,
375 use this as the status */
378 switch (message->db.reg_status)
380 case US_NO_LOGIN_YET:
384 status = UREG_ALREADY_REGISTERED;
387 status = UREG_NO_PASSWD_YET;
390 status = UREG_DELETED;
393 status = UREG_NOT_ALLOWED;
396 status = UREG_ENROLLED;
398 case US_ENROLL_NOT_ALLOWED:
399 status = UREG_ENROLL_NOT_ALLOWED;
402 status = UREG_MISC_ERROR;
403 critical_alert(FAIL_INST,"Bad user state %d for login %s.",
404 message->db.reg_status, message->db.login);
407 /* Set retval to the login name so that the client can use
408 it in the error message it will give the user. */
409 (void) strcpy(retval,message->db.login);
414 com_err(whoami, status, " returned from verify_user");
416 com_err(whoami, 0, "User verified");
423 unsigned int status = SUCCESS; /* Return status */
424 static char krbrealm[REALM_SZ]; /* kerberos realm name */
425 static int inited = 0;
428 com_err(whoami, 0, "Entering ureg_kadm_init");
433 bzero(krbrealm, sizeof(krbrealm));
434 if (status = krb_get_lrealm(krbrealm, 1)) {
435 status += krb_err_base;
436 com_err(whoami, status, " fetching kerberos realm");
441 /* Get keys for interacting with Kerberos admin server. */
442 /* principal, instance, realm, service, service instance, life, file */
443 if (status = krb_get_svc_in_tkt("register", "sms", krbrealm, PWSERV_NAME,
444 KADM_SINST, 1, KEYFILE))
445 status += krb_err_base;
447 if (status != SUCCESS)
448 com_err(whoami, status, " while get admin tickets");
451 com_err(whoami, status, "Succeeded in getting admin tickets");
455 if (status == SUCCESS) {
456 if ((status = kadm_init_link(PWSERV_NAME, KADM_SINST, krbrealm)) !=
458 com_err(whoami, status, " while initializing kadmin connection");
465 int null_callproc(argc,argv,message)
469 /* This routine is a null callback that should be used for queries that
470 do not return tuples. If it ever gets called, something is wrong. */
472 critical_alert(FAIL_INST,"Something returned from an update query.");
477 * This routine reserves a principal in kerberos by setting up a
478 * principal with a random initial key.
480 int reserve_krb(login)
483 int status = SUCCESS;
486 u_long *lkey = (u_long *)key;
489 com_err(whoami, 0, "Entering reserve_krb");
492 if ((status = ureg_kadm_init()) == SUCCESS) {
493 bzero((char *)&new, sizeof(new));
494 SET_FIELD(KADM_DESKEY, new.fields);
495 SET_FIELD(KADM_NAME, new.fields);
497 (void) des_random_key(key);
498 new.key_low = htonl(lkey[0]);
499 new.key_high = htonl(lkey[1]);
500 strcpy(new.name, login);
502 com_err(whoami, 0, "Creating kerberos principal for %s", login);
503 status = kadm_add(&new);
504 if (status != KADM_SUCCESS)
505 com_err(whoami, status, " while reserving principal");
507 bzero((char *)&new, sizeof(new));
516 * This routine reserves a principal in kerberos by setting up a
517 * principal with a random initial key.
519 int setpass_krb(login, password)
523 int status = SUCCESS;
526 u_long *lkey = (u_long *)key;
528 if ((status = ureg_kadm_init()) == SUCCESS) {
529 bzero((char *)&new, sizeof(new));
530 SET_FIELD(KADM_DESKEY, new.fields);
531 SET_FIELD(KADM_NAME, new.fields);
533 (void) des_string_to_key(password, key);
534 new.key_low = htonl(lkey[0]);
535 new.key_high = htonl(lkey[1]);
536 strcpy(new.name, login);
538 com_err(whoami, 0, "Setting password for %s", login);
539 /* First arguement is not used if user has modify privileges */
540 if ((status = kadm_mod(&new, &new)) != KADM_SUCCESS) {
541 if (status == KADM_NOENTRY) {
543 "kerberos principal doesn't exist; creating");
544 if ((status = kadm_add(&new)) != KADM_SUCCESS)
545 com_err(whoami, status,
546 " while creating kerberos principal");
549 com_err(whoami, status, " while setting password");
557 int reserve_user(message,retval)
561 int q_argc; /* Number of arguments to query */
562 char *q_argv[3]; /* Arguments to Moira query */
563 char *q_name; /* Name of Moira query */
564 int status = SUCCESS; /* General purpose error status */
565 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
566 char *login; /* The login name the user wants */
567 register int i; /* A counter */
569 /* Log that we are about to reserve a user. */
570 com_err(whoami, 0, "reserving user %s %s",
571 message->first, message->last);
573 /* Check to make sure that we can verify this user. */
574 if ((status = verify_user(message,retval)) == SUCCESS)
576 /* Get the requested login name from leftover packet information. */
577 login = message->leftover;
579 /* Check the login name for validity. The login name is currently
580 is allowed to contain lowercase letters in any position and
581 and numbers and underscore characters in any position but the
583 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
584 status = UREG_INVALID_UNAME;
586 if (status == SUCCESS)
587 if ((login[0] == '_') || isdigit(login[0]))
588 status = UREG_INVALID_UNAME;
590 if (status == SUCCESS)
592 for (i = 0; i < strlen(login); i++)
593 if (!islower(login[i]) && !isdigit(login[i]) &&
596 status = UREG_INVALID_UNAME;
600 if (status == SUCCESS)
602 /* Now that we have a valid user with a valid login... */
604 /* First, try to reserve the user in Moira. */
605 (void) sprintf(fstype_buf,"%d",SMS_FS_STUDENT);
606 q_name = "register_user";
607 q_argv[0] = message->db.uid;
609 q_argv[2] = fstype_buf;
611 status = sms_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
618 status = UREG_LOGIN_USED;
621 status = UREG_MISC_ERROR;
624 critical_alert(FAIL_INST,"%s returned from register_user.",
625 error_message(status));
626 status = UREG_MISC_ERROR;
631 if (status == SUCCESS)
634 * Moira login was successfully created; try to reserve kerberos
637 * If this routine fails, store the login in the retval so
638 * that it can be used in the client-side error message.
640 if ((status = reserve_krb(login)) != SUCCESS)
641 (void) strcpy(retval, login);
645 com_err(whoami, status, " returned from reserve_user");
647 com_err(whoami, 0, "User reserved");
653 int set_final_status(message)
655 /* This routine updates a user's registration status to fully
659 char *q_name; /* Name of Moira query */
660 int q_argc; /* Number of arguments for Moira query */
661 char *q_argv[2]; /* Arguments to get user by uid */
662 char state[7]; /* Can hold a 16 bit integer */
663 int status; /* Error status */
665 if (message->request == UREG_SET_PASSWORD)
666 (void) sprintf(state,"%d",US_REGISTERED);
667 else if (message->db.reg_status == US_NO_LOGIN_YET)
668 (void) sprintf(state,"%d",US_ENROLLED);
670 (void) sprintf(state,"%d",US_ENROLL_NOT_ALLOWED);
672 login = message->db.login;
673 com_err(whoami, 0, "Setting final status for %s to %s", login, state);
675 q_name = "update_user_status";
679 if ((status = sms_query(q_name, q_argc, q_argv, null_callproc,
680 (char *)0)) != SMS_SUCCESS) {
681 if (status == SMS_DEADLOCK)
682 status = UREG_MISC_ERROR;
684 critical_alert(FAIL_INST,"%s returned from update_user_status.",
685 error_message(status));
688 com_err(whoami, status, " returned from set_final_status");
690 com_err(whoami, 0, "Final status set");
695 int set_password(message,retval)
698 /* This routine is used to set the initial password for the new user. */
700 int status = SUCCESS; /* Return status */
701 char *passwd; /* User's password */
703 com_err(whoami, 0, "setting password %s %s",
704 message->first, message->last);
706 status = verify_user(message,retval);
708 /* Don't set the password unless the registration status of the user
709 is that he exists and has no password. */
710 if (status == SUCCESS)
711 status = UREG_NO_LOGIN_YET;
712 if (status == UREG_NO_PASSWD_YET)
714 /* User is in proper state for this transaction. */
716 passwd = message->leftover;
719 if ((status = setpass_krb(message->db.login, passwd)) != SUCCESS)
720 /* If failure, allow login name to be used in client
722 (void) strcpy(retval,message->db.login);
724 /* Otherwise, mark user as finished. */
725 status = set_final_status(message);
729 com_err(whoami, status, " returned from set_passwd");
731 com_err(whoami, 0, "Password set");
737 int getuserinfo(argc, argv, qargv)
742 int status = SUCCESS;
745 critical_alert(FAIL_INST,
746 "Wrong number of args returned from get_user_by_uid");
749 qargv[U_NAME] = strsave(argv[U_NAME]);
750 qargv[U_UID+1] = strsave(argv[U_UID]);
751 qargv[U_SHELL+1] = strsave(argv[U_SHELL]);
752 qargv[U_LAST+1] = strsave(argv[U_LAST]);
753 qargv[U_FIRST+1] = strsave(argv[U_FIRST]);
754 qargv[U_MIDDLE+1] = strsave(argv[U_MIDDLE]);
755 qargv[U_STATE+1] = strsave(argv[U_STATE]);
756 qargv[U_MITID+1] = strsave(argv[U_MITID]);
757 qargv[U_CLASS+1] = strsave(argv[U_CLASS]);
758 qargv[U_MODTIME+1] = NULL;
764 int set_identity(message,retval)
768 int q_argc; /* Number of arguments to query */
769 char *q_argv[U_END]; /* Arguments to Moira query */
770 char *q_name; /* Name of Moira query */
771 int status = SUCCESS; /* General purpose error status */
772 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
773 char *login; /* The login name the user wants */
774 register int i; /* A counter */
776 /* Log that we are about to reserve a user. */
777 com_err(whoami, 0, "setting identity %s %s",
778 message->first, message->last);
780 /* Check to make sure that we can verify this user. */
781 status = verify_user(message,retval);
782 if (status == SUCCESS || status == UREG_NOT_ALLOWED)
785 /* Get the requested login name from leftover packet information. */
786 login = message->leftover;
788 /* Check the login name for validity. The login name is currently
789 is allowed to contain lowercase letters in any position and
790 and numbers and underscore characters in any position but the
792 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
793 status = UREG_INVALID_UNAME;
795 if (status == SUCCESS)
796 if ((login[0] == '_') || isdigit(login[0]))
797 status = UREG_INVALID_UNAME;
798 if (status == SUCCESS)
800 for (i = 0; i < strlen(login); i++)
801 if (!islower(login[i]) && !isdigit(login[i]) &&
804 status = UREG_INVALID_UNAME;
808 if (status == SUCCESS)
810 /* Now that we have a valid user with a valid login... */
812 q_argv[0] = message->db.uid;
813 status = sms_query("get_user_by_uid", 1, q_argv, getuserinfo, q_argv);
814 if (status != SUCCESS) {
815 com_err(whoami, status, " while getting user info");
818 q_argv[U_NAME+1] = login;
819 status = sms_query("update_user", U_MODTIME+1, q_argv,
820 null_callproc, NULL);
827 status = UREG_LOGIN_USED;
830 status = UREG_MISC_ERROR;
833 critical_alert(FAIL_INST,"%s returned from update_user.",
834 error_message(status));
835 status = UREG_MISC_ERROR;
839 if (status == SUCCESS)
841 /* Moira login was successfully created; try to reserve kerberos
843 /* If this routine fails, store the login in the retval so
844 that it can be used in the client-side error message. */
845 if ((status = reserve_krb(login)) != SUCCESS)
846 (void) strcpy(retval, login);
850 com_err(whoami, status, " returned from set_identity");
852 com_err(whoami, 0, "Identity set");
858 void reg_com_err_hook(whoami, code, fmt, pvar)
865 fputs(whoami, stderr);
869 fputs(error_message(code), stderr);
872 _doprnt(fmt, pvar, stderr);