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 SMS 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 * SMS 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>
24 #include "admin_server.h"
25 #include "admin_err.h"
28 extern char admin_errmsg[];
30 static char krbhst[BUFSIZ]; /* kerberos server name */
31 static char krbrealm[REALM_SZ]; /* kerberos realm name */
32 void reg_com_err_hook();
39 struct msg message; /* Storage for parsed packet */
40 int status = SUCCESS; /* Error status */
41 char retval[BUFSIZ]; /* Buffer to hold return message for client */
43 void req_initialize(); /* Initialize request layer */
44 void get_request(); /* Get a request */
45 void report(); /* Respond to a request */
50 /* Use com_err or output to stderr for all log messages. */
52 fprintf(stderr,"*** Debugging messages enabled. ***\n");
55 /* Error messages sent one line at a time */
58 set_com_err_hook(reg_com_err_hook);
60 /* Initialize user registration error table for com_err */
63 /* Connect to the SMS server */
64 if ((status = sms_connect(SMS_SERVER)) != SMS_SUCCESS)
66 com_err(whoami, status, " on connect");
70 /* Authorize, telling the server who you are */
71 if ((status = sms_auth(whoami)) != SMS_SUCCESS)
73 com_err(whoami, status, " on auth");
77 if (status = krb_get_lrealm(krbrealm, 1)) {
78 status += ERROR_TABLE_BASE_krb;
79 com_err(whoami, status, " fetching kerberos realm");
83 if (status = krb_get_krbhst(krbhst, krbrealm, 1)) {
84 status += ERROR_TABLE_BASE_krb;
85 com_err(whoami, status, " fetching kerberos hostname");
89 for (s = krbhst; *s && *s != '.'; s++)
95 journal = fopen(JOURNAL, "a");
96 if (journal == NULL) {
97 com_err(whoami, errno, " while opening journal file");
101 /* Allow request layer to initialize */
104 /* Sit around waiting for requests from the client. */
107 get_request(&message);
109 switch((int)message.request)
111 case UREG_VERIFY_USER:
112 status = verify_user(&message,retval);
114 case UREG_RESERVE_LOGIN:
115 status = reserve_user(&message,retval);
117 case UREG_SET_PASSWORD:
119 status = set_password(&message,retval);
122 status = set_identity(&message,retval);
125 status = UREG_UNKNOWN_REQUEST;
126 critical_alert(FAIL_INST,"Unknown request %d from userreg.",
131 /* Report what happened to client */
132 report(status, retval);
136 /* This is necessary so that this server can know where to put its
140 return("/tmp/tkt_ureg");
143 int parse_encrypted(message,data)
144 struct msg *message; /* Formatted packet */
145 struct db_data *data; /* Data from the SMS database */
146 /* This routine makes sure that the ID from the database matches
147 the ID sent accross in the packet. The information in the packet
148 was created in the following way:
150 The plain text ID number was encrypted via EncryptID() resulting
151 in the form that would appear in the SMS database. This is
152 concatinated to the plain text ID so that the ID string contains plain
153 text ID followed by a null followed by the encrypted ID. Other
154 information such as the username or password is appended. The whole
155 thing is then DES encrypted using the encrypted ID as the source of
158 This routine tries each encrypted ID in the database that belongs
159 to someone with this user's first and last name and tries to
160 decrypt the packet with this information. If it succeeds, it returns
161 zero and initializes all the fields of the formatted packet structure
162 that depend on the encrypted information. */
164 C_Block key; /* The key for DES en/decryption */
165 Key_schedule sched; /* En/decryption schedule */
166 static char decrypt[BUFSIZ]; /* Buffer to hold decrypted information */
167 long decrypt_len; /* Length of decypted ID information */
168 char recrypt[14]; /* Buffer to hold re-encrypted information */
169 static char hashid[14]; /* Buffer to hold one-way encrypted ID */
170 char idnumber[BUFSIZ]; /* Buffer to hold plain-text ID */
171 char *temp; /* A temporary string pointer */
172 int len; /* Keeps track of length left in packet */
173 int status = SUCCESS; /* Error status */
176 com_err(whoami,0,"Entering parse_encrypted");
179 /* Make the decrypted information length the same as the encrypted
180 information length. Both are integral multples of eight bytes
181 because of the DES encryption routines. */
182 decrypt_len = (long)message->encrypted_len;
184 /* Get key from the one-way encrypted ID in the SMS database */
185 string_to_key(data->mit_id, key);
186 /* Get schedule from key */
187 key_sched(key, sched);
188 /* Decrypt information from packet using this key. Since decrypt_len
189 is an integral multiple of eight bytes, it will probably be null-
191 pcbc_encrypt(message->encrypted,decrypt, decrypt_len, sched, key, DECRYPT);
193 /* Extract the plain text and encrypted ID fields from the decrypted
194 packet information. */
195 /* Since the decrypted information starts with the plain-text ID
196 followed by a null, if the decryption worked, this will only
197 copy the plain text part of the decrypted information. It is
198 important that strncpy be used because if we are not using the
199 correct key, there is no guarantee that a null will occur
200 anywhere in the string. */
201 (void) strncpy(idnumber,decrypt,(int)decrypt_len);
202 /* Point temp to the end of the plain text ID number. */
203 temp = decrypt + strlen(idnumber) + 1;
204 /* Find out how much more packet there is. */
205 len = message->encrypted_len - (temp - decrypt);
206 /* Copy the next CRYPT_LEN bytes of the decrypted information into
207 hashid if there are CRYPT_LEN more bytes to copy. There will be
208 if we have the right key. */
209 (void) strncpy(hashid, temp, min(len, CRYPT_LEN));
210 /* Point temp to the end of the encrypted ID field */
211 temp += strlen(hashid) + 1;
212 /* Find out how much more room there is. */
213 len = message->encrypted_len - (temp - decrypt);
215 /* Now compare encrypted ID's don't match. */
216 if (strcmp(hashid, data->mit_id)) status = FAILURE;
217 if (status == SUCCESS)
219 EncryptID(recrypt, idnumber, message->first, message->last);
220 /* Now compare encrypted plain text to ID from database. */
221 if (strcmp(recrypt, data->mit_id)) status = FAILURE;
224 if (status == SUCCESS)
226 /* We made it. Now we can finish initializing message. */
227 /* Point leftover to whatever is left over! */
228 message->leftover = temp;
229 message->leftover_len = len;
230 /* Since we know we have the right user, fill in the information
231 from the SMS database. */
232 message->db.reg_status = data->reg_status;
233 (void) strncpy(message->db.uid,data->uid, sizeof(message->db.uid));
234 (void) strncpy(message->db.mit_id,data->mit_id,
235 sizeof(message->db.mit_id));
236 (void) strncpy(message->db.login,data->login, sizeof(message->db.login));
241 com_err(whoami,status," parse_encrypted failed.");
243 com_err(whoami,status,"parse_encrypted succeeded.");
249 int db_callproc(argc,argv,queue)
250 int argc; /* Number of arguments returned by SMS */
251 char *argv[]; /* Arguments returned by SMS */
252 struct save_queue *queue; /* Queue to save information in */
253 /* This function is called by sms_query after each tuple found. It is
254 used by find_user to cache information about each user found. */
256 struct db_data *data; /* Structure to store the information in */
257 int status = SUCCESS; /* Error status */
260 com_err(whoami,0,"Entering db_callproc.");
267 "Wrong number of arguments returned from get_user_by_name.");
272 /* extract the needed information from the results of the SMS query */
273 data = (struct db_data *)malloc(sizeof(struct db_data));
274 data->reg_status = atoi(argv[U_STATE]);
275 (void) strncpy(data->login,argv[U_NAME],sizeof(data->login));
276 (void) strncpy(data->mit_id,argv[U_MITID],sizeof(data->mit_id));
277 (void) strncpy(data->uid,argv[U_UID],sizeof(data->uid));
279 fprintf(stderr,"Found in database:\n");
280 fprintf(stderr," Registration status: %d\n",data->reg_status);
281 fprintf(stderr," login: %s\n",data->login);
282 fprintf(stderr," MIT ID: %s\n",data->mit_id);
283 fprintf(stderr," uid: %s\n",data->uid);
285 sq_save_data(queue,data);
291 int find_user(message)
292 struct msg *message; /* Formatted packet structure */
293 /* This routine verifies that a user is allowed to register by finding
294 him/her in the SMS database. It returns the status of the SMS
295 query that it calls. */
297 #define GUBN_ARGS 2 /* Arguements needed by get_user_by_name */
298 char *q_name; /* Name of query */
299 int q_argc; /* Number of arguments for query */
300 char *q_argv[GUBN_ARGS]; /* Arguments to query */
301 int status = SUCCESS; /* Query return status */
303 struct save_queue *queue; /* Queue to hold SMS data */
304 struct db_data *data; /* Structure for data for one tuple */
305 short verified = FALSE; /* Have we verified the user? */
307 /* Zero the mit_id field in the formatted packet structure. This
308 being zeroed means that no user was found. */
309 bzero(message->db.mit_id,sizeof(message->db.mit_id));
311 if (status == SUCCESS)
313 /* Get ready to make an SMS query */
314 q_name = "get_user_by_name";
315 q_argc = GUBN_ARGS; /* #defined in this routine */
316 q_argv[0] = message->first;
317 q_argv[1] = message->last;
319 /* Create queue to hold information */
323 status = sms_query(q_name,q_argc,q_argv,db_callproc,(char *)queue);
326 fprintf(stderr," %d returned by get_user_by_name\n",status);
329 if (status == SMS_SUCCESS)
331 /* Traverse the list, freeing data as we go. If sq_get_data()
332 returns zero if there is no more data on the queue. */
333 while (sq_get_data(queue,&data))
336 /* parse_encrypted returns zero on success */
337 verified = (parse_encrypted(message,data) == SUCCESS);
342 /* Destroy the queue */
347 fprintf(stderr,"Returned from find_user\n");
348 fprintf(stderr," MIT ID: %s\n", message->db.mit_id);
349 fprintf(stderr," Registration status: %d\n",message->db.reg_status);
350 fprintf(stderr," uid: %s\n",message->db.uid);
351 fprintf(stderr," login: %s\n",message->db.login);
352 fprintf(stderr," Status from query: %d\n",status);
358 int verify_user(message,retval)
361 /* This routine determines whether a user is in the databse and returns
362 his state so that other routines can figure out whether he is the
363 correct state for various transactions. */
366 int status = SUCCESS; /* Return status */
368 /* Log that we are about to veryify user */
369 com_err(whoami,0,"verify_user %s %s",message->first,message->last);
371 /* Figure out what user (if any) can be found based on the
372 encrypted information in the packet. (See the comment on
373 parse_encrypted().) */
375 status = find_user(message);
377 /* If SMS coudn't find the user */
378 if (status == SMS_NO_MATCH)
379 status = UREG_USER_NOT_FOUND;
380 else if (status == SMS_SUCCESS)
382 /* If the information sent over in the packet did not point to a
383 valid user, the mit_id field in the formatted packet structure
385 if (message->db.mit_id[0] == NULL)
386 status = UREG_USER_NOT_FOUND;
387 /* If the user was found but the registration has already started,
388 use this as the status */
391 switch (message->db.reg_status)
393 case US_NO_LOGIN_YET:
397 status = UREG_ALREADY_REGISTERED;
400 status = UREG_NO_PASSWD_YET;
403 status = UREG_DELETED;
406 status = UREG_NOT_ALLOWED;
409 status = UREG_ENROLLED;
411 case US_ENROLL_NOT_ALLOWED:
412 status = UREG_ENROLL_NOT_ALLOWED;
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);
426 com_err(whoami,status," returned from verify_user");
433 int status = SUCCESS; /* Return status */
435 /* Get keys for interacting with Kerberos admin server. */
436 /* principal, instance, realm, service, service instance, life, file */
437 if (status = krb_get_svc_in_tkt("register", "sms", krbrealm, "changepw",
439 status += ERROR_TABLE_BASE_krb;
442 if (status == SUCCESS)
443 com_err(whoami,status,"Succeeded in getting tickets.");
445 com_err(whoami,status,"Failed to get tickets.");
450 int null_callproc(argc,argv,message)
454 /* This routine is a null callback that should be used for queries that
455 do not return tuples. If it ever gets called, something is wrong. */
457 critical_alert(FAIL_INST,"Something returned from an update query.");
461 int do_admin_call(login, passwd, uid)
462 char *login; /* Requested kerberos principal */
463 char *passwd; /* Requested password */
464 char *uid; /* Uid of user who owns this principal */
465 /* This routine gets tickets, makes the appropriate call to admin_call,
466 and destroys tickets. */
468 int status; /* Error status */
469 char uid_buf[20]; /* Holds uid for kerberos */
471 com_err(whoami,0,"Entering do_admin_call");
473 if ((status = ureg_get_tkt()) == SUCCESS)
475 /* Try to reserve kerberos principal. To do this, send a
476 password request and a null password. It will only succeed
477 if there is no principal or the principal exists and has no
479 /* 13 chars of placebo for backwards-compatability - the admin
480 server protocol reqires this. */
481 bzero(uid_buf,sizeof(uid_buf));
482 (void) sprintf(uid_buf, "%13s", uid);
484 if ((status = admin_call(ADMIN_ADD_NEW_KEY_ATTR, login,
485 "", passwd, uid_buf)) != KSUCCESS)
487 com_err(whoami,status," server error: %s",admin_errmsg);
489 if (strcmp(admin_errmsg,
490 "Principal already in kerberos database.") == 0)
491 status = UREG_KRB_TAKEN;
492 critical_alert(FAIL_INST,"%s is known to Kerberos but not SMS.",
498 com_err(whoami,status," returned from do_admin_call");
502 int reserve_user(message,retval)
506 int q_argc; /* Number of arguments to query */
507 char *q_argv[3]; /* Arguments to SMS query */
508 char *q_name; /* Name of SMS query */
509 int status = SUCCESS; /* General purpose error status */
510 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
511 char *login; /* The login name the user wants */
512 register int i; /* A counter */
514 /* Log that we are about to reserve a user. */
515 com_err(whoami, 0, "reserve_user %s %s",
516 message->first, message->last);
518 /* Check to make sure that we can verify this user. */
519 if ((status = verify_user(message,retval)) == SUCCESS)
521 /* Get the requested login name from leftover packet information. */
522 login = message->leftover;
524 /* Check the login name for validity. The login name is currently
525 is allowed to contain lowercase letters and numbers in any
526 position and underscore characters in any position but the
528 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
529 status = UREG_INVALID_UNAME;
531 if (status == SUCCESS)
533 status = UREG_INVALID_UNAME;
534 if (status == SUCCESS)
536 for (i = 0; i < strlen(login); i++)
537 if (!islower(login[i]) && !isdigit(login[i]) &&
540 status = UREG_INVALID_UNAME;
544 if (status == SUCCESS)
546 /* Now that we have a valid user with a valid login... */
548 /* First, try to reserve the user in SMS. */
549 (void) sprintf(fstype_buf,"%d",SMS_FS_STUDENT);
550 q_name = "register_user";
551 q_argv[0] = message->db.uid;
553 q_argv[2] = fstype_buf;
555 status = sms_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
562 status = UREG_LOGIN_USED;
565 status = UREG_MISC_ERROR;
568 critical_alert(FAIL_INST,"%s returned from register_user.",
569 error_message(status));
570 status = UREG_MISC_ERROR;
574 if (status == SUCCESS)
576 /* SMS login was successfully created; try to reserve kerberos
578 /* If this routine fails, store the login in the retval so
579 that it can be used in the client-side error message. */
580 if ((status = do_admin_call(login, "", message->db.uid)) != SUCCESS)
581 (void) strcpy(retval, login);
584 com_err(whoami, status, " returned from reserve_user");
589 int set_final_status(message)
591 /* This routine updates a user's registration status to fully
595 char *q_name; /* Name of SMS query */
596 int q_argc; /* Number of arguments for SMS query */
597 char *q_argv[2]; /* Arguments to get user by uid */
598 char state[7]; /* Can hold a 16 bit integer */
599 int status; /* Error status */
601 if (message->request == UREG_SET_PASSWORD)
602 (void) sprintf(state,"%d",US_REGISTERED);
603 else if (message->db.reg_status == US_NO_LOGIN_YET)
604 (void) sprintf(state,"%d",US_ENROLLED);
606 (void) sprintf(state,"%d",US_ENROLL_NOT_ALLOWED);
608 login = message->db.login;
609 com_err(whoami, 0, "Setting final status for %s to %s", login, state);
611 q_name = "update_user_status";
615 if ((status = sms_query(q_name, q_argc, q_argv, null_callproc,
616 (char *)0)) != SMS_SUCCESS) {
617 if (status == SMS_DEADLOCK)
618 status = UREG_MISC_ERROR;
620 critical_alert(FAIL_INST,"%s returned from update_user_status.",
621 error_message(status));
623 com_err(whoami,status," returned from set_final_status");
628 int set_password(message,retval)
631 /* This routine is used to set the initial password for the new user. */
633 int status = SUCCESS; /* Return status */
634 char *passwd; /* User's password */
636 com_err(whoami, 0, " set_password %s %s",
637 message->first, message->last);
639 status = verify_user(message,retval);
641 /* Don't set the password unless the registration status of the user
642 is that he exists and has no password. */
643 if (status == SUCCESS)
644 status = UREG_NO_LOGIN_YET;
645 if (status == UREG_NO_PASSWD_YET)
647 /* User is in proper state for this transaction. */
649 passwd = message->leftover;
652 if ((status = do_admin_call(message->db.login,
653 passwd, message->db.uid)) != SUCCESS)
654 /* If failure, allow login name to be used in client
656 (void) strcpy(retval,message->db.login);
658 /* Otherwise, mark user as finished. */
659 status = set_final_status(message);
661 com_err(whoami, status, " returned from set_passwd");
667 int getuserinfo(argc, argv, qargv)
672 int status = SUCCESS;
675 critical_alert(FAIL_INST,
676 "Wrong number of args returned from get_user_by_uid");
679 qargv[U_NAME] = strsave(argv[U_NAME]);
680 qargv[U_UID+1] = strsave(argv[U_UID]);
681 qargv[U_SHELL+1] = strsave(argv[U_SHELL]);
682 qargv[U_LAST+1] = strsave(argv[U_LAST]);
683 qargv[U_FIRST+1] = strsave(argv[U_FIRST]);
684 qargv[U_MIDDLE+1] = strsave(argv[U_MIDDLE]);
685 qargv[U_STATE+1] = strsave(argv[U_STATE]);
686 qargv[U_MITID+1] = strsave(argv[U_MITID]);
687 qargv[U_CLASS+1] = strsave(argv[U_CLASS]);
688 qargv[U_MODTIME+1] = NULL;
694 int set_identity(message,retval)
698 int q_argc; /* Number of arguments to query */
699 char *q_argv[U_END]; /* Arguments to SMS query */
700 char *q_name; /* Name of SMS query */
701 int status = SUCCESS; /* General purpose error status */
702 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
703 char *login; /* The login name the user wants */
704 register int i; /* A counter */
706 /* Log that we are about to reserve a user. */
707 com_err(whoami, 0, "set_identity %s %s",
708 message->first, message->last);
710 /* Check to make sure that we can verify this user. */
711 status = verify_user(message,retval);
712 if (status == SUCCESS || status == UREG_NOT_ALLOWED)
715 /* Get the requested login name from leftover packet information. */
716 login = message->leftover;
718 /* Check the login name for validity. The login name is currently
719 is allowed to contain lowercase letters and numbers in any
720 position and underscore characters in any position but the
722 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
723 status = UREG_INVALID_UNAME;
725 if (status == SUCCESS)
727 status = UREG_INVALID_UNAME;
728 if (status == SUCCESS)
730 for (i = 0; i < strlen(login); i++)
731 if (!islower(login[i]) && !isdigit(login[i]) &&
734 status = UREG_INVALID_UNAME;
738 if (status == SUCCESS)
740 /* Now that we have a valid user with a valid login... */
742 q_argv[0] = message->db.uid;
743 status = sms_query("get_user_by_uid", 1, q_argv, getuserinfo, q_argv);
744 if (status != SUCCESS) {
745 com_err(whoami, status, " while getting user info");
748 q_argv[U_NAME+1] = login;
749 status = sms_query("update_user", U_MODTIME+1, q_argv,
750 null_callproc, NULL);
757 status = UREG_LOGIN_USED;
760 status = UREG_MISC_ERROR;
763 critical_alert(FAIL_INST,"%s returned from update_user.",
764 error_message(status));
765 status = UREG_MISC_ERROR;
769 if (status == SUCCESS)
771 /* SMS login was successfully created; try to reserve kerberos
773 /* If this routine fails, store the login in the retval so
774 that it can be used in the client-side error message. */
775 if ((status = do_admin_call(login, "", message->db.uid)) != SUCCESS)
776 (void) strcpy(retval, login);
779 com_err(whoami, status, " returned from set_identity");
785 void reg_com_err_hook(whoami, code, fmt, pvar)
792 fputs(whoami, stderr);
796 fputs(error_message(code), stderr);
799 _doprnt(fmt, pvar, stderr);