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 if (status != ETIMEDOUT)
493 critical_alert(FAIL_INST,"%s is known to Kerberos but not SMS.",
499 com_err(whoami,status," returned from do_admin_call");
503 int reserve_user(message,retval)
507 int q_argc; /* Number of arguments to query */
508 char *q_argv[3]; /* Arguments to SMS query */
509 char *q_name; /* Name of SMS query */
510 int status = SUCCESS; /* General purpose error status */
511 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
512 char *login; /* The login name the user wants */
513 register int i; /* A counter */
515 /* Log that we are about to reserve a user. */
516 com_err(whoami, 0, "reserve_user %s %s",
517 message->first, message->last);
519 /* Check to make sure that we can verify this user. */
520 if ((status = verify_user(message,retval)) == SUCCESS)
522 /* Get the requested login name from leftover packet information. */
523 login = message->leftover;
525 /* Check the login name for validity. The login name is currently
526 is allowed to contain lowercase letters and numbers in any
527 position and underscore characters in any position but the
529 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
530 status = UREG_INVALID_UNAME;
532 if (status == SUCCESS)
534 status = UREG_INVALID_UNAME;
535 if (status == SUCCESS)
537 for (i = 0; i < strlen(login); i++)
538 if (!islower(login[i]) && !isdigit(login[i]) &&
541 status = UREG_INVALID_UNAME;
545 if (status == SUCCESS)
547 /* Now that we have a valid user with a valid login... */
549 /* First, try to reserve the user in SMS. */
550 (void) sprintf(fstype_buf,"%d",SMS_FS_STUDENT);
551 q_name = "register_user";
552 q_argv[0] = message->db.uid;
554 q_argv[2] = fstype_buf;
556 status = sms_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
563 status = UREG_LOGIN_USED;
566 status = UREG_MISC_ERROR;
569 critical_alert(FAIL_INST,"%s returned from register_user.",
570 error_message(status));
571 status = UREG_MISC_ERROR;
575 if (status == SUCCESS)
577 /* SMS login was successfully created; try to reserve kerberos
579 /* If this routine fails, store the login in the retval so
580 that it can be used in the client-side error message. */
581 if ((status = do_admin_call(login, "", message->db.uid)) != SUCCESS)
582 (void) strcpy(retval, login);
585 com_err(whoami, status, " returned from reserve_user");
590 int set_final_status(message)
592 /* This routine updates a user's registration status to fully
596 char *q_name; /* Name of SMS query */
597 int q_argc; /* Number of arguments for SMS query */
598 char *q_argv[2]; /* Arguments to get user by uid */
599 char state[7]; /* Can hold a 16 bit integer */
600 int status; /* Error status */
602 if (message->request == UREG_SET_PASSWORD)
603 (void) sprintf(state,"%d",US_REGISTERED);
604 else if (message->db.reg_status == US_NO_LOGIN_YET)
605 (void) sprintf(state,"%d",US_ENROLLED);
607 (void) sprintf(state,"%d",US_ENROLL_NOT_ALLOWED);
609 login = message->db.login;
610 com_err(whoami, 0, "Setting final status for %s to %s", login, state);
612 q_name = "update_user_status";
616 if ((status = sms_query(q_name, q_argc, q_argv, null_callproc,
617 (char *)0)) != SMS_SUCCESS) {
618 if (status == SMS_DEADLOCK)
619 status = UREG_MISC_ERROR;
621 critical_alert(FAIL_INST,"%s returned from update_user_status.",
622 error_message(status));
624 com_err(whoami,status," returned from set_final_status");
629 int set_password(message,retval)
632 /* This routine is used to set the initial password for the new user. */
634 int status = SUCCESS; /* Return status */
635 char *passwd; /* User's password */
637 com_err(whoami, 0, " set_password %s %s",
638 message->first, message->last);
640 status = verify_user(message,retval);
642 /* Don't set the password unless the registration status of the user
643 is that he exists and has no password. */
644 if (status == SUCCESS)
645 status = UREG_NO_LOGIN_YET;
646 if (status == UREG_NO_PASSWD_YET)
648 /* User is in proper state for this transaction. */
650 passwd = message->leftover;
653 if ((status = do_admin_call(message->db.login,
654 passwd, message->db.uid)) != SUCCESS)
655 /* If failure, allow login name to be used in client
657 (void) strcpy(retval,message->db.login);
659 /* Otherwise, mark user as finished. */
660 status = set_final_status(message);
662 com_err(whoami, status, " returned from set_passwd");
668 int getuserinfo(argc, argv, qargv)
673 int status = SUCCESS;
676 critical_alert(FAIL_INST,
677 "Wrong number of args returned from get_user_by_uid");
680 qargv[U_NAME] = strsave(argv[U_NAME]);
681 qargv[U_UID+1] = strsave(argv[U_UID]);
682 qargv[U_SHELL+1] = strsave(argv[U_SHELL]);
683 qargv[U_LAST+1] = strsave(argv[U_LAST]);
684 qargv[U_FIRST+1] = strsave(argv[U_FIRST]);
685 qargv[U_MIDDLE+1] = strsave(argv[U_MIDDLE]);
686 qargv[U_STATE+1] = strsave(argv[U_STATE]);
687 qargv[U_MITID+1] = strsave(argv[U_MITID]);
688 qargv[U_CLASS+1] = strsave(argv[U_CLASS]);
689 qargv[U_MODTIME+1] = NULL;
695 int set_identity(message,retval)
699 int q_argc; /* Number of arguments to query */
700 char *q_argv[U_END]; /* Arguments to SMS query */
701 char *q_name; /* Name of SMS query */
702 int status = SUCCESS; /* General purpose error status */
703 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
704 char *login; /* The login name the user wants */
705 register int i; /* A counter */
707 /* Log that we are about to reserve a user. */
708 com_err(whoami, 0, "set_identity %s %s",
709 message->first, message->last);
711 /* Check to make sure that we can verify this user. */
712 status = verify_user(message,retval);
713 if (status == SUCCESS || status == UREG_NOT_ALLOWED)
716 /* Get the requested login name from leftover packet information. */
717 login = message->leftover;
719 /* Check the login name for validity. The login name is currently
720 is allowed to contain lowercase letters and numbers in any
721 position and underscore characters in any position but the
723 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
724 status = UREG_INVALID_UNAME;
726 if (status == SUCCESS)
728 status = UREG_INVALID_UNAME;
729 if (status == SUCCESS)
731 for (i = 0; i < strlen(login); i++)
732 if (!islower(login[i]) && !isdigit(login[i]) &&
735 status = UREG_INVALID_UNAME;
739 if (status == SUCCESS)
741 /* Now that we have a valid user with a valid login... */
743 q_argv[0] = message->db.uid;
744 status = sms_query("get_user_by_uid", 1, q_argv, getuserinfo, q_argv);
745 if (status != SUCCESS) {
746 com_err(whoami, status, " while getting user info");
749 q_argv[U_NAME+1] = login;
750 status = sms_query("update_user", U_MODTIME+1, q_argv,
751 null_callproc, NULL);
758 status = UREG_LOGIN_USED;
761 status = UREG_MISC_ERROR;
764 critical_alert(FAIL_INST,"%s returned from update_user.",
765 error_message(status));
766 status = UREG_MISC_ERROR;
770 if (status == SUCCESS)
772 /* SMS login was successfully created; try to reserve kerberos
774 /* If this routine fails, store the login in the retval so
775 that it can be used in the client-side error message. */
776 if ((status = do_admin_call(login, "", message->db.uid)) != SUCCESS)
777 (void) strcpy(retval, login);
780 com_err(whoami, status, " returned from set_identity");
786 void reg_com_err_hook(whoami, code, fmt, pvar)
793 fputs(whoami, stderr);
797 fputs(error_message(code), stderr);
800 _doprnt(fmt, pvar, stderr);