]> andersk Git - moira.git/blob - reg_svr/reg_svr.c
changed kerberos routine names for new libraries
[moira.git] / reg_svr / reg_svr.c
1 /*
2  *      $Source$
3  *      $Author$
4  *      $Header$
5  *
6  *      Copyright (C) 1987, 1988 by the Massachusetts Institute of Technology
7  *      For copying and distribution information, please see the file
8  *      <mit-copyright.h>.
9  *
10  *      Server for user registration with SMS and Kerberos.
11  *
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.
16  */
17
18 #ifndef lint
19 static char *rcsid_reg_svr_c = "$Header$";
20 #endif lint
21
22 #include <mit-copyright.h>
23 #include "reg_svr.h"
24 #include "admin_server.h"
25 #include "admin_err.h"
26 #include "krb_et.h"
27
28 extern char admin_errmsg[];
29
30 static char krbhst[BUFSIZ];     /* kerberos server name */
31 static char krbrealm[REALM_SZ]; /* kerberos realm name */
32
33 main(argc,argv)
34   int argc;
35   char *argv[];
36 {
37     struct msg message;         /* Storage for parsed packet */
38     int status = SUCCESS;       /* Error status */
39     char retval[BUFSIZ];        /* Buffer to hold return message for client */
40     
41     void req_initialize();      /* Initialize request layer */
42     void get_request();         /* Get a request */
43     void report();              /* Respond to a request */
44
45     /* Initialize */
46     whoami = argv[0];
47     
48     /* Use com_err or output to stderr for all log messages. */    
49 #ifdef DEBUG
50     fprintf(stderr,"*** Debugging messages enabled. ***\n");
51 #endif DEBUG
52     
53     /* Error messages sent one line at a time */
54     setlinebuf(stderr);
55     
56     /* Initialize user registration error table for com_err */
57     init_ureg_err_tbl();
58     
59     /* Connect to the SMS server */
60     if ((status = sms_connect(SMS_SERVER)) != SMS_SUCCESS) 
61     {
62         com_err(whoami, status, " on connect");
63         exit(1);
64     }
65     
66     /* Authorize, telling the server who you are */
67     if ((status = sms_auth(whoami)) != SMS_SUCCESS) 
68     {
69         com_err(whoami, status, " on auth");
70         exit(1);
71     }
72     
73     if (status = krb_get_lrealm(krbrealm, 1)) {
74         status += ERROR_TABLE_BASE_krb;
75         com_err(whoami, status, " fetching kerberos realm");
76         exit(1);
77     }
78     
79     if (status = krb_get_krbhst(krbhst, krbrealm, 1)) {
80         status += ERROR_TABLE_BASE_krb;
81         com_err(whoami, status, " fetching kerberos hostname");
82         exit(1);
83     } else {
84         char *s;
85         for (s = krbhst; *s && *s != '.'; s++)
86             if (isupper(*s))
87                 *s = tolower(*s);
88         *s = 0;
89     }
90
91     journal = fopen(JOURNAL, "a");
92     if (journal == NULL) {
93         com_err(whoami, errno, " while opening journal file");
94         exit(1);
95     }
96     
97     /* Allow request layer to initialize */
98     req_initialize();
99     
100     /* Sit around waiting for requests from the client. */
101     for (;;) 
102     {
103         get_request(&message);
104         
105         switch((int)message.request) 
106         {
107           case UREG_VERIFY_USER:
108             status = verify_user(&message,retval);
109             break;
110           case UREG_RESERVE_LOGIN:
111             status = reserve_user(&message,retval);
112             break;
113           case UREG_SET_PASSWORD:
114             status = set_password(&message,retval);
115             break;
116             
117           default:
118             status = UREG_UNKNOWN_REQUEST;
119             critical_alert(FAIL_INST,"Unknown request %d from userreg.",
120                            message.request);
121             break;
122         }
123         
124         /* Report what happened to client */
125         report(status, retval);
126     }
127 }
128
129 /* This is necessary so that this server can know where to put its
130    tickets. */
131 char *tkt_string()
132 {
133     return("/tmp/tkt_ureg");
134 }
135
136 int parse_encrypted(message,data)
137   struct msg *message;          /* Formatted packet */
138   struct db_data *data;         /* Data from the SMS database */
139 /* This routine makes sure that the ID from the database matches
140    the ID sent accross in the packet.  The information in the packet
141    was created in the following way:
142
143    The plain text ID number was encrypted via EncryptID() resulting
144    in the form that would appear in the SMS database.  This is
145    concatinated to the plain text ID so that the ID string contains plain
146    text ID followed by a null followed by the encrypted ID.  Other
147    information such as the username or password is appended.  The whole
148    thing is then DES encrypted using the encrypted ID as the source of
149    the key.
150
151    This routine tries each encrypted ID in the database that belongs
152    to someone with this user's first and last name and tries to 
153    decrypt the packet with this information.  If it succeeds, it returns 
154    zero and initializes all the fields of the formatted packet structure
155    that depend on the encrypted information. */
156 {
157     C_Block key;                /* The key for DES en/decryption */
158     Key_schedule sched;         /* En/decryption schedule */
159     static char decrypt[BUFSIZ];   /* Buffer to hold decrypted information */
160     long decrypt_len;           /* Length of decypted ID information */
161     char recrypt[14];           /* Buffer to hold re-encrypted information */
162     static char hashid[14];     /* Buffer to hold one-way encrypted ID */
163     char idnumber[BUFSIZ];      /* Buffer to hold plain-text ID */
164     char *temp;                 /* A temporary string pointer */
165     int len;                    /* Keeps track of length left in packet */
166     int status = SUCCESS;       /* Error status */
167     
168 #ifdef DEBUG
169     com_err(whoami,0,"Entering parse_encrypted");
170 #endif
171
172     /* Make the decrypted information length the same as the encrypted
173        information length.  Both are integral multples of eight bytes 
174        because of the DES encryption routines. */
175     decrypt_len = (long)message->encrypted_len;
176     
177     /* Get key from the one-way encrypted ID in the SMS database */
178     string_to_key(data->mit_id, key);
179     /* Get schedule from key */
180     key_sched(key, sched);
181     /* Decrypt information from packet using this key.  Since decrypt_len
182        is an integral multiple of eight bytes, it will probably be null-
183        padded. */
184     pcbc_encrypt(message->encrypted,decrypt, decrypt_len, sched, key, DECRYPT);
185     
186     /* Extract the plain text and encrypted ID fields from the decrypted
187        packet information. */
188     /* Since the decrypted information starts with the plain-text ID
189        followed by a null, if the decryption worked, this will only 
190        copy the plain text part of the decrypted information.  It is
191        important that strncpy be used because if we are not using the
192        correct key, there is no guarantee that a null will occur
193        anywhere in the string. */
194     (void) strncpy(idnumber,decrypt,(int)decrypt_len);
195     /* Point temp to the end of the plain text ID number. */
196     temp = decrypt + strlen(idnumber) + 1;
197     /* Find out how much more packet there is. */
198     len = message->encrypted_len - (temp - decrypt);
199     /* Copy the next CRYPT_LEN bytes of the decrypted information into 
200        hashid if there are CRYPT_LEN more bytes to copy.  There will be
201        if we have the right key. */
202     (void) strncpy(hashid, temp, min(len, CRYPT_LEN));
203     /* Point temp to the end of the encrypted ID field */
204     temp += strlen(hashid) + 1;
205     /* Find out how much more room there is. */
206     len = message->encrypted_len - (temp - decrypt);
207     
208     /* Now compare encrypted ID's don't match. */
209     if (strcmp(hashid, data->mit_id)) status = FAILURE;
210     if (status == SUCCESS)
211     {
212         EncryptID(recrypt, idnumber, message->first, message->last);
213         /* Now compare encrypted plain text to ID from database. */
214         if (strcmp(recrypt, data->mit_id)) status = FAILURE;
215     }
216     
217     if (status == SUCCESS)
218     {
219         /* We made it.  Now we can finish initializing message. */
220         /* Point leftover to whatever is left over! */
221         message->leftover = temp;
222         message->leftover_len = len;
223         /* Since we know we have the right user, fill in the information 
224            from the SMS database. */
225         message->db.reg_status = data->reg_status;
226         (void) strncpy(message->db.uid,data->uid, sizeof(message->db.uid));
227         (void) strncpy(message->db.mit_id,data->mit_id, 
228                        sizeof(message->db.mit_id));
229         (void) strncpy(message->db.login,data->login, sizeof(message->db.login));
230     }
231     
232 #ifdef DEBUG
233     if (status)
234         com_err(whoami,status," parse_encrypted failed.");
235     else
236         com_err(whoami,status,"parse_encrypted succeeded.");
237 #endif
238
239     return status;
240 }
241
242 int db_callproc(argc,argv,queue)
243   int argc;                     /* Number of arguments returned by SMS */
244   char *argv[];                 /* Arguments returned by SMS */
245   struct save_queue *queue;     /* Queue to save information in */
246 /* This function is called by sms_query after each tuple found.  It is
247    used by find_user to cache information about each user found.  */
248 {
249     struct db_data *data;       /* Structure to store the information in */
250     int status = SUCCESS;       /* Error status */
251     
252 #ifdef DEBUG
253     com_err(whoami,0,"Entering db_callproc.");
254 #endif
255
256     if (argc != U_END)
257     {
258         critical_alert
259             (FAIL_INST,
260              "Wrong number of arguments returned from get_user_by_name.");
261         status = SMS_ABORT;
262     }
263     else
264     {
265         /* extract the needed information from the results of the SMS query */
266         data = (struct db_data *)malloc(sizeof(struct db_data));
267         data->reg_status = atoi(argv[U_STATE]);
268         (void) strncpy(data->login,argv[U_NAME],sizeof(data->login));
269         (void) strncpy(data->mit_id,argv[U_MITID],sizeof(data->mit_id));
270         (void) strncpy(data->uid,argv[U_UID],sizeof(data->uid));
271 #ifdef DEBUG
272         fprintf(stderr,"Found in database:\n");
273         fprintf(stderr,"   Registration status: %d\n",data->reg_status);
274         fprintf(stderr,"   login: %s\n",data->login);
275         fprintf(stderr,"   MIT ID: %s\n",data->mit_id);
276         fprintf(stderr,"   uid: %s\n",data->uid);
277 #endif
278         sq_save_data(queue,data);
279     }
280
281     return status;
282 }
283     
284 int find_user(message)
285   struct msg *message;          /* Formatted packet structure */
286 /* This routine verifies that a user is allowed to register by finding
287    him/her in the SMS database.  It returns the status of the SMS
288    query that it calls. */
289 {
290 #define GUBN_ARGS 2             /* Arguements needed by get_user_by_name */
291     char *q_name;               /* Name of query */
292     int q_argc;                 /* Number of arguments for query */
293     char *q_argv[GUBN_ARGS];    /* Arguments to query */
294     int status = SUCCESS;       /* Query return status */
295
296     struct save_queue *queue;   /* Queue to hold SMS data */
297     struct db_data *data;       /* Structure for data for one tuple */
298     short verified = FALSE;     /* Have we verified the user? */
299
300     /* Zero the mit_id field in the formatted packet structure.  This
301        being zeroed means that no user was found. */
302     bzero(message->db.mit_id,sizeof(message->db.mit_id));
303     
304     if (status == SUCCESS)
305     {
306         /* Get ready to make an SMS query */
307         q_name = "get_user_by_name";
308         q_argc = GUBN_ARGS;     /* #defined in this routine */
309         q_argv[0] = message->first;
310         q_argv[1] = message->last;
311         
312         /* Create queue to hold information */
313         queue = sq_create();
314         
315         /* Do it */
316         status = sms_query(q_name,q_argc,q_argv,db_callproc,(char *)queue);
317         
318 #ifdef DEBUG
319         fprintf(stderr," %d returned by get_user_by_name\n",status);
320 #endif
321         
322         if (status == SMS_SUCCESS) 
323         {
324             /* Traverse the list, freeing data as we go.  If sq_get_data()
325                returns zero if there is no more data on the queue. */
326             while (sq_get_data(queue,&data))
327             {
328                 if (!verified)
329                     /* parse_encrypted returns zero on success */
330                     verified = (parse_encrypted(message,data) == SUCCESS);
331                 free((char *)data);
332             }
333         }
334
335         /* Destroy the queue */
336         sq_destroy(queue);
337     }
338     
339 #ifdef DEBUG
340     fprintf(stderr,"Returned from find_user\n");
341     fprintf(stderr,"   MIT ID: %s\n", message->db.mit_id);
342     fprintf(stderr,"   Registration status: %d\n",message->db.reg_status);
343     fprintf(stderr,"   uid: %s\n",message->db.uid);
344     fprintf(stderr,"   login: %s\n",message->db.login);
345     fprintf(stderr,"   Status from query: %d\n",status);
346 #endif DEBGUG
347
348     return status;
349 }
350
351 int verify_user(message,retval)
352   struct msg *message;
353   char *retval;
354   /* This routine determines whether a user is in the databse and returns
355      his state so that other routines can figure out whether he is the 
356      correct state for various transactions. */
357      
358 {
359     int status = SUCCESS;       /* Return status */
360
361     /* Log that we are about to veryify user */
362     com_err(whoami,0,"verify_user %s %s",message->first,message->last);
363
364     /* Figure out what user (if any) can be found based on the
365        encrypted information in the packet.  (See the comment on 
366        parse_encrypted().) */
367
368     status = find_user(message);
369
370     /* If SMS coudn't find the user */
371     if (status == SMS_NO_MATCH) 
372         status = UREG_USER_NOT_FOUND;
373     else if (status == SMS_SUCCESS)
374     {
375         /* If the information sent over in the packet did not point to a
376            valid user, the mit_id field in the formatted packet structure
377            will be empty. */
378         if (message->db.mit_id[0] == NULL)
379             status = UREG_USER_NOT_FOUND;
380         /* If the user was found but the registration has already started,
381            use this as the status */
382         else
383         {
384             switch (message->db.reg_status)
385             {
386               case US_NO_LOGIN_YET:
387                 status = SUCCESS;
388                 break;
389               case US_REGISTERED:
390                 status = UREG_ALREADY_REGISTERED;
391                 break;
392               case US_NO_PASSWD:
393                 status = UREG_NO_PASSWD_YET;
394                 break;
395               case US_DELETED:
396                 status = UREG_DELETED;
397                 break;
398               case US_NOT_ALLOWED:
399                 status = UREG_NOT_ALLOWED;
400                 break;
401
402               default:
403                 status = UREG_MISC_ERROR;
404                 critical_alert(FAIL_INST,"Bad user state for login %s.",
405                                message->db.login);
406                 break;
407             }
408             /* Set retval to the login name so that the client can use
409                it in the error message it will give the user. */
410             (void) strcpy(retval,message->db.login);
411         }
412     }
413     
414     com_err(whoami,status," returned from verify_user");
415
416     return status;
417 }
418         
419 int ureg_get_tkt()
420 {
421     int status = SUCCESS;       /* Return status */
422
423     /* Get keys for interacting with Kerberos admin server. */
424     /* principal, instance, realm, service, service instance, life, file */
425     if (status = krb_get_svc_in_tkt("register", "sms", krbrealm, "changepw", 
426                                 krbhst, 1, KEYFILE))
427         status += ERROR_TABLE_BASE_krb;
428
429 #ifdef DEBUG
430     if (status == SUCCESS)
431         com_err(whoami,status,"Succeeded in getting tickets.");
432     else
433         com_err(whoami,status,"Failed to get tickets.");
434 #endif
435     return status;
436 }
437
438 int null_callproc(argc,argv,message)
439   int argc;
440   char *argv[];
441   char *message;
442   /* This routine is a null callback that should be used for queries that
443      do not return tuples.  If it ever gets called, something is wrong. */
444 {
445     critical_alert(FAIL_INST,"Something returned from an update query.");
446     return FAILURE;
447 }
448
449 int do_admin_call(login, passwd, uid)
450   char *login;                  /* Requested kerberos principal */
451   char *passwd;                 /* Requested password */
452   char *uid;                    /* Uid of user who owns this principal */
453   /* This routine gets tickets, makes the appropriate call to admin_call,
454      and destroys tickets. */
455 {
456     int status;                 /* Error status */
457     char uid_buf[20];           /* Holds uid for kerberos */
458
459     com_err(whoami,0,"Entering do_admin_call");
460
461     if ((status = ureg_get_tkt()) == SUCCESS)
462     {
463         /* Try to reserve kerberos principal.  To do this, send a 
464            password request and a null password.  It will only succeed
465            if there is no principal or the principal exists and has no 
466            password. */
467         /* 13 chars of placebo for backwards-compatability - the admin
468            server protocol reqires this. */
469         bzero(uid_buf,sizeof(uid_buf));
470         (void) sprintf(uid_buf, "%13s", uid);
471         
472         if ((status = admin_call(ADMIN_ADD_NEW_KEY_ATTR, login, 
473                                  "", passwd, uid_buf)) != KSUCCESS)
474         {
475             com_err(whoami,status," server error: %s",admin_errmsg);
476             
477             if (strcmp(admin_errmsg,
478                        "Principal already in kerberos database.") == 0)
479                 status = UREG_KRB_TAKEN;
480             critical_alert(FAIL_INST,"%s is known to Kerberos but not SMS.", 
481                            login);
482         }
483     }
484     
485     dest_tkt();
486     com_err(whoami,status," returned from do_admin_call");
487     return status;
488 }
489
490 int reserve_user(message,retval)
491   struct msg *message;
492   char *retval;
493 {
494     int q_argc;                 /* Number of arguments to query */
495     char *q_argv[3];            /* Arguments to SMS query */
496     char *q_name;               /* Name of SMS query */
497     int status = SUCCESS;       /* General purpose error status */
498     char fstype_buf[7];         /* Buffer to hold fs_type, a 16 bit number */
499     char *login;                /* The login name the user wants */
500     register int i;             /* A counter */
501
502     /* Log that we are about to reserve a user. */
503     com_err(whoami, 0, "reserve_user %s %s", 
504             message->first, message->last);
505     
506     /* Check to make sure that we can verify this user. */
507     if ((status = verify_user(message,retval)) == SUCCESS)
508     {
509         /* Get the requested login name from leftover packet information. */
510         login = message->leftover;
511
512         /* Check the login name for validity.  The login name is currently
513            is allowed to contain lowercase letters and numbers in any
514            position and underscore characters in any position but the 
515            first. */
516         if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
517             status = UREG_INVALID_UNAME;
518     }
519     if (status == SUCCESS)
520         if (login[1] == '_')
521             status = UREG_INVALID_UNAME;
522     if (status == SUCCESS)
523     {
524         for (i = 0; i < strlen(login); i++)
525             if (!islower(login[i]) && !isdigit(login[i]) && 
526                 (login[i] != '_'))
527             {
528                 status = UREG_INVALID_UNAME;
529                 break;
530             }
531     }
532     if (status == SUCCESS)
533     {
534         /* Now that we have a valid user with a valid login... */
535
536         /* First, try to reserve the user in SMS. */
537         (void) sprintf(fstype_buf,"%d",SMS_FS_STUDENT);
538         q_name = "register_user";
539         q_argv[0] = message->db.uid;
540         q_argv[1] = login;
541         q_argv[2] = fstype_buf;
542         q_argc = 3;
543         status = sms_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
544         switch (status)
545         {
546           case SMS_SUCCESS:
547             status = SUCCESS;
548             break;
549           case SMS_IN_USE:
550             status = UREG_LOGIN_USED;
551             break;
552           default:
553             critical_alert(FAIL_INST,"%s returned from register_user.",
554                            error_message(status));
555             status = UREG_MISC_ERROR;
556             break;
557         }
558     }
559     if (status == SUCCESS)
560     {
561         /* SMS login was successfully created; try to reserve kerberos
562            principal. */
563         /* If this routine fails, store the login in the retval so
564            that it can be used in the client-side error message. */
565         if ((status = do_admin_call(login, "", message->db.uid)) != SUCCESS)
566             (void) strcpy(retval, login);
567     }
568
569     com_err(whoami, status, " returned from reserve_user");
570     
571     return status;
572 }
573
574 int set_final_status(login)
575   char *login;
576     /* This routine updates a user's registration status to fully 
577        registered. */
578 {
579     char *q_name;               /* Name of SMS query */
580     int q_argc;                 /* Number of arguments for SMS query */
581     char *q_argv[2];            /* Arguments to get user by uid */
582     char state[7];              /* Can hold a 16 bit integer */
583     int status;                 /* Error status */
584
585     com_err(whoami, 0, "Setting final status for %s", login);
586
587     (void) sprintf(state,"%d",US_REGISTERED);
588     q_name = "update_user_status";
589     q_argc = 2;
590     q_argv[0] = login;
591     q_argv[1] = state;
592     if ((status = sms_query(q_name, q_argc, q_argv, null_callproc,
593                             (char *)0)) != SMS_SUCCESS)
594         critical_alert(FAIL_INST,"%s returned from update_user_status.",
595                        error_message(status));
596     
597     com_err(whoami,status," returned from set_final_status");
598     return status;
599 }
600
601
602 int set_password(message,retval)
603   struct msg *message;
604   char *retval;
605   /* This routine is used to set the initial password for the new user. */
606 {
607     int status = SUCCESS;       /* Return status */
608     char *passwd;               /* User's password */
609
610     com_err(whoami, 0, " set_password %s %s",
611             message->first, message->last);
612
613     status = verify_user(message,retval);
614
615     /* Don't set the password unless the registration status of the user
616        is that he exists and has no password. */
617     if (status == SUCCESS)
618         status = UREG_NO_LOGIN_YET;
619     if (status == UREG_NO_PASSWD_YET)
620     {
621         /* User is in proper state for this transaction. */
622         
623         passwd = message->leftover;
624         
625         /* Set password. */
626         if ((status = do_admin_call(message->db.login, 
627                                     passwd, message->db.uid)) != SUCCESS)
628             /* If failure, allow login name to be used in client 
629                error message */
630             (void) strcpy(retval,message->db.login);
631         else
632             /* Otherwise, mark user as finished. */
633             status = set_final_status(message->db.login);
634     }
635     com_err(whoami, status, " returned from set_passwd");
636     
637     return status;
638 }
This page took 0.093959 seconds and 5 git commands to generate.