X-Git-Url: http://andersk.mit.edu/gitweb/moira.git/blobdiff_plain/3e9b5b7bf6a5bbcd2a7aba48fb9bf801b718a874..ea0caf4a83b273a8b146ffa0b87e07cb66c8ed3e:/reg_svr/reg_svr.c diff --git a/reg_svr/reg_svr.c b/reg_svr/reg_svr.c index d365f7ec..daef5ab7 100644 --- a/reg_svr/reg_svr.c +++ b/reg_svr/reg_svr.c @@ -1,837 +1,968 @@ -/* - * $Source$ - * $Author$ - * $Header$ - * - * Copyright (C) 1987 by the Massachusetts Institute of Technology +/* $Id$ * - * Server for user registration with SMS and Kerberos. + * Server for user registration with Moira and Kerberos. * - * This program is a client of the SMS server and the Kerberos - * admin_server, and is a server for the userreg program. - * - * $Log$ - * Revision 1.9 1988-07-26 14:50:40 qjb - * Added comments and did some cleaning up in preparation for rewrite. - * This version will not run; the last version that will is 1.8. + * This program is a client of the Kerberos admin_server and a + * server for the userreg program. It is not a client of the + * Moira server as it is linked with libmoiraglue which bypasses + * the network protocol. * - * Revision 1.8 88/07/20 15:39:25 mar - * find realm at runtime; don't use hard-coded one - * - * Revision 1.7 88/02/08 15:08:15 mar - * Moved header file locations - * - * Revision 1.6 87/09/21 15:19:11 wesommer - * Allow numbers, _, and . as legal characters in the username. - * - * Revision 1.5 87/09/10 22:18:32 wesommer - * Clean up output format. - * - * Revision 1.4 87/09/04 23:33:19 wesommer - * Deleted test scaffolding (second oops.) - * - * Revision 1.3 87/09/03 03:05:18 wesommer - * Version used for userreg tests. - * - * Revision 1.2 87/08/22 18:39:45 wesommer - * User registration server. - * - * Revision 1.1 87/07/31 15:48:13 wesommer - * Initial revision - * + * Copyright (C) 1987-1998 by the Massachusetts Institute of Technology + * For copying and distribution information, please see the file + * . */ -#ifndef lint -static char *rcsid_reg_svr_c = "$Header$"; -#endif lint +#include +#include +#include +#include + +#include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include + #include -#include -#include -#include "ureg_err.h" -#include "ureg_proto.h" -#include "sms.h" -#include "admin_server.h" -#include "admin_err.h" -#include - -#define WHOAMI "reg_svr" /* Name of program for SMS logging */ -#define CUR_SMS_VERSION SMS_VERSION_2 /* SMS version for this program */ - - -extern int abort(); -extern char *strdup(); -extern char *malloc(); -extern int krb_err_base; +#include +#include +#include + +#include "reg_svr.h" + +RCSID("$Header$"); + extern char admin_errmsg[]; -extern int errno; /* Unix error number */ - -/* This is the structure used to store the packet information */ -/* Get this. The register client gets the MIT id of the registering user - in plain text, encrypts it with one-way password encryption, - concatenates that to the plain text id, and then des encrypts the - whole thing using the password encrypted id as the key! The result - goes in enc_mitid. */ -struct msg -{ - u_long version; /* SMS version */ - u_long request; /* Request */ - char *first; /* First name */ - char *last; /* Last name */ - char *enc_mitid; /* See comment above */ - int enc_mitid_len; /* Length of enc_mitid */ -}; - -static char errmsg[BUFSIZ]; - -main() +FILE *journal; +char *whoami; + +int parse_encrypted(struct msg *message, struct db_data *data); +int parse_encrypted(struct msg *message, struct db_data *data); +int db_callproc(int argc, char **argv, void *queue); +int find_user(struct msg *message); +int verify_user(struct msg *message, char *retval); +int ureg_kadm_init(void); +int reserve_krb(char *login); +int setpass_krb(char *login, char *password); +int reserve_user(struct msg *message, char *retval); +int set_final_status(struct msg *message); +int set_password(struct msg *message, char *retval); +int getuserinfo(int argc, char **argv, void *qa); +int set_identity(struct msg *message, char *retval); +int get_secure(struct msg *message, char *retval); +int set_secure(struct msg *message, char *retval); + +int main(int argc, char *argv[]) { - struct servent *sp; /* Service info from /etc/services */ - int s; /* Socket descriptor */ - struct sockaddr_in sin; /* Internet style socket address */ - int addrlen; /* Size of socket address (sin) */ - char packet[BUFSIZ]; /* Buffer for packet transmission */ - int pktlen; /* Size of packet */ - u_long seqno; /* Sequence number for packet transmission */ - struct msg message; /* Storage for parsed packet */ - int status; /* General purpose error status */ - - /* Error messages sent one line at a time */ - setlinebuf(stderr); - - /* Initialize user registration error table */ - init_ureg_err_tbl(); - - /* Get service information from /etc/services */ - if ((sp = getservbyname("sms_ureg", "udp")) == NULL) - { - fprintf(stderr, "Unknown service sms_ureg/udp\n"); - exit(1); - } - - /* Get an internet style datagram socket */ - if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0) - { - perror("socket"); - exit(1); - } - bzero((char *)&sin, sizeof(sin)); - - sin.sin_family = AF_INET; - sin.sin_port = sp->s_port; - sin.sin_addr.s_addr = INADDR_ANY; - - /* Bind a name to the socket */ - if (bind(s, &sin, sizeof(sin)) < 0) - { - perror("bind"); - exit(1); - } - - /* Connect to the SMS server */ - if ((status = sms_connect()) != SMS_SUCCESS) - { - com_err(WHOAMI, status, " on connect"); - exit(1); - } - - /* Authorize, telling the server who you are */ - if ((status = sms_auth(WHOAMI)) != SMS_SUCCESS) - { - com_err(WHOAMI, status, " on auth"); - exit(1); - } - - /* Sit around waiting for requests from the client. */ - for (;;) - { - com_err(WHOAMI, 0, "Ready for next request"); - addrlen = sizeof(sin); - bzero(errmsg, BUFSIZ); - /* Receive a packet into buf. */ - if ((pktlen = recvfrom(s,packet,sizeof(packet),0,&sin,&addrlen)) < 0) - { - perror("recvfrom"); - if (errno == EINTR) continue; - exit(1); - } - - /* Parse a request packet */ - if ((status = parse_pkt(packet, pktlen, &seqno, &message)) != 0) - { - pktlen = sizeof(packet); - /* Format packet to send back to the client */ - format_pkt(packet, &pktlen, seqno, status, (char *)NULL); - /* Report the error the the client */ - (void) sendto(s, packet, pktlen, 0, &sin, addrlen); - continue; - } - - /* do action */ - switch((int)message.request) + struct msg message; /* Storage for parsed packet */ + int status = SUCCESS; /* Error status */ + char retval[BUFSIZ]; /* Buffer to hold return message for client */ + + /* Initialize */ + whoami = argv[0]; + + /* Error messages sent one line at a time */ + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); + setvbuf(stdout, NULL, _IOLBF, BUFSIZ); + + /* Initialize com_err error tables */ + init_ureg_err_tbl(); + init_krb_err_tbl(); + init_kadm_err_tbl(); + initialize_gdss_error_table(); + + /* Set the name of our kerberos ticket file */ + krb_set_tkt_string("/tmp/tkt_ureg"); + + /* Connect to the Moira server */ + if ((status = mr_connect(MOIRA_SERVER)) != MR_SUCCESS) + { + com_err(whoami, status, " on mr_connect"); + exit(1); + } + + /* Authorize, telling the server who you are */ + if ((status = mr_auth(whoami)) != MR_SUCCESS) + { + com_err(whoami, status, " on mr_auth"); + exit(1); + } + + journal = fopen(REGJOURNAL, "a"); + if (!journal) + { + com_err(whoami, errno, " while opening journal file"); + exit(1); + } + + /* Allow request layer to initialize */ + req_initialize(); + + /* Sit around waiting for requests from the client. */ + for (;;) + { + get_request(&message); + + switch (message.request) { - case UREG_VERIFY_USER: - status = verify_user(&message); - break; - case UREG_RESERVE_LOGIN: - status = reserve_user(&message); - break; - case UREG_SET_PASSWORD: - status = set_password(&message); - break; - - default: - status = UREG_UNKNOWN_REQUEST; - break; + case UREG_VERIFY_USER: + status = verify_user(&message, retval); + break; + case UREG_RESERVE_LOGIN: + status = reserve_user(&message, retval); + break; + case UREG_SET_PASSWORD: + case UREG_GET_KRB: + status = set_password(&message, retval); + break; + case UREG_SET_IDENT: + status = set_identity(&message, retval); + break; + case UREG_GET_SECURE: + status = get_secure(&message, retval); + break; + case UREG_SET_SECURE: + status = set_secure(&message, retval); + break; + default: + status = UREG_UNKNOWN_REQUEST; + critical_alert(FAIL_INST, "Unknown request %d from userreg.", + message.request); + break; } - - /* Report what happened to client */ - pktlen = sizeof(packet); - format_pkt(packet, &pktlen, seqno, status, errmsg); - sendto(s, packet, pktlen, 0, &sin, addrlen); + + /* Report what happened to client */ + report(status, retval); } } -int got_one; -int reg_status; -char *mit_id; -char *reg_misc; -int reg_misc_len; -int user_id; - -#define min(a,b) ((a)>(b)?(b):(a)) - -int validate_idno(message, db_mit_id, first, last) - struct msg *message; /* Formatted packet */ - char *db_mit_id; /* Encrypted MIT ID from SMS database */ - char *first, *last; /* First and last name for MIT ID encryption */ /* This routine makes sure that the ID from the database matches the ID sent accross in the packet. The information in the packet was created in the following way: - The plain text ID number was encrypted via crypt() resulting in - the form that would appear in the SMS database. This is - concatinated to the plain text ID so that the ID string contains - plain text ID followed by a null followed by the encrypted ID. - The whole thing is then DES encrypted using the encrypted ID as - the source of the key. + The database used to contain encrypted IDs. Now we don't encrypt + them in the database, although there are still some encrypted IDs + there. - This routine tries each encrypted ID in the database that belongs - to someone with this user's first and last name and tries to - decrypt the packet with this information. */ + The plain text ID number was encrypted via EncryptID() resulting + in the form that would appear in the Moira database. This is + concatenated to the plain text ID so that the ID string contains plain + text ID followed by a null followed by the encrypted ID. Other + information such as the username or password is appended. The whole + thing is then DES encrypted using the encrypted ID as the source of + the key. + + This routine tries each ID in the database that belongs + to someone with this user's first and last name and tries to + decrypt the packet with this information. If it succeeds, it returns + zero and initializes all the fields of the formatted packet structure + that depend on the encrypted information. */ + +int parse_encrypted(struct msg *message, struct db_data *data) { - C_Block key; /* The key for DES en/decryption */ - Key_schedule sched; /* En/decryption schedule */ - static char decrypt[BUFSIZ]; /* Buffer to hold decrypted information */ - long decrypt_len; /* Length of decypted ID information */ - char recrypt[14]; /* Buffer to hold re-encrypted information */ - static char hashid[14]; /* Buffer to hold one-way encrypted ID */ - char idnumber[BUFSIZ]; /* Buffer to hold plain-text ID */ - char *temp; /* A temporary storage buffer */ - int len; /* */ - - mit_id = 0; - /* Make the decrypted information length the same as the encrypted - information length. Both are integral multples of eight bytes - because of the DES encryption routines. */ - decrypt_len = (long)message->enc_mitid_len; - - /* Get key from the one-way encrypted ID in the SMS database */ - string_to_key(db_mit_id, key); - /* Get schedule from key */ - key_sched(key, sched); - /* Decrypt information from packet using this key. Since decrypt_len - is an integral multiple of eight bytes, it will probably be null- - padded. */ - pcbc_encrypt(message->enc_mitid, decrypt, \ - decrypt_len, sched, key, DECRYPT); - - /* Extract the plain text and encrypted ID fields from the decrypted - packet information. */ - /* Since the decrypted information starts with the plain-text ID - followed by a null, if the decryption worked, this will only - copy the plain text part of the decrypted information. It is - important that strncpy be used because if we are not using the - correct key, there is no guarantee that a null will occur - anywhere in the string. */ - (void) strncpy(idnumber, decrypt, decrypt_len); - /* Point temp to the end of the plain text ID number. */ - temp = decrypt + strlen(idnumber) + 1; - /* Find out how much more room there is. */ - len = message->enc_mitid_len - (temp - decrypt); - /* Copy the next 14 bytes of the decrypted information into - hashid if there are 14 more bytes to copy. There will be - if we have the right key. */ - (void) strncpy(hashid, temp, min(len, 14)); - /* Point temp to the end of the encrypted ID field */ - temp += strlen(hashid) + 1; - /* Find out how much more room there is. */ - len = message->enc_mitid_len - (temp - decrypt); - - /* Now compare encrypted ID's returning with an error if they - don't match. */ - if (strcmp(hashid, db_mit_id)) return 1; - encrypt_mitid(recrypt, idnumber, first, last); - /* Now compare encrypted plain text to ID from database. */ - if (strcmp(recrypt, db_mit_id)) return 1; - - /* We made it. */ - reg_misc = temp; - reg_misc_len = len; - mit_id = hashid; - return 0; -} + des_cblock key; /* The key for DES en/decryption */ + des_key_schedule sched; /* En/decryption schedule */ + static char decrypt[BUFSIZ]; /* Buffer to hold decrypted information */ + long decrypt_len; /* Length of decypted ID information */ + static char hashid[14]; /* Buffer to hold one-way encrypted ID */ + char idnumber[BUFSIZ]; /* Buffer to hold plain-text ID */ + char *temp; /* A temporary string pointer */ + int len; /* Keeps track of length left in packet */ + int status = SUCCESS; /* Error status */ + + /* Make the decrypted information length the same as the encrypted + information length. Both are integral multples of eight bytes + because of the DES encryption routines. */ + decrypt_len = message->encrypted_len; -static int status_in_db; + /* Get key from the possibly one-way encrypted ID in the Moira database */ + if (data->mit_id[0] >= '0' && data->mit_id[0] <= '9') + { + char buf[32]; + + EncryptID(buf, data->mit_id, message->first, message->last); + des_string_to_key(buf, key); + } + else + des_string_to_key(data->mit_id, key); -vfy_callbk(argc, argv, p_message) - int argc; /* Should sanity check this.. */ - char **argv; - char *p_message; + /* Get schedule from key */ + des_key_sched(key, sched); + /* Decrypt information from packet using this key. Since decrypt_len + is an integral multiple of eight bytes, it will probably be null- + padded. */ + des_pcbc_encrypt(message->encrypted, decrypt, decrypt_len, + sched, key, DES_DECRYPT); + + /* Extract the plain text and encrypted ID fields from the decrypted + packet information. */ + /* Since the decrypted information starts with the plain-text ID + followed by a null, if the decryption worked, this will only + copy the plain text part of the decrypted information. It is + important that strncpy be used because if we are not using the + correct key, there is no guarantee that a null will occur + anywhere in the string. */ + strncpy(idnumber, decrypt, decrypt_len); + /* Check that the idnumber of a mismatched decryption doesn't overflow + * the buffer. */ + if (strlen(idnumber) != 9) + return FAILURE; + + /* Point temp to the end of the plain text ID number. */ + temp = decrypt + strlen(idnumber) + 1; + /* Find out how much more packet there is. */ + len = message->encrypted_len - (temp - decrypt); + /* Copy the next CRYPT_LEN bytes of the decrypted information into + hashid if there are CRYPT_LEN more bytes to copy. There will be + if we have the right key. */ + strncpy(hashid, temp, min(len, CRYPT_LEN)); + /* Point temp to the end of the encrypted ID field */ + temp += strlen(hashid) + 1; + /* Find out how much more room there is. */ + len = message->encrypted_len - (temp - decrypt); + + /* Now compare encrypted ID and clear text ID for a match. */ + if (strcmp(hashid, data->mit_id) && + strcmp(idnumber, data->mit_id)) + status = FAILURE; + + if (status == SUCCESS) + { + /* We made it. Now we can finish initializing message. */ + /* Point leftover to whatever is left over! */ + message->leftover = temp; + message->leftover_len = len; + /* Since we know we have the right user, fill in the information + from the Moira database. */ + message->db.reg_status = data->reg_status; + strncpy(message->db.uid, data->uid, sizeof(message->db.uid)); + strncpy(message->db.mit_id, data->mit_id, sizeof(message->db.mit_id)); + strncpy(message->db.login, data->login, sizeof(message->db.login)); + } + + return status; +} + +/* This function is called by mr_query after each tuple found. It is + used by find_user to cache information about each user found. */ +int db_callproc(int argc, char **argv, void*queue) { - struct msg *message = (struct msg *)p_message; - char *db_mit_id; - char *firstname, *lastname; - int status; - - if (got_one) return 0; - reg_status = 0; - - db_mit_id = argv[8]; - firstname = argv[5]; - lastname = argv[4]; - - status = validate_idno(message, db_mit_id, firstname, lastname); - if (status) return 0; /* Nope; decryption failed */ - - status_in_db = atoi(argv[7]); - reg_status = status_in_db; - - if (status_in_db != 0) - { - (void) strcpy(errmsg, argv[0]); - } - user_id = atoi(argv[1]); - got_one = 1; - return 0; -} - -encrypt_mitid(buf, idnumber, first, last) - char *buf, *idnumber, *first, *last; + struct db_data *data; /* Structure to store the information in */ + int status = SUCCESS; /* Error status */ + + if (argc != U_END) + { + critical_alert(FAIL_INST, "Wrong number of arguments returned " + "from get_user_account_by_name."); + status = MR_ABORT; + } + else + { + /* extract the needed information from the results of the Moira query */ + data = malloc(sizeof(struct db_data)); + data->reg_status = atoi(argv[U_STATE]); + strncpy(data->login, argv[U_NAME], sizeof(data->login)); + strncpy(data->mit_id, argv[U_MITID], sizeof(data->mit_id)); + strncpy(data->uid, argv[U_UID], sizeof(data->uid)); + sq_save_data(queue, data); + } + + return status; +} + +/* This routine verifies that a user is allowed to register by finding + him/her in the Moira database. It returns the status of the Moira + query that it calls. */ +int find_user(struct msg *message) { - char salt[3]; - extern char *crypt(); - -#define _tolower(c) ((c)|0x60) - - salt[0] = _tolower(last[0]); - salt[1] = _tolower(first[0]); - salt[2] = 0; - - (void) strcpy(buf, crypt(&idnumber[2], salt)); +#define GUBN_ARGS 2 /* Arguements needed by get_user_by_name */ + char *q_name; /* Name of Moira query */ + int q_argc; /* Number of arguments for query */ + char *q_argv[GUBN_ARGS]; /* Arguments to query */ + int status = SUCCESS; /* Query return status */ + + struct save_queue *queue; /* Queue to hold Moira data */ + struct db_data *data; /* Structure for data for one tuple */ + short verified = FALSE; /* Have we verified the user? */ + + /* Zero the mit_id field in the formatted packet structure. This + being zeroed means that no user was found. */ + memset(message->db.mit_id, 0, sizeof(message->db.mit_id)); + + if (status == SUCCESS) + { + /* Get ready to make a Moira query */ + q_name = "get_user_account_by_name"; + q_argc = GUBN_ARGS; /* #defined in this routine */ + q_argv[0] = message->first; + q_argv[1] = message->last; + + /* Create queue to hold information */ + queue = sq_create(); + + /* Do it */ + status = mr_query(q_name, q_argc, q_argv, db_callproc, queue); + + if (status == MR_SUCCESS) + { + /* Traverse the list, freeing data as we go. If sq_get_data() + returns zero if there is no more data on the queue. */ + while (sq_get_data(queue, &data)) + { + if (!verified) + /* parse_encrypted returns zero on success */ + verified = (parse_encrypted(message, data) == SUCCESS); + free(data); + } + } + + /* Destroy the queue */ + sq_destroy(queue); + } + + return status; } -int verify_user(message) - struct msg *message; +/* This routine determines whether a user is in the databse and returns + his state so that other routines can figure out whether he is the + correct state for various transactions. */ +int verify_user(struct msg *message, char *retval) { - char *argv[3]; - int status; - - com_err(WHOAMI, 0, " verify_user %s %s\n", - message->first, message->last); - argv[0] = "get_user_by_first_and_last"; - argv[1] = message->first; - argv[2] = message->last; - got_one = 0; - - status = sms_query_internal(3, argv, vfy_callbk, (char *)message); - - if (status == SMS_NO_MATCH) status = UREG_USER_NOT_FOUND; - if (!got_one && !status) + int status = SUCCESS; /* Return status */ + + /* Log that we are about to veryify user */ + com_err(whoami, 0, "verifying user %s %s", message->first, message->last); + + /* Figure out what user (if any) can be found based on the + encrypted information in the packet. (See the comment on + parse_encrypted().) */ + + status = find_user(message); + + /* If Moira coudn't find the user */ + if (status == MR_NO_MATCH) + status = UREG_USER_NOT_FOUND; + else if (status == MR_SUCCESS) + { + /* If the information sent over in the packet did not point to a + valid user, the mit_id field in the formatted packet structure + will be empty. */ + if (message->db.mit_id[0] == '\0') status = UREG_USER_NOT_FOUND; - - if (status != SMS_SUCCESS) goto punt; - - if (reg_status == 1) status = UREG_ALREADY_REGISTERED; - if (reg_status == 2) status = UREG_NO_PASSWD_YET; - - punt: - return status; + /* If the user was found but the registration has already started, + use this as the status */ + else + { + switch (message->db.reg_status) + { + case US_NO_LOGIN_YET: + status = SUCCESS; + break; + case US_REGISTERED: + status = UREG_ALREADY_REGISTERED; + break; + case US_NO_PASSWD: + status = UREG_NO_PASSWD_YET; + break; + case US_DELETED: + status = UREG_DELETED; + break; + case US_NOT_ALLOWED: + status = UREG_NOT_ALLOWED; + break; + case US_ENROLLED: + status = UREG_ENROLLED; + break; + case US_ENROLL_NOT_ALLOWED: + status = UREG_ENROLL_NOT_ALLOWED; + break; + case US_HALF_ENROLLED: + status = UREG_HALF_ENROLLED; + break; + default: + status = UREG_MISC_ERROR; + critical_alert(FAIL_INST, "Bad user state %d for login %s.", + message->db.reg_status, message->db.login); + break; + } + /* Set retval to the login name so that the client can use + it in the error message it will give the user. */ + strcpy(retval, message->db.login); + } + } + + if (status) + com_err(whoami, status, " returned from verify_user"); + else + com_err(whoami, 0, "User verified"); + + return status; } -reserve_user(message) - struct msg *message; +int ureg_kadm_init(void) { - char *argv[3]; - int status; - int i; - char *login; - char uid_buf[20]; - char realm[REALM_SZ]; - - com_err(WHOAMI, 0, " reserve_user %s %s\n", - message->first, message->last); - - argv[0] = "gufl"; /* get_user_by_first_and_last */ - argv[1] = message->first; - argv[2] = message->last; - got_one = 0; - - status = sms_query_internal(3, argv, vfy_callbk, (char *)message); - - if (status == SMS_NO_MATCH) status = UREG_USER_NOT_FOUND; - if (!got_one && !status) - status = UREG_USER_NOT_FOUND; - - if (status != SMS_SUCCESS) goto punt; - if (reg_status != 0) + unsigned int status = SUCCESS; /* Return status */ + static char krbrealm[REALM_SZ]; /* kerberos realm name */ + static char *host; /* local hostname in principal fmt */ + static int inited = 0; + char *p; + struct utsname uts; + + if (!inited) { - status = UREG_ALREADY_REGISTERED; - goto punt; + inited++; + memset(krbrealm, 0, sizeof(krbrealm)); + if ((status = krb_get_lrealm(krbrealm, 1))) + { + status += krb_err_base; + com_err(whoami, status, " fetching kerberos realm"); + exit(1); + } + if (uname(&uts) < 0) + com_err(whoami, errno, "getting local hostname"); + host = canonicalize_hostname(strdup(uts.nodename)); + for (p = host; *p && *p != '.'; p++) + { + if (isupper(*p)) + *p = tolower(*p); + } + *p = 0; } - /* - * He's made it past this phase already. - */ - if (status_in_db == 2) + + /* Get keys for interacting with Kerberos admin server. */ + /* principal, instance, realm, service, service instance, life, file */ + if ((status = krb_get_svc_in_tkt(MOIRA_SNAME, host, krbrealm, PWSERV_NAME, + KADM_SINST, 1, KEYFILE))) + status += krb_err_base; + + if (status != SUCCESS) + com_err(whoami, status, " while get admin tickets"); + else { - status = 0; - goto punt; + if ((status = kadm_init_link(PWSERV_NAME, KADM_SINST, krbrealm)) != + KADM_SUCCESS) + com_err(whoami, status, " while initializing kadmin connection"); } - - for (i = 0; i < reg_misc_len && reg_misc[i]; i++) + + return status; +} + +/* + * This routine reserves a principal in kerberos by setting up a + * principal with a random initial key. + */ +int reserve_krb(char *login) +{ + int status = SUCCESS; + Kadm_vals new; + des_cblock key; + u_long *lkey = (u_long *)key; + + if ((status = ureg_kadm_init()) == SUCCESS) { - if (!islower(reg_misc[i]) && !isdigit(reg_misc[i]) && - reg_misc[i] != '_' && reg_misc[i] != '.') + memset(&new, 0, sizeof(new)); + SET_FIELD(KADM_DESKEY, new.fields); + SET_FIELD(KADM_NAME, new.fields); + + des_random_key(key); + new.key_low = htonl(lkey[0]); + new.key_high = htonl(lkey[1]); + strcpy(new.name, login); + + com_err(whoami, 0, "Creating kerberos principal for %s", login); + status = kadm_add(&new); + if (status != KADM_SUCCESS) + com_err(whoami, status, " while reserving principal"); + + memset(&new, 0, sizeof(new)); + } + + dest_tkt(); + + return status; +} + +/* + * This routine reserves a principal in kerberos by setting up a + * principal with a random initial key. + */ +int setpass_krb(char *login, char *password) +{ + int status = SUCCESS; + Kadm_vals new; + des_cblock key; + u_long *lkey = (u_long *)key; + + if ((status = ureg_kadm_init()) == SUCCESS) + { + memset(&new, 0, sizeof(new)); + SET_FIELD(KADM_DESKEY, new.fields); + SET_FIELD(KADM_NAME, new.fields); + + des_string_to_key(password, key); + new.key_low = htonl(lkey[0]); + new.key_high = htonl(lkey[1]); + strcpy(new.name, login); + + com_err(whoami, 0, "Setting password for %s", login); + /* First arguement is not used if user has modify privileges */ + if ((status = kadm_mod(&new, &new)) != KADM_SUCCESS) { - status = UREG_INVALID_UNAME; - goto punt; + if (status == KADM_NOENTRY) + { + com_err(whoami, 0, "kerberos principal doesn't exist; creating"); + if ((status = kadm_add(&new)) != KADM_SUCCESS) + com_err(whoami, status, " while creating kerberos principal"); + } + else + com_err(whoami, status, " while setting password"); } } - if (i < 3 || i > 8) + + dest_tkt(); + return status; +} + +int reserve_user(struct msg *message, char *retval) +{ + int q_argc; /* Number of arguments to query */ + char *q_argv[3]; /* Arguments to Moira query */ + char *q_name; /* Name of Moira query */ + int status = SUCCESS; /* General purpose error status */ + char fstype_buf[7]; /* Buffer to hold fs_type, a 16 bit number */ + char *login; /* The login name the user wants */ + int i; /* A counter */ + + /* Log that we are about to reserve a user. */ + com_err(whoami, 0, "reserving user %s %s", message->first, message->last); + + /* Check to make sure that we can verify this user. */ + if ((status = verify_user(message, retval)) == SUCCESS) { + /* Get the requested login name from leftover packet information. */ + login = message->leftover; + + /* Check the login name for validity. The login name is currently + is allowed to contain lowercase letters in any position and + and numbers and underscore characters in any position but the + first. */ + if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME)) status = UREG_INVALID_UNAME; - goto punt; - } - login = reg_misc; - - /* Send request to kerberos admin_server for login name */ - /* get keys */ - if ((status = get_krbrlm(realm, 1)) != KSUCCESS) - { - status += krb_err_base; - goto punt; - } - status = get_svc_in_tkt("register", "sms", realm, - "changepw", "kerberos", - 1, "/etc/srvtab"); - if (status) - { - status += krb_err_base; - goto punt; - } - - /* send set password request to kerberos admin_server */ - (void) sprintf(uid_buf, "%013d", user_id); /* 13 chars of placebo */ - /* for backwards-compat. */ - - status = admin_call(ADMIN_ADD_NEW_KEY_ATTR, login, "", - "", uid_buf); - - if (status) - { - if (status == ADMIN_SERVER_ERROR) + } + if (status == SUCCESS) + if ((login[0] == '_') || isdigit(login[0])) + status = UREG_INVALID_UNAME; + + if (status == SUCCESS) + { + for (i = 0; i < strlen(login); i++) { - printf("Server error: %s\n", admin_errmsg); - - if (strcmp(admin_errmsg, - "Principal already in kerberos database.") ==0) - status = UREG_LOGIN_USED; + if (!islower(login[i]) && !isdigit(login[i]) && + (login[i] != '_')) + { + status = UREG_INVALID_UNAME; + break; + } } - goto punt; - } - - dest_tkt(); - /* If valid: */ - - /* Set login name */ - status = set_login(login, mit_id); - - - if (status) - { - com_err("set_login", status, (char *)0); - goto punt; - } - /* choose post office */ - - status = choose_pobox(login); - if (status) - { - com_err("choose_pobox", status, (char *)0); - goto punt; - } - /* create group */ - - status = create_group(login); - if (status == SMS_LIST) status = UREG_LOGIN_USED; - - if (status) - { - com_err("create_group", status, (char *)0); - goto punt; - } - /* set quota entry, create filsys */ - - status = alloc_filsys(login, SMS_FS_STUDENT, 0, 0); - if (status == SMS_FILESYS_EXISTS) status = UREG_LOGIN_USED; - if (status) - { - com_err("alloc_filsys", status, (char *)0); - goto punt; - } - /* set filsys and status in SMS database */ - - status = set_status_filsys(reg_misc, mit_id); - if (status) - { - com_err("set_filsys", status, (char *)0); - goto punt; - } - punt: - dest_tkt(); - - com_err(WHOAMI, status, " returned from reserve_user"); - return status; + } + if (status == SUCCESS) + { + /* Now that we have a valid user with a valid login... */ + + /* First, try to reserve the user in Moira. */ + sprintf(fstype_buf, "%d", MR_FS_STUDENT); + q_name = "register_user"; + q_argv[0] = message->db.uid; + q_argv[1] = login; + q_argv[2] = fstype_buf; + q_argc = 3; + status = mr_query(q_name, q_argc, q_argv, NULL, NULL); + switch (status) + { + case MR_SUCCESS: + status = SUCCESS; + break; + case MR_IN_USE: + status = UREG_LOGIN_USED; + break; + case MR_DEADLOCK: + status = UREG_MISC_ERROR; + break; + default: + critical_alert(FAIL_INST, "%s returned from register_user.", + error_message(status)); + status = UREG_MISC_ERROR; + break; + } + } + + if (status == SUCCESS) + { + /* + * Moira login was successfully created; try to reserve kerberos + * principal. + * + * If this routine fails, store the login in the retval so + * that it can be used in the client-side error message. + */ + if ((status = reserve_krb(login)) != SUCCESS) + strcpy(retval, login); + } + + if (status) + com_err(whoami, status, " returned from reserve_user"); + else + com_err(whoami, 0, "User reserved"); + + return status; } -set_password(message) - struct msg *message; +/* This routine updates a user's registration status to fully + registered. */ +int set_final_status(struct msg *message) { - char *argv[3]; - int status; - char uid_buf[10]; - char realm[REALM_SZ]; - - com_err(WHOAMI, 0, " set_password %s %s\n", - message->first, message->last); - - /* validate that user is who he claims to be */ - - argv[0] = "get_user_by_first_and_last"; - argv[1] = message->first; - argv[2] = message->last; - got_one = 0; - - status = sms_query_internal(3, argv, vfy_callbk, (char *)message); - - if (status == SMS_NO_MATCH) status = UREG_USER_NOT_FOUND; - if (!got_one && !status) - status = UREG_USER_NOT_FOUND; - - if (status != SMS_SUCCESS) goto punt; - - /* validate that state is equal to '2' (login, but no password) */ - - if (reg_status != 2) - { - status = UREG_NO_LOGIN_YET; - goto punt; - } - - /* get keys */ - if ((status = get_krbrlm(realm, 1)) != KSUCCESS) - { - goto punt; - } - status = get_svc_in_tkt("register", "sms", realm, - "changepw", "kerberos", - 1, "/etc/srvtab"); - if (status) - { - status += krb_err_base; - goto punt; - } - - (void) sprintf(uid_buf, "%013d", user_id); /* 13 chars of placebo */ - /* for backwards-compat. */ - /* send set password request to kerberos admin_server */ - /**####**/ - status = admin_call(ADMIN_ADD_NEW_KEY_ATTR, errmsg, "", - reg_misc, uid_buf); - - if (status) goto punt; - dest_tkt(); - /**#####**/ - status = set_final_status(errmsg, mit_id); - - /* reflect reply to client */ - punt: - dest_tkt(); - return status; + char *login; + char *q_name; /* Name of Moira query */ + int q_argc; /* Number of arguments for Moira query */ + char *q_argv[2]; /* Arguments to get user by uid */ + char state[7]; /* Can hold a 16 bit integer */ + int status; /* Error status */ + + if (message->request == UREG_SET_PASSWORD) + sprintf(state, "%d", US_REGISTERED); + else if (message->db.reg_status == US_NO_LOGIN_YET) + sprintf(state, "%d", US_ENROLLED); + else + sprintf(state, "%d", US_ENROLL_NOT_ALLOWED); + + login = message->db.login; + com_err(whoami, 0, "Setting final status for %s to %s", login, state); + + q_name = "update_user_status"; + q_argc = 2; + q_argv[0] = login; + q_argv[1] = state; + if ((status = mr_query(q_name, q_argc, q_argv, NULL, NULL)) + != MR_SUCCESS) + { + if (status == MR_DEADLOCK) + status = UREG_MISC_ERROR; + else + critical_alert(FAIL_INST, "%s returned from update_user_status.", + error_message(status)); + } + if (status) + com_err(whoami, status, " returned from set_final_status"); + else + com_err(whoami, 0, "Final status set"); + return status; } -parse_pkt(packet, pktlen, seqnop, messagep) - char *packet; - int pktlen; - u_long *seqnop; - struct msg *messagep; - /* This routine checks a packet and puts the information in it in - a structure if it is valid. */ + +/* This routine is used to set the initial password for the new user. */ +int set_password(struct msg *message, char *retval) { - if (pktlen < 4) return UREG_BROKEN_PACKET; - /* Extract the SMS version from the packet */ - bcopy(packet, (char *)&messagep->version, sizeof(long)); - /* Convert byte order from network to host */ - messagep->version = ntohl(messagep->version); - /* Verify version */ - if (messagep->version != CUR_SMS_VERSION) return UREG_WRONG_VERSION; - - packet += 4; - pktlen -= 4; - - if (pktlen < 4) return UREG_BROKEN_PACKET; - /* Extract the sequence number from the packet */ - bcopy(packet, (char *)seqnop, sizeof(long)); - - packet += 4; - pktlen -= 4; - - if (pktlen < 4) return UREG_BROKEN_PACKET; - /* Extract the request from the packet */ - bcopy(packet, (char *)(&messagep->request), sizeof(long)); - messagep->request = ntohl(messagep->request); - packet += 4; - pktlen -= 4; - - /* Extract first name from the packet */ - messagep->first = packet; - - /* Scan forward until null appears in the packet or there - is no more packet! */ - for (; *packet && pktlen > 0; --pktlen, ++packet) continue; - if (pktlen <= 0) return UREG_BROKEN_PACKET; - - /* Skip over the null */ - packet++, pktlen--; - - /* Extract last name from the packet */ - messagep->last = packet; - - for (; *packet && pktlen > 0; --pktlen, ++packet) continue; - if (pktlen <= 0) return UREG_BROKEN_PACKET; - - packet++, pktlen--; - - if (pktlen <= 0) return UREG_BROKEN_PACKET; - - /* Extract MIT id information from packet; see comment on - struct msg. */ - messagep->enc_mitid = packet; - messagep->enc_mitid_len = pktlen; - - return 0; + int status = SUCCESS; /* Return status */ + char *passwd; /* User's password */ + + com_err(whoami, 0, "setting password %s %s", message->first, message->last); + + status = verify_user(message, retval); + + /* Don't set the password unless the registration status of the user + is that he exists and has no password. */ + if (status == SUCCESS) + status = UREG_NO_LOGIN_YET; + if ((message->request == UREG_SET_PASSWORD && + status == UREG_NO_PASSWD_YET) || + (message->request == UREG_GET_KRB && + status == UREG_HALF_ENROLLED)) + { + /* User is in proper state for this transaction. */ + + passwd = message->leftover; + + /* Set password. */ + if ((status = setpass_krb(message->db.login, passwd)) != SUCCESS) + /* If failure, allow login name to be used in client error message */ + strcpy(retval, message->db.login); + else + /* Otherwise, mark user as finished. */ + status = set_final_status(message); + } + + if (status) + com_err(whoami, status, " returned from set_passwd"); + else + com_err(whoami, 0, "Password set"); + + return status; } -format_pkt(packet, pktlenp, seqno, status, message) - char *packet; - int *pktlenp; - u_long seqno; - int status; - char *message; - /* This routine prepares a packet to send back to the client. */ + +int getuserinfo(int argc, char **argv, void *qa) { - u_long vers = htonl((u_long)CUR_SMS_VERSION); - status = htonl((u_long)status); - - /* Put current SMS version into the packet */ - bcopy((char *)&vers, packet, sizeof(long)); - /* Put sequence number into the packet */ - bcopy((char *)&seqno, packet+sizeof(long), sizeof(long)); - /* Put error status into the packet */ - bcopy((char *)&status, packet+ 2*sizeof(long), sizeof(long)); - *pktlenp = sizeof(long) * 3; - /* Copy the message into the packet */ - (void) strcpy(packet+3*sizeof(long), message); - (*pktlenp) += strlen(message); + char **qargv = qa; + int status = SUCCESS; + int i; + + if (argc != U_END) + { + critical_alert(FAIL_INST, + "Wrong number of args returned from get_user_by_uid"); + status = MR_ABORT; + } + else + { + qargv[U_NAME] = strdup(argv[U_NAME]); + for (i = 1; i < U_MODTIME; i++) + qargv[i + 1] = strdup(argv[i]); + qargv[U_MODTIME + 1] = NULL; + } + return status; } -store_user(argc, argv, argp) - int argc; - char **argv; - char *argp; + +int set_identity(struct msg *message, char *retval) { - char **retv = (char **) argp; - int i; - - for (i = 0; i < argc; i++) + char *q_argv[U_END]; /* Arguments to Moira query */ + int status = SUCCESS; /* General purpose error status */ + char *login; /* The login name the user wants */ + int i; /* A counter */ + + /* Log that we are about to reserve a user. */ + com_err(whoami, 0, "setting identity %s %s", + message->first, message->last); + + /* Check to make sure that we can verify this user. */ + status = verify_user(message, retval); + if (status == SUCCESS || status == UREG_NOT_ALLOWED) + { + status = SUCCESS; + /* Get the requested login name from leftover packet information. */ + login = message->leftover; + + /* Check the login name for validity. The login name is currently + is allowed to contain lowercase letters in any position and + and numbers and underscore characters in any position but the + first. */ + if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME)) + status = UREG_INVALID_UNAME; + } + if (status == SUCCESS) + { + if ((login[0] == '_') || isdigit(login[0])) + status = UREG_INVALID_UNAME; + } + if (status == SUCCESS) + { + for (i = 0; i < strlen(login); i++) + { + if (!islower(login[i]) && !isdigit(login[i]) && + (login[i] != '_')) + { + status = UREG_INVALID_UNAME; + break; + } + } + } + if (status == SUCCESS) { - if (retv[i]) + /* Now that we have a valid user with a valid login... */ + + q_argv[0] = message->db.uid; + status = mr_query("get_user_account_by_uid", 1, q_argv, + getuserinfo, q_argv); + if (status != SUCCESS) + { + com_err(whoami, status, " while getting user info"); + return status; + } + q_argv[U_NAME + 1] = login; + q_argv[U_STATE + 1] = "7"; + q_argv[U_SIGNATURE + 1] = ""; + status = mr_query("update_user_account", U_MODTIME + 1, q_argv, + NULL, NULL); + switch (status) { - free(retv[i]); - retv[i]=0; + case MR_SUCCESS: + status = SUCCESS; + break; + case MR_IN_USE: + case MR_NOT_UNIQUE: + status = UREG_LOGIN_USED; + break; + case MR_DEADLOCK: + status = UREG_MISC_ERROR; + break; + default: + critical_alert(FAIL_INST, "%s returned from update_user_account.", + error_message(status)); + status = UREG_MISC_ERROR; + break; } - retv[i] = strdup(argv[i]); } - return 0; + if (status == SUCCESS) + { + /* Moira login was successfully created; try to reserve kerberos + principal. */ + /* If this routine fails, store the login in the retval so + that it can be used in the client-side error message. */ + if ((status = reserve_krb(login)) != SUCCESS) + strcpy(retval, login); + } + + if (status) + com_err(whoami, status, " returned from set_identity"); + else + com_err(whoami, 0, "Identity set"); + + return status; } -/* - * Set login name of user with "idnumber" to be "username" +/* Find out if someone's secure instance password is set. + * Returns UREG_ALREADY_REGISTERED if set, SUCCESS (0) if not. */ -set_login(username, idnumber) - char *username; - char *idnumber; +int get_secure(struct msg *message, char *retval) { - char *argv[2]; - int status, i; - char *retv[13]; - - argv[0] = "get_user_by_mitid"; - argv[1] = idnumber; - - for (i=0; i<13; i++) - { - retv[i] = 0; - } - - status = sms_query_internal(2, argv, store_user, (char *)(retv+1)); - if (status) return status; - - retv[0] = retv[1]; - retv[1] = username; - if (retv[4]) free(retv[4]); - retv[4] = "null"; /* No such filesystem */ - - printf("Update_user(%s, %s)\n", retv[0], retv[1]); - - status = sms_query("update_user", 12, retv, abort, 0); - retv[1] = 0; - retv[4] = 0; - - for (i=1; i<12; i++) - { - if (retv[i]) free(retv[i]); - retv[i] = 0; - } - - return status; -} + int status; + char *argv[U_END]; -/* - * Set the status and filsys of user with username "uname" and filesys filsys. - */ + com_err(whoami, 0, "checking status of secure password for %s", + message->first); + argv[0] = message->first; + status = mr_query("get_user_account_by_login", 1, argv, getuserinfo, argv); + if (status != SUCCESS) + { + com_err(whoami, status, " while getting user info"); + return status; + } + if (atoi(argv[U_SECURE + 1])) + return UREG_ALREADY_REGISTERED; + return SUCCESS; +} + + +/* Set someone's secure instance password. */ -set_status_filsys(username, idnumber) - char *username; - char *idnumber; +int set_secure(struct msg *message, char *retval) { - char *argv[2]; - int status, i; - char *retv[13]; - - argv[0] = "get_user_by_mitid"; - argv[1] = idnumber; - - for (i=0; i<13; i++) - { - retv[i] = 0; - } - - status = sms_query_internal(2, argv, store_user, (char *)(retv+1)); - if (status) return status; - - retv[0] = retv[1]; - - free(retv[4]); - retv[4] = username; - - free(retv[8]); - retv[8] = "2"; - - printf("Update_user(%s, %s)\n", retv[0], retv[1]); - - status = sms_query("update_user", 12, retv, abort, 0); - retv[4] = 0; - retv[8] = 0; - for (i=1; i<12; i++) - { - if (retv[i]) free(retv[i]); - retv[i] = 0; + int status; + char *argv[U_END], *bp, buf[512], *passwd, *id; + KTEXT_ST creds; + AUTH_DAT auth; + C_Block key; + Key_schedule keys; + Kadm_vals kv; + u_long *lkey = (u_long *)key; + struct timeval now; + static int inited = 0; + static char *host; + struct utsname uts; + + if (!inited) + { + inited++; + if (uname(&uts) < 0) + com_err(whoami, errno, "getting local hostname"); + host = strdup(krb_get_phost(uts.nodename)); } - return status; -} -/* - * Set the status and filsys of user with username "uname" and filesys filsys. - */ -set_final_status(username, idnumber) - char *username; - char *idnumber; -{ - char *argv[2]; - int status, i; - char *retv[13]; - - argv[0] = "get_user_by_mitid"; - argv[1] = idnumber; - - for (i=0; i<13; i++) - { - retv[i] = 0; - } - - status = sms_query_internal(2, argv, store_user, (char *)(retv+1)); - if (status) return status; - - retv[0] = retv[1]; - - free(retv[8]); - retv[8] = "1"; - - printf("Update_user(%s, %s)\n", retv[0], retv[1]); - - status = sms_query("update_user", 12, retv, abort, 0); - retv[8] = 0; - for (i=1; i<12; i++) - { - if (retv[i]) free(retv[i]); - retv[i] = 0; + com_err(whoami, 0, "setting secure passwd for %s", message->first); + argv[0] = message->first; + status = mr_query("get_user_account_by_login", 1, argv, getuserinfo, argv); + if (status != SUCCESS) + { + com_err(whoami, status, " while getting user info"); + return status; + } + if (atoi(argv[U_SECURE + 1])) + { + com_err(whoami, UREG_ALREADY_REGISTERED, "in set_secure()"); + return UREG_ALREADY_REGISTERED; } - return status; -} -create_group(login) - char *login; -{ - int status; - static char *cr[] = - { - "add_user_group", - 0, - }; - - cr[1] = login; - - return sms_query_internal(2, cr, abort, 0); -} + bp = message->encrypted; + /* round up to word boundary */ + bp = (char *)((((u_long)bp + 3) >> 2) << 2); -/* - * Local Variables: - * mode: c - * c-argdecl-indent: 2 - * c-brace-offset: -4 - * c-continued-statement-offset: 2 - * c-indent-level: 4 - * c-label-offset: -2 - * End: - */ + creds.length = ntohl(*((int *)bp)); + bp += sizeof(int); + memcpy(creds.dat, bp, creds.length); + creds.mbz = 0; + bp += creds.length; + + status = krb_rd_req(&creds, "changepw", host, cur_req_sender(), &auth, ""); + if (status) + { + status += krb_err_base; + com_err(whoami, status, " verifying credentials in set_secure()"); + return status; + } + + message->leftover_len = ntohl(*((int *)(bp))); + bp += sizeof(int); + message->leftover = bp; + + des_key_sched(auth.session, keys); + des_pcbc_encrypt(message->leftover, buf, message->leftover_len, + keys, auth.session, 0); + + id = buf; + passwd = strchr(buf, ','); + *passwd++ = 0; + + if (strcmp(id, argv[U_MITID + 1])) + { + char buf[32]; + + EncryptID(buf, id, argv[U_FIRST + 1], argv[U_LAST + 1]); + if (strcmp(buf, argv[U_MITID + 1])) + { + status = UREG_USER_NOT_FOUND; + com_err(whoami, status, "IDs mismatch: %s (%s), %s", id, buf, + argv[U_MITID + 1]); + return status; + } + } + + /* now do actual password setting stuff */ + + if ((status = ureg_kadm_init()) != SUCCESS) + { + com_err(whoami, status, "initing kadm stuff"); + return status; + } + + memset(&kv, 0, sizeof(kv)); + SET_FIELD(KADM_DESKEY, kv.fields); + SET_FIELD(KADM_NAME, kv.fields); + SET_FIELD(KADM_INST, kv.fields); + des_string_to_key(passwd, key); + kv.key_low = htonl(lkey[0]); + kv.key_high = htonl(lkey[1]); + strcpy(kv.name, message->first); + strcpy(kv.instance, "extra"); + + if ((status = kadm_add(&kv)) != KADM_SUCCESS) + { + com_err(whoami, status, " while creating kerberos principal"); + return status; + } + + argv[0] = message->first; + argv[1] = buf; + gettimeofday(&now, NULL); + sprintf(buf, "%ld", now.tv_sec); + status = mr_query("update_user_security_status", 2, argv, getuserinfo, argv); + if (status != SUCCESS) + { + com_err(whoami, status, " while updating user status"); + return status; + } + return SUCCESS; +}