6 * Copyright (C) 1987 by the Massachusetts Institute of Technology
8 * Server for user registration with SMS and Kerberos.
10 * This program is a client of the Kerberos admin_server and a
11 * server for the userreg program. It is not a client of the
12 * SMS server as it is linked with libsmsglue which bypasses
13 * the network protocol.
17 static char *rcsid_reg_svr_c = "$Header$";
21 #include "admin_server.h"
22 #include "admin_err.h"
24 extern int krb_err_base;
25 extern char admin_errmsg[];
27 static char krbhst[BUFSIZ]; /* kerberos server name */
28 static char krbrealm[REALM_SZ]; /* kerberos realm name */
34 struct msg message; /* Storage for parsed packet */
35 int status = SUCCESS; /* Error status */
36 char retval[BUFSIZ]; /* Buffer to hold return message for client */
38 void req_initialize(); /* Initialize request layer */
39 void get_request(); /* Get a request */
40 void report(); /* Respond to a request */
45 /* Use com_err or output to stderr for all log messages. */
47 fprintf(stderr,"*** Debugging messages enabled. ***\n");
50 /* Error messages sent one line at a time */
53 /* Initialize user registration error table for com_err */
56 /* Connect to the SMS server */
57 if ((status = sms_connect()) != SMS_SUCCESS)
59 com_err(whoami, status, " on connect");
63 /* Authorize, telling the server who you are */
64 if ((status = sms_auth(whoami)) != SMS_SUCCESS)
66 com_err(whoami, status, " on auth");
70 if (status = get_krbrlm(krbrealm, 1)) {
71 status += krb_err_base;
72 com_err(whoami, status, " fetching kerberos realm");
76 if (status = get_krbhst(krbhst, krbrealm, 1)) {
77 status += krb_err_base;
78 com_err(whoami, status, " fetching kerberos hostname");
82 for (s = krbhst; *s && *s != '.'; s++)
88 journal = fopen(JOURNAL, "a");
89 if (journal == NULL) {
90 com_err(whoami, errno, " while opening journal file");
94 /* Allow request layer to initialize */
97 /* Sit around waiting for requests from the client. */
100 get_request(&message);
102 switch((int)message.request)
104 case UREG_VERIFY_USER:
105 status = verify_user(&message,retval);
107 case UREG_RESERVE_LOGIN:
108 status = reserve_user(&message,retval);
110 case UREG_SET_PASSWORD:
111 status = set_password(&message,retval);
115 status = UREG_UNKNOWN_REQUEST;
116 critical_alert(FAIL_INST,"Unknown request %d from userreg.",
121 /* Report what happened to client */
122 report(status, retval);
126 /* This is necessary so that this server can know where to put its
130 return("/tmp/tkt_ureg");
133 int parse_encrypted(message,data)
134 struct msg *message; /* Formatted packet */
135 struct db_data *data; /* Data from the SMS database */
136 /* This routine makes sure that the ID from the database matches
137 the ID sent accross in the packet. The information in the packet
138 was created in the following way:
140 The plain text ID number was encrypted via EncryptID() resulting
141 in the form that would appear in the SMS database. This is
142 concatinated to the plain text ID so that the ID string contains plain
143 text ID followed by a null followed by the encrypted ID. Other
144 information such as the username or password is appended. The whole
145 thing is then DES encrypted using the encrypted ID as the source of
148 This routine tries each encrypted ID in the database that belongs
149 to someone with this user's first and last name and tries to
150 decrypt the packet with this information. If it succeeds, it returns
151 zero and initializes all the fields of the formatted packet structure
152 that depend on the encrypted information. */
154 C_Block key; /* The key for DES en/decryption */
155 Key_schedule sched; /* En/decryption schedule */
156 static char decrypt[BUFSIZ]; /* Buffer to hold decrypted information */
157 long decrypt_len; /* Length of decypted ID information */
158 char recrypt[14]; /* Buffer to hold re-encrypted information */
159 static char hashid[14]; /* Buffer to hold one-way encrypted ID */
160 char idnumber[BUFSIZ]; /* Buffer to hold plain-text ID */
161 char *temp; /* A temporary string pointer */
162 int len; /* Keeps track of length left in packet */
163 int status = SUCCESS; /* Error status */
166 com_err(whoami,0,"Entering parse_encrypted");
169 /* Make the decrypted information length the same as the encrypted
170 information length. Both are integral multples of eight bytes
171 because of the DES encryption routines. */
172 decrypt_len = (long)message->encrypted_len;
174 /* Get key from the one-way encrypted ID in the SMS database */
175 string_to_key(data->mit_id, key);
176 /* Get schedule from key */
177 key_sched(key, sched);
178 /* Decrypt information from packet using this key. Since decrypt_len
179 is an integral multiple of eight bytes, it will probably be null-
181 pcbc_encrypt(message->encrypted,decrypt, decrypt_len, sched, key, DECRYPT);
183 /* Extract the plain text and encrypted ID fields from the decrypted
184 packet information. */
185 /* Since the decrypted information starts with the plain-text ID
186 followed by a null, if the decryption worked, this will only
187 copy the plain text part of the decrypted information. It is
188 important that strncpy be used because if we are not using the
189 correct key, there is no guarantee that a null will occur
190 anywhere in the string. */
191 (void) strncpy(idnumber,decrypt,(int)decrypt_len);
192 /* Point temp to the end of the plain text ID number. */
193 temp = decrypt + strlen(idnumber) + 1;
194 /* Find out how much more packet there is. */
195 len = message->encrypted_len - (temp - decrypt);
196 /* Copy the next CRYPT_LEN bytes of the decrypted information into
197 hashid if there are CRYPT_LEN more bytes to copy. There will be
198 if we have the right key. */
199 (void) strncpy(hashid, temp, min(len, CRYPT_LEN));
200 /* Point temp to the end of the encrypted ID field */
201 temp += strlen(hashid) + 1;
202 /* Find out how much more room there is. */
203 len = message->encrypted_len - (temp - decrypt);
205 /* Now compare encrypted ID's don't match. */
206 if (strcmp(hashid, data->mit_id)) status = FAILURE;
207 if (status == SUCCESS)
209 EncryptID(recrypt, idnumber, message->first, message->last);
210 /* Now compare encrypted plain text to ID from database. */
211 if (strcmp(recrypt, data->mit_id)) status = FAILURE;
214 if (status == SUCCESS)
216 /* We made it. Now we can finish initializing message. */
217 /* Point leftover to whatever is left over! */
218 message->leftover = temp;
219 message->leftover_len = len;
220 /* Since we know we have the right user, fill in the information
221 from the SMS database. */
222 message->db.reg_status = data->reg_status;
223 (void) strncpy(message->db.uid,data->uid, sizeof(message->db.uid));
224 (void) strncpy(message->db.mit_id,data->mit_id,
225 sizeof(message->db.mit_id));
226 (void) strncpy(message->db.login,data->login, sizeof(message->db.login));
231 com_err(whoami,status," parse_encrypted failed.");
233 com_err(whoami,status,"parse_encrypted succeeded.");
239 int db_callproc(argc,argv,queue)
240 int argc; /* Number of arguments returned by SMS */
241 char *argv[]; /* Arguments returned by SMS */
242 struct save_queue *queue; /* Queue to save information in */
243 /* This function is called by sms_query after each tuple found. It is
244 used by find_user to cache information about each user found. */
246 struct db_data *data; /* Structure to store the information in */
247 int status = SUCCESS; /* Error status */
250 com_err(whoami,0,"Entering db_callproc.");
257 "Wrong number of arguments returned from get_user_by_name.");
262 /* extract the needed information from the results of the SMS query */
263 data = (struct db_data *)malloc(sizeof(struct db_data));
264 data->reg_status = atoi(argv[U_STATE]);
265 (void) strncpy(data->login,argv[U_NAME],sizeof(data->login));
266 (void) strncpy(data->mit_id,argv[U_MITID],sizeof(data->mit_id));
267 (void) strncpy(data->uid,argv[U_UID],sizeof(data->uid));
269 fprintf(stderr,"Found in database:\n");
270 fprintf(stderr," Registration status: %d\n",data->reg_status);
271 fprintf(stderr," login: %s\n",data->login);
272 fprintf(stderr," MIT ID: %s\n",data->mit_id);
273 fprintf(stderr," uid: %s\n",data->uid);
275 sq_save_data(queue,data);
281 int find_user(message)
282 struct msg *message; /* Formatted packet structure */
283 /* This routine verifies that a user is allowed to register by finding
284 him/her in the SMS database. It returns the status of the SMS
285 query that it calls. */
287 #define GUBN_ARGS 2 /* Arguements needed by get_user_by_name */
288 char *q_name; /* Name of query */
289 int q_argc; /* Number of arguments for query */
290 char *q_argv[GUBN_ARGS]; /* Arguments to query */
291 int status = SUCCESS; /* Query return status */
293 struct save_queue *queue; /* Queue to hold SMS data */
294 struct db_data *data; /* Structure for data for one tuple */
295 short verified = FALSE; /* Have we verified the user? */
297 /* Zero the mit_id field in the formatted packet structure. This
298 being zeroed means that no user was found. */
299 bzero(message->db.mit_id,sizeof(message->db.mit_id));
301 if (status == SUCCESS)
303 /* Get ready to make an SMS query */
304 q_name = "get_user_by_name";
305 q_argc = GUBN_ARGS; /* #defined in this routine */
306 q_argv[0] = message->first;
307 q_argv[1] = message->last;
309 /* Create queue to hold information */
313 status = sms_query(q_name,q_argc,q_argv,db_callproc,(char *)queue);
316 fprintf(stderr," %d returned by get_user_by_name\n",status);
319 if (status == SMS_SUCCESS)
321 /* Traverse the list, freeing data as we go. If sq_get_data()
322 returns zero if there is no more data on the queue. */
323 while (sq_get_data(queue,&data))
326 /* parse_encrypted returns zero on success */
327 verified = (parse_encrypted(message,data) == SUCCESS);
332 /* Destroy the queue */
337 fprintf(stderr,"Returned from find_user\n");
338 fprintf(stderr," MIT ID: %s\n", message->db.mit_id);
339 fprintf(stderr," Registration status: %d\n",message->db.reg_status);
340 fprintf(stderr," uid: %s\n",message->db.uid);
341 fprintf(stderr," login: %s\n",message->db.login);
342 fprintf(stderr," Status from query: %d\n",status);
348 int verify_user(message,retval)
351 /* This routine determines whether a user is in the databse and returns
352 his state so that other routines can figure out whether he is the
353 correct state for various transactions. */
356 int status = SUCCESS; /* Return status */
358 /* Log that we are about to veryify user */
359 com_err(whoami,0,"verify_user %s %s",message->first,message->last);
361 /* Figure out what user (if any) can be found based on the
362 encrypted information in the packet. (See the comment on
363 parse_encrypted().) */
365 status = find_user(message);
367 /* If SMS coudn't find the user */
368 if (status == SMS_NO_MATCH)
369 status = UREG_USER_NOT_FOUND;
370 else if (status == SMS_SUCCESS)
372 /* If the information sent over in the packet did not point to a
373 valid user, the mit_id field in the formatted packet structure
375 if (message->db.mit_id[0] == NULL)
376 status = UREG_USER_NOT_FOUND;
377 /* If the user was found but the registration has already started,
378 use this as the status */
381 switch (message->db.reg_status)
383 case US_NO_LOGIN_YET:
387 status = UREG_ALREADY_REGISTERED;
390 status = UREG_NO_PASSWD_YET;
393 status = UREG_DELETED;
396 status = UREG_NOT_ALLOWED;
400 status = UREG_MISC_ERROR;
401 critical_alert(FAIL_INST,"Bad user state for login %s.",
405 /* Set retval to the login name so that the client can use
406 it in the error message it will give the user. */
407 (void) strcpy(retval,message->db.login);
411 com_err(whoami,status," returned from verify_user");
418 int status = SUCCESS; /* Return status */
420 /* Get keys for interacting with Kerberos admin server. */
421 /* principal, instance, realm, service, service instance, life, file */
422 if (status = get_svc_in_tkt("register", "sms", krbrealm, "changepw",
424 status += krb_err_base;
427 if (status == SUCCESS)
428 com_err(whoami,status,"Succeeded in getting tickets.");
430 com_err(whoami,status,"Failed to get tickets.");
435 int null_callproc(argc,argv,message)
439 /* This routine is a null callback that should be used for queries that
440 do not return tuples. If it ever gets called, something is wrong. */
442 critical_alert(FAIL_INST,"Something returned from an update query.");
446 int do_admin_call(login, passwd, uid)
447 char *login; /* Requested kerberos principal */
448 char *passwd; /* Requested password */
449 char *uid; /* Uid of user who owns this principal */
450 /* This routine gets tickets, makes the appropriate call to admin_call,
451 and destroys tickets. */
453 int status; /* Error status */
454 char uid_buf[20]; /* Holds uid for kerberos */
456 com_err(whoami,0,"Entering do_admin_call");
458 if ((status = ureg_get_tkt()) == SUCCESS)
460 /* Try to reserve kerberos principal. To do this, send a
461 password request and a null password. It will only succeed
462 if there is no principal or the principal exists and has no
464 /* 13 chars of placebo for backwards-compatability - the admin
465 server protocol reqires this. */
466 bzero(uid_buf,sizeof(uid_buf));
467 (void) sprintf(uid_buf, "%13s", uid);
469 if ((status = admin_call(ADMIN_ADD_NEW_KEY_ATTR, login,
470 "", passwd, uid_buf)) != KSUCCESS)
472 com_err(whoami,status," server error: %s",admin_errmsg);
474 if (strcmp(admin_errmsg,
475 "Principal already in kerberos database.") == 0)
476 status = UREG_KRB_TAKEN;
477 critical_alert(FAIL_INST,"%s is known to Kerberos but not SMS.",
483 com_err(whoami,status," returned from do_adin_call");
487 int reserve_user(message,retval)
491 int q_argc; /* Number of arguments to query */
492 char *q_argv[3]; /* Arguments to SMS query */
493 char *q_name; /* Name of SMS query */
494 int status = SUCCESS; /* General purpose error status */
495 char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */
496 char *login; /* The login name the user wants */
497 register int i; /* A counter */
499 /* Log that we are about to reserve a user. */
500 com_err(whoami, 0, "reserve_user %s %s",
501 message->first, message->last);
503 /* Check to make sure that we can verify this user. */
504 if ((status = verify_user(message,retval)) == SUCCESS)
506 /* Get the requested login name from leftover packet information. */
507 login = message->leftover;
509 /* Check the login name for validity. The login name is currently
510 is allowed to contain lowercase letters and numbers in any
511 position and underscore characters and periods in any position
513 if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
514 status = UREG_INVALID_UNAME;
516 if (status == SUCCESS)
518 status = UREG_INVALID_UNAME;
519 if (status == SUCCESS)
521 for (i = 0; i < strlen(login); i++)
522 if (!islower(login[i]) && !isdigit(login[i]) &&
525 status = UREG_INVALID_UNAME;
529 if (status == SUCCESS)
531 /* Now that we have a valid user with a valid login... */
533 /* First, try to reserve the user in SMS. */
534 (void) sprintf(fstype_buf,"%d",SMS_FS_STUDENT);
535 q_name = "register_user";
536 q_argv[0] = message->db.uid;
538 q_argv[2] = fstype_buf;
540 status = sms_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
547 status = UREG_LOGIN_USED;
550 status = UREG_MISC_ERROR;
551 critical_alert(FAIL_INST,"%s returned from register_user.",
552 error_message(status));
556 if (status == SUCCESS)
558 /* SMS login was successfully created; try to reserve kerberos
560 /* If this routine fails, store the login in the retval so
561 that it can be used in the client-side error message. */
562 if ((status = do_admin_call(login, "", message->db.uid)) != SUCCESS)
563 (void) strcpy(retval, login);
566 com_err(whoami, status, " returned from reserve_user");
571 int set_final_status(login)
573 /* This routine updates a user's registration status to fully
576 char *q_name; /* Name of SMS query */
577 int q_argc; /* Number of arguments for SMS query */
578 char *q_argv[2]; /* Arguments to get user by uid */
579 char state[7]; /* Can hold a 16 bit integer */
580 int status; /* Error status */
582 com_err(whoami, 0, "Setting final status for %s", login);
584 (void) sprintf(state,"%d",US_REGISTERED);
585 q_name = "update_user_status";
589 if ((status = sms_query(q_name, q_argc, q_argv, null_callproc,
590 (char *)0)) != SMS_SUCCESS)
591 critical_alert(FAIL_INST,"%s returned from update_user_status.",
592 error_message(status));
594 com_err(whoami,status," returned from set_final_status");
599 int set_password(message,retval)
602 /* This routine is used to set the initial password for the new user. */
604 int status = SUCCESS; /* Return status */
605 char *passwd; /* User's password */
607 com_err(whoami, 0, " set_password %s %s",
608 message->first, message->last);
610 status = verify_user(message,retval);
612 /* Don't set the password unless the registration status of the user
613 is that he exists and has no password. */
614 if (status == SUCCESS)
615 status = UREG_NO_LOGIN_YET;
616 if (status == UREG_NO_PASSWD_YET)
618 /* User is in proper state for this transaction. */
620 passwd = message->leftover;
623 if ((status = do_admin_call(message->db.login,
624 passwd, message->db.uid)) != SUCCESS)
625 /* If failure, allow login name to be used in client
627 (void) strcpy(retval,message->db.login);
629 /* Otherwise, mark user as finished. */
630 status = set_final_status(message->db.login);
632 com_err(whoami, status, " returned from set_passwd");
640 * c-argdecl-indent: 2
642 * c-continued-statement-offset: 4