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 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 /* Point temp to the end of the plain text ID number. */
191 temp = decrypt + strlen(idnumber) + 1;
192 /* Find out how much more packet there is. */
193 len = message->encrypted_len - (temp - decrypt);
194 /* Copy the next CRYPT_LEN bytes of the decrypted information into
195 hashid if there are CRYPT_LEN more bytes to copy. There will be
196 if we have the right key. */
197 (void) strncpy(hashid, temp, min(len, CRYPT_LEN));
198 /* Point temp to the end of the encrypted ID field */
199 temp += strlen(hashid) + 1;
200 /* Find out how much more room there is. */
201 len = message->encrypted_len - (temp - decrypt);
203 /* Now compare encrypted ID's don't match. */
204 if (strcmp(hashid, data->mit_id)) status = FAILURE;
205 if (status == SUCCESS)
207 EncryptID(recrypt, idnumber, message->first, message->last);
208 /* Now compare encrypted plain text to ID from database. */
209 if (strcmp(recrypt, data->mit_id)) status = FAILURE;
212 if (status == SUCCESS)
214 /* We made it. Now we can finish initializing message. */
215 /* Point leftover to whatever is left over! */
216 message->leftover = temp;
217 message->leftover_len = len;
218 /* Since we know we have the right user, fill in the information
219 from the Moira database. */
220 message->db.reg_status = data->reg_status;
221 (void) strncpy(message->db.uid,data->uid, sizeof(message->db.uid));
222 (void) strncpy(message->db.mit_id,data->mit_id,
223 sizeof(message->db.mit_id));
224 (void) strncpy(message->db.login,data->login, sizeof(message->db.login));
229 com_err(whoami, status, " in parse_encrypted");
231 com_err(whoami, status, "parse_encrypted succeeded");
237 int db_callproc(argc,argv,queue)
238 int argc; /* Number of arguments returned by Moira */
239 char *argv[]; /* Arguments returned by Moira */
240 struct save_queue *queue; /* Queue to save information in */
241 /* This function is called by sms_query after each tuple found. It is
242 used by find_user to cache information about each user found. */
244 struct db_data *data; /* Structure to store the information in */
245 int status = SUCCESS; /* Error status */
248 com_err(whoami, 0, "Entering db_callproc.");
255 "Wrong number of arguments returned from get_user_by_name.");
260 /* extract the needed information from the results of the Moira query */
261 data = (struct db_data *)malloc(sizeof(struct db_data));
262 data->reg_status = atoi(argv[U_STATE]);
263 (void) strncpy(data->login,argv[U_NAME],sizeof(data->login));
264 (void) strncpy(data->mit_id,argv[U_MITID],sizeof(data->mit_id));
265 (void) strncpy(data->uid,argv[U_UID],sizeof(data->uid));
267 fprintf(stderr,"Found in database:\n");
268 fprintf(stderr," Registration status: %d\n",data->reg_status);
269 fprintf(stderr," login: %s\n",data->login);
270 fprintf(stderr," MIT ID: %s\n",data->mit_id);
271 fprintf(stderr," uid: %s\n",data->uid);
273 sq_save_data(queue,data);
279 int find_user(message)
280 struct msg *message; /* Formatted packet structure */
281 /* This routine verifies that a user is allowed to register by finding
282 him/her in the Moira database. It returns the status of the Moira
283 query that it calls. */
285 #define GUBN_ARGS 2 /* Arguements needed by get_user_by_name */
286 char *q_name; /* Name of query */
287 int q_argc; /* Number of arguments for query */
288 char *q_argv[GUBN_ARGS]; /* Arguments to query */
289 int status = SUCCESS; /* Query return status */
291 struct save_queue *queue; /* Queue to hold Moira data */
292 struct db_data *data; /* Structure for data for one tuple */
293 short verified = FALSE; /* Have we verified the user? */
295 /* Zero the mit_id field in the formatted packet structure. This
296 being zeroed means that no user was found. */
297 bzero(message->db.mit_id,sizeof(message->db.mit_id));
299 if (status == SUCCESS)
301 /* Get ready to make an Moira query */
302 q_name = "get_user_by_name";
303 q_argc = GUBN_ARGS; /* #defined in this routine */
304 q_argv[0] = message->first;
305 q_argv[1] = message->last;
307 /* Create queue to hold information */
311 status = sms_query(q_name,q_argc,q_argv,db_callproc,(char *)queue);
314 fprintf(stderr," %d returned by get_user_by_name\n",status);
317 if (status == SMS_SUCCESS)
319 /* Traverse the list, freeing data as we go. If sq_get_data()
320 returns zero if there is no more data on the queue. */
321 while (sq_get_data(queue,&data))
324 /* parse_encrypted returns zero on success */
325 verified = (parse_encrypted(message,data) == SUCCESS);
330 /* Destroy the queue */
335 fprintf(stderr,"Returned from find_user\n");
336 fprintf(stderr," MIT ID: %s\n", message->db.mit_id);
337 fprintf(stderr," Registration status: %d\n",message->db.reg_status);
338 fprintf(stderr," uid: %s\n",message->db.uid);
339 fprintf(stderr," login: %s\n",message->db.login);
340 fprintf(stderr," Status from query: %d\n",status);
346 int verify_user(message,retval)
349 /* This routine determines whether a user is in the databse and returns
350 his state so that other routines can figure out whether he is the
351 correct state for various transactions. */
354 int status = SUCCESS; /* Return status */
356 /* Log that we are about to veryify user */
357 com_err(whoami, 0, "verifying user %s %s",message->first,message->last);
359 /* Figure out what user (if any) can be found based on the
360 encrypted information in the packet. (See the comment on
361 parse_encrypted().) */
363 status = find_user(message);
365 /* If Moira coudn't find the user */
366 if (status == SMS_NO_MATCH)
367 status = UREG_USER_NOT_FOUND;
368 else if (status == SMS_SUCCESS)
370 /* If the information sent over in the packet did not point to a
371 valid user, the mit_id field in the formatted packet structure
373 if (message->db.mit_id[0] == NULL)
374 status = UREG_USER_NOT_FOUND;
375 /* If the user was found but the registration has already started,
376 use this as the status */
379 switch (message->db.reg_status)
381 case US_NO_LOGIN_YET:
385 status = UREG_ALREADY_REGISTERED;
388 status = UREG_NO_PASSWD_YET;
391 status = UREG_DELETED;
394 status = UREG_NOT_ALLOWED;
397 status = UREG_ENROLLED;
399 case US_ENROLL_NOT_ALLOWED:
400 status = UREG_ENROLL_NOT_ALLOWED;
402 case US_HALF_ENROLLED:
403 status = UREG_HALF_ENROLLED;
406 status = UREG_MISC_ERROR;
407 critical_alert(FAIL_INST,"Bad user state %d for login %s.",
408 message->db.reg_status, message->db.login);
411 /* Set retval to the login name so that the client can use
412 it in the error message it will give the user. */
413 (void) strcpy(retval,message->db.login);
418 com_err(whoami, status, " returned from verify_user");
420 com_err(whoami, 0, "User verified");
427 unsigned int status = SUCCESS; /* Return status */
428 static char krbrealm[REALM_SZ]; /* kerberos realm name */
429 static char hostbuf[BUFSIZ], *host; /* local hostname in principal fmt */
430 static int inited = 0;
434 com_err(whoami, 0, "Entering ureg_kadm_init");
439 bzero(krbrealm, sizeof(krbrealm));
440 if (status = krb_get_lrealm(krbrealm, 1)) {
441 status += krb_err_base;
442 com_err(whoami, status, " fetching kerberos realm");
445 if (gethostname(hostbuf, sizeof(hostbuf)) < 0)
446 com_err(whoami, errno, "getting local hostname");
447 host = canonicalize_hostname(strsave(hostbuf));
448 for (p = host; *p && *p != '.'; p++)
454 /* Get keys for interacting with Kerberos admin server. */
455 /* principal, instance, realm, service, service instance, life, file */
456 if (status = krb_get_svc_in_tkt(MOIRA_SNAME, host, krbrealm, PWSERV_NAME,
457 KADM_SINST, 1, KEYFILE))
458 status += krb_err_base;
460 if (status != SUCCESS)
461 com_err(whoami, status, " while get admin tickets");
464 com_err(whoami, status, "Succeeded in getting admin tickets");
468 if (status == SUCCESS) {
469 if ((status = kadm_init_link(PWSERV_NAME, KADM_SINST, krbrealm)) !=
471 com_err(whoami, status, " while initializing kadmin connection");
478 int null_callproc(argc,argv,message)
482 /* This routine is a null callback that should be used for queries that
483 do not return tuples. If it ever gets called, something is wrong. */
485 critical_alert(FAIL_INST,"Something returned from an update query.");
490 * This routine reserves a principal in kerberos by setting up a
491 * principal with a random initial key.
493 int reserve_krb(login)
496 int status = SUCCESS;
499 u_long *lkey = (u_long *)key;
502 com_err(whoami, 0, "Entering reserve_krb");
505 if ((status = ureg_kadm_init()) == SUCCESS) {
506 bzero((char *)&new, sizeof(new));
507 SET_FIELD(KADM_DESKEY, new.fields);
508 SET_FIELD(KADM_NAME, new.fields);
510 (void) des_random_key(key);
511 new.key_low = htonl(lkey[0]);
512 new.key_high = htonl(lkey[1]);
513 strcpy(new.name, login);
515 com_err(whoami, 0, "Creating kerberos principal for %s", login);
516 status = kadm_add(&new);
517 if (status != KADM_SUCCESS)
518 com_err(whoami, status, " while reserving principal");
520 bzero((char *)&new, sizeof(new));
529 * This routine reserves a principal in kerberos by setting up a
530 * principal with a random initial key.
532 int setpass_krb(login, password)
536 int status = SUCCESS;
539 u_long *lkey = (u_long *)key;
541 if ((status = ureg_kadm_init()) == SUCCESS) {
542 bzero((char *)&new, sizeof(new));
543 SET_FIELD(KADM_DESKEY, new.fields);
544 SET_FIELD(KADM_NAME, new.fields);
546 (void) des_string_to_key(password, key);
547 new.key_low = htonl(lkey[0]);
548 new.key_high = htonl(lkey[1]);
549 strcpy(new.name, login);
551 com_err(whoami, 0, "Setting password for %s", login);
552 /* First arguement is not used if user has modify privileges */
553 if ((status = kadm_mod(&new, &new)) != KADM_SUCCESS) {
554 if (status == KADM_NOENTRY) {
556 "kerberos principal doesn't exist; creating");
557 if ((status = kadm_add(&new)) != KADM_SUCCESS)
558 com_err(whoami, status,
559 " while creating kerberos principal");
562 com_err(whoami, status, " while setting password");
570 int reserve_user(message,retval)
574 int q_argc; /* Number of arguments to query */
575 char *q_argv[3]; /* Arguments to Moira query */
576 char *q_name; /* Name of Moira query */
577 int status = SUCCESS; /* General purpose error status */
578 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
579 char *login; /* The login name the user wants */
580 register int i; /* A counter */
582 /* Log that we are about to reserve a user. */
583 com_err(whoami, 0, "reserving user %s %s",
584 message->first, message->last);
586 /* Check to make sure that we can verify this user. */
587 if ((status = verify_user(message,retval)) == SUCCESS)
589 /* Get the requested login name from leftover packet information. */
590 login = message->leftover;
592 /* Check the login name for validity. The login name is currently
593 is allowed to contain lowercase letters in any position and
594 and numbers and underscore characters in any position but the
596 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
597 status = UREG_INVALID_UNAME;
599 if (status == SUCCESS)
600 if ((login[0] == '_') || isdigit(login[0]))
601 status = UREG_INVALID_UNAME;
603 if (status == SUCCESS)
605 for (i = 0; i < strlen(login); i++)
606 if (!islower(login[i]) && !isdigit(login[i]) &&
609 status = UREG_INVALID_UNAME;
613 if (status == SUCCESS)
615 /* Now that we have a valid user with a valid login... */
617 /* First, try to reserve the user in Moira. */
618 (void) sprintf(fstype_buf,"%d",SMS_FS_STUDENT);
619 q_name = "register_user";
620 q_argv[0] = message->db.uid;
622 q_argv[2] = fstype_buf;
624 status = sms_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
631 status = UREG_LOGIN_USED;
634 status = UREG_MISC_ERROR;
637 critical_alert(FAIL_INST,"%s returned from register_user.",
638 error_message(status));
639 status = UREG_MISC_ERROR;
644 if (status == SUCCESS)
647 * Moira login was successfully created; try to reserve kerberos
650 * If this routine fails, store the login in the retval so
651 * that it can be used in the client-side error message.
653 if ((status = reserve_krb(login)) != SUCCESS)
654 (void) strcpy(retval, login);
658 com_err(whoami, status, " returned from reserve_user");
660 com_err(whoami, 0, "User reserved");
666 int set_final_status(message)
668 /* This routine updates a user's registration status to fully
672 char *q_name; /* Name of Moira query */
673 int q_argc; /* Number of arguments for Moira query */
674 char *q_argv[2]; /* Arguments to get user by uid */
675 char state[7]; /* Can hold a 16 bit integer */
676 int status; /* Error status */
678 if (message->request == UREG_SET_PASSWORD)
679 (void) sprintf(state,"%d",US_REGISTERED);
680 else if (message->db.reg_status == US_NO_LOGIN_YET)
681 (void) sprintf(state,"%d",US_ENROLLED);
683 (void) sprintf(state,"%d",US_ENROLL_NOT_ALLOWED);
685 login = message->db.login;
686 com_err(whoami, 0, "Setting final status for %s to %s", login, state);
688 q_name = "update_user_status";
692 if ((status = sms_query(q_name, q_argc, q_argv, null_callproc,
693 (char *)0)) != SMS_SUCCESS) {
694 if (status == SMS_DEADLOCK)
695 status = UREG_MISC_ERROR;
697 critical_alert(FAIL_INST,"%s returned from update_user_status.",
698 error_message(status));
701 com_err(whoami, status, " returned from set_final_status");
703 com_err(whoami, 0, "Final status set");
708 int set_password(message,retval)
711 /* This routine is used to set the initial password for the new user. */
713 int status = SUCCESS; /* Return status */
714 char *passwd; /* User's password */
716 com_err(whoami, 0, "setting password %s %s",
717 message->first, message->last);
719 status = verify_user(message,retval);
721 /* Don't set the password unless the registration status of the user
722 is that he exists and has no password. */
723 if (status == SUCCESS)
724 status = UREG_NO_LOGIN_YET;
725 if (((int)message->request == UREG_SET_PASSWORD &&
726 status == UREG_NO_PASSWD_YET) ||
727 ((int)message->request == UREG_GET_KRB &&
728 status == UREG_HALF_ENROLLED))
730 /* User is in proper state for this transaction. */
732 passwd = message->leftover;
735 if ((status = setpass_krb(message->db.login, passwd)) != SUCCESS)
736 /* If failure, allow login name to be used in client
738 (void) strcpy(retval,message->db.login);
740 /* Otherwise, mark user as finished. */
741 status = set_final_status(message);
745 com_err(whoami, status, " returned from set_passwd");
747 com_err(whoami, 0, "Password set");
753 int getuserinfo(argc, argv, qargv)
758 int status = SUCCESS;
761 critical_alert(FAIL_INST,
762 "Wrong number of args returned from get_user_by_uid");
765 qargv[U_NAME] = strsave(argv[U_NAME]);
766 qargv[U_UID+1] = strsave(argv[U_UID]);
767 qargv[U_SHELL+1] = strsave(argv[U_SHELL]);
768 qargv[U_LAST+1] = strsave(argv[U_LAST]);
769 qargv[U_FIRST+1] = strsave(argv[U_FIRST]);
770 qargv[U_MIDDLE+1] = strsave(argv[U_MIDDLE]);
771 qargv[U_STATE+1] = strsave(argv[U_STATE]);
772 qargv[U_MITID+1] = strsave(argv[U_MITID]);
773 qargv[U_CLASS+1] = strsave(argv[U_CLASS]);
774 qargv[U_MODTIME+1] = NULL;
780 int set_identity(message,retval)
784 int q_argc; /* Number of arguments to query */
785 char *q_argv[U_END]; /* Arguments to Moira query */
786 char *q_name; /* Name of Moira query */
787 int status = SUCCESS; /* General purpose error status */
788 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
789 char *login; /* The login name the user wants */
790 register int i; /* A counter */
792 /* Log that we are about to reserve a user. */
793 com_err(whoami, 0, "setting identity %s %s",
794 message->first, message->last);
796 /* Check to make sure that we can verify this user. */
797 status = verify_user(message,retval);
798 if (status == SUCCESS || status == UREG_NOT_ALLOWED)
801 /* Get the requested login name from leftover packet information. */
802 login = message->leftover;
804 /* Check the login name for validity. The login name is currently
805 is allowed to contain lowercase letters in any position and
806 and numbers and underscore characters in any position but the
808 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
809 status = UREG_INVALID_UNAME;
811 if (status == SUCCESS)
812 if ((login[0] == '_') || isdigit(login[0]))
813 status = UREG_INVALID_UNAME;
814 if (status == SUCCESS)
816 for (i = 0; i < strlen(login); i++)
817 if (!islower(login[i]) && !isdigit(login[i]) &&
820 status = UREG_INVALID_UNAME;
824 if (status == SUCCESS)
826 /* Now that we have a valid user with a valid login... */
828 q_argv[0] = message->db.uid;
829 status = sms_query("get_user_by_uid", 1, q_argv, getuserinfo, q_argv);
830 if (status != SUCCESS) {
831 com_err(whoami, status, " while getting user info");
834 q_argv[U_NAME+1] = login;
835 q_argv[U_STATE+1] = "7";
836 status = sms_query("update_user", U_MODTIME+1, q_argv,
837 null_callproc, NULL);
844 status = UREG_LOGIN_USED;
847 status = UREG_MISC_ERROR;
850 critical_alert(FAIL_INST,"%s returned from update_user.",
851 error_message(status));
852 status = UREG_MISC_ERROR;
856 if (status == SUCCESS)
858 /* Moira login was successfully created; try to reserve kerberos
860 /* If this routine fails, store the login in the retval so
861 that it can be used in the client-side error message. */
862 if ((status = reserve_krb(login)) != SUCCESS)
863 (void) strcpy(retval, login);
867 com_err(whoami, status, " returned from set_identity");
869 com_err(whoami, 0, "Identity set");
875 void reg_com_err_hook(whoami, code, fmt, pvar)
882 fputs(whoami, stderr);
886 fputs(error_message(code), stderr);
889 _doprnt(fmt, pvar, stderr);