]> andersk Git - moira.git/blob - reg_svr/reg_svr.c
make . no longer a legal char in usernames
[moira.git] / reg_svr / reg_svr.c
1 /*
2  *      $Source$
3  *      $Author$
4  *      $Header$
5  *
6  *      Copyright (C) 1987 by the Massachusetts Institute of Technology
7  *
8  *      Server for user registration with SMS and Kerberos.
9  *
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.
14  */
15
16 #ifndef lint
17 static char *rcsid_reg_svr_c = "$Header$";
18 #endif lint
19
20 #include "reg_svr.h"
21 #include "admin_server.h"
22 #include "admin_err.h"
23
24 extern int krb_err_base;
25 extern char admin_errmsg[];
26
27 static char krbhst[BUFSIZ];     /* kerberos server name */
28 static char krbrealm[REALM_SZ]; /* kerberos realm name */
29
30 main(argc,argv)
31   int argc;
32   char *argv[];
33 {
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 */
37     
38     void req_initialize();      /* Initialize request layer */
39     void get_request();         /* Get a request */
40     void report();              /* Respond to a request */
41
42     /* Initialize */
43     whoami = argv[0];
44     
45     /* Use com_err or output to stderr for all log messages. */    
46 #ifdef DEBUG
47     fprintf(stderr,"*** Debugging messages enabled. ***\n");
48 #endif DEBUG
49     
50     /* Error messages sent one line at a time */
51     setlinebuf(stderr);
52     
53     /* Initialize user registration error table for com_err */
54     init_ureg_err_tbl();
55     
56     /* Connect to the SMS server */
57     if ((status = sms_connect()) != SMS_SUCCESS) 
58     {
59         com_err(whoami, status, " on connect");
60         exit(1);
61     }
62     
63     /* Authorize, telling the server who you are */
64     if ((status = sms_auth(whoami)) != SMS_SUCCESS) 
65     {
66         com_err(whoami, status, " on auth");
67         exit(1);
68     }
69     
70     if (status = get_krbrlm(krbrealm, 1)) {
71         status += krb_err_base;
72         com_err(whoami, status, " fetching kerberos realm");
73         exit(1);
74     }
75     
76     if (status = get_krbhst(krbhst, krbrealm, 1)) {
77         status += krb_err_base;
78         com_err(whoami, status, " fetching kerberos hostname");
79         exit(1);
80     } else {
81         char *s;
82         for (s = krbhst; *s && *s != '.'; s++)
83             if (isupper(*s))
84                 *s = tolower(*s);
85         *s = 0;
86     }
87
88     journal = fopen(JOURNAL, "a");
89     if (journal == NULL) {
90         com_err(whoami, errno, " while opening journal file");
91         exit(1);
92     }
93     
94     /* Allow request layer to initialize */
95     req_initialize();
96     
97     /* Sit around waiting for requests from the client. */
98     for (;;) 
99     {
100         get_request(&message);
101         
102         switch((int)message.request) 
103         {
104           case UREG_VERIFY_USER:
105             status = verify_user(&message,retval);
106             break;
107           case UREG_RESERVE_LOGIN:
108             status = reserve_user(&message,retval);
109             break;
110           case UREG_SET_PASSWORD:
111             status = set_password(&message,retval);
112             break;
113             
114           default:
115             status = UREG_UNKNOWN_REQUEST;
116             critical_alert(FAIL_INST,"Unknown request %d from userreg.",
117                            message.request);
118             break;
119         }
120         
121         /* Report what happened to client */
122         report(status, retval);
123     }
124 }
125
126 /* This is necessary so that this server can know where to put its
127    tickets. */
128 char *tkt_string()
129 {
130     return("/tmp/tkt_ureg");
131 }
132
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:
139
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
146    the key.
147
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. */
153 {
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 */
164     
165 #ifdef DEBUG
166     com_err(whoami,0,"Entering parse_encrypted");
167 #endif
168
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;
173     
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-
180        padded. */
181     pcbc_encrypt(message->encrypted,decrypt, decrypt_len, sched, key, DECRYPT);
182     
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);
204     
205     /* Now compare encrypted ID's don't match. */
206     if (strcmp(hashid, data->mit_id)) status = FAILURE;
207     if (status == SUCCESS)
208     {
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;
212     }
213     
214     if (status == SUCCESS)
215     {
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));
227     }
228     
229 #ifdef DEBUG
230     if (status)
231         com_err(whoami,status," parse_encrypted failed.");
232     else
233         com_err(whoami,status,"parse_encrypted succeeded.");
234 #endif
235
236     return status;
237 }
238
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.  */
245 {
246     struct db_data *data;       /* Structure to store the information in */
247     int status = SUCCESS;       /* Error status */
248     
249 #ifdef DEBUG
250     com_err(whoami,0,"Entering db_callproc.");
251 #endif
252
253     if (argc != U_END)
254     {
255         critical_alert
256             (FAIL_INST,
257              "Wrong number of arguments returned from get_user_by_name.");
258         status = SMS_ABORT;
259     }
260     else
261     {
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));
268 #ifdef DEBUG
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);
274 #endif
275         sq_save_data(queue,data);
276     }
277
278     return status;
279 }
280     
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. */
286 {
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 */
292
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? */
296
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));
300     
301     if (status == SUCCESS)
302     {
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;
308         
309         /* Create queue to hold information */
310         queue = sq_create();
311         
312         /* Do it */
313         status = sms_query(q_name,q_argc,q_argv,db_callproc,(char *)queue);
314         
315 #ifdef DEBUG
316         fprintf(stderr," %d returned by get_user_by_name\n",status);
317 #endif
318         
319         if (status == SMS_SUCCESS) 
320         {
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))
324             {
325                 if (!verified)
326                     /* parse_encrypted returns zero on success */
327                     verified = (parse_encrypted(message,data) == SUCCESS);
328                 free((char *)data);
329             }
330         }
331
332         /* Destroy the queue */
333         sq_destroy(queue);
334     }
335     
336 #ifdef DEBUG
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);
343 #endif DEBGUG
344
345     return status;
346 }
347
348 int verify_user(message,retval)
349   struct msg *message;
350   char *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. */
354      
355 {
356     int status = SUCCESS;       /* Return status */
357
358     /* Log that we are about to veryify user */
359     com_err(whoami,0,"verify_user %s %s",message->first,message->last);
360
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().) */
364
365     status = find_user(message);
366
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)
371     {
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
374            will be empty. */
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 */
379         else
380         {
381             switch (message->db.reg_status)
382             {
383               case US_NO_LOGIN_YET:
384                 status = SUCCESS;
385                 break;
386               case US_REGISTERED:
387                 status = UREG_ALREADY_REGISTERED;
388                 break;
389               case US_NO_PASSWD:
390                 status = UREG_NO_PASSWD_YET;
391                 break;
392               case US_DELETED:
393                 status = UREG_DELETED;
394                 break;
395               case US_NOT_ALLOWED:
396                 status = UREG_NOT_ALLOWED;
397                 break;
398
399               default:
400                 status = UREG_MISC_ERROR;
401                 critical_alert(FAIL_INST,"Bad user state for login %s.",
402                                message->db.login);
403                 break;
404             }
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);
408         }
409     }
410     
411     com_err(whoami,status," returned from verify_user");
412
413     return status;
414 }
415         
416 int ureg_get_tkt()
417 {
418     int status = SUCCESS;       /* Return status */
419
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", 
423                                 krbhst, 1, KEYFILE))
424         status += krb_err_base;
425
426 #ifdef DEBUG
427     if (status == SUCCESS)
428         com_err(whoami,status,"Succeeded in getting tickets.");
429     else
430         com_err(whoami,status,"Failed to get tickets.");
431 #endif
432     return status;
433 }
434
435 int null_callproc(argc,argv,message)
436   int argc;
437   char *argv[];
438   char *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. */
441 {
442     critical_alert(FAIL_INST,"Something returned from an update query.");
443     return FAILURE;
444 }
445
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. */
452 {
453     int status;                 /* Error status */
454     char uid_buf[20];           /* Holds uid for kerberos */
455
456     com_err(whoami,0,"Entering do_admin_call");
457
458     if ((status = ureg_get_tkt()) == SUCCESS)
459     {
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 
463            password. */
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);
468         
469         if ((status = admin_call(ADMIN_ADD_NEW_KEY_ATTR, login, 
470                                  "", passwd, uid_buf)) != KSUCCESS)
471         {
472             com_err(whoami,status," server error: %s",admin_errmsg);
473             
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.", 
478                            login);
479         }
480     }
481     
482     dest_tkt();
483     com_err(whoami,status," returned from do_adin_call");
484     return status;
485 }
486
487 int reserve_user(message,retval)
488   struct msg *message;
489   char *retval;
490 {
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 */
498
499     /* Log that we are about to reserve a user. */
500     com_err(whoami, 0, "reserve_user %s %s", 
501             message->first, message->last);
502     
503     /* Check to make sure that we can verify this user. */
504     if ((status = verify_user(message,retval)) == SUCCESS)
505     {
506         /* Get the requested login name from leftover packet information. */
507         login = message->leftover;
508
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
512            but the first. */
513         if ((strlen(login) < MIN_UNAME) || (strlen(login) > MAX_UNAME))
514             status = UREG_INVALID_UNAME;
515     }
516     if (status == SUCCESS)
517         if (login[1] == '_')
518             status = UREG_INVALID_UNAME;
519     if (status == SUCCESS)
520     {
521         for (i = 0; i < strlen(login); i++)
522             if (!islower(login[i]) && !isdigit(login[i]) && 
523                 (login[i] != '_'))
524             {
525                 status = UREG_INVALID_UNAME;
526                 break;
527             }
528     }
529     if (status == SUCCESS)
530     {
531         /* Now that we have a valid user with a valid login... */
532
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;
537         q_argv[1] = login;
538         q_argv[2] = fstype_buf;
539         q_argc = 3;
540         status = sms_query(q_name,q_argc,q_argv,null_callproc,(char *)0);
541         switch (status)
542         {
543           case SMS_SUCCESS:
544             status = SUCCESS;
545             break;
546           case SMS_IN_USE:
547             status = UREG_LOGIN_USED;
548             break;
549           default:
550             status = UREG_MISC_ERROR;
551             critical_alert(FAIL_INST,"%s returned from register_user.",
552                            error_message(status));
553             break;
554         }
555     }
556     if (status == SUCCESS)
557     {
558         /* SMS login was successfully created; try to reserve kerberos
559            principal. */
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);
564     }
565
566     com_err(whoami, status, " returned from reserve_user");
567     
568     return status;
569 }
570
571 int set_final_status(login)
572   char *login;
573     /* This routine updates a user's registration status to fully 
574        registered. */
575 {
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 */
581
582     com_err(whoami, 0, "Setting final status for %s", login);
583
584     (void) sprintf(state,"%d",US_REGISTERED);
585     q_name = "update_user_status";
586     q_argc = 2;
587     q_argv[0] = login;
588     q_argv[1] = state;
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));
593     
594     com_err(whoami,status," returned from set_final_status");
595     return status;
596 }
597
598
599 int set_password(message,retval)
600   struct msg *message;
601   char *retval;
602   /* This routine is used to set the initial password for the new user. */
603 {
604     int status = SUCCESS;       /* Return status */
605     char *passwd;               /* User's password */
606
607     com_err(whoami, 0, " set_password %s %s",
608             message->first, message->last);
609
610     status = verify_user(message,retval);
611
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)
617     {
618         /* User is in proper state for this transaction. */
619         
620         passwd = message->leftover;
621         
622         /* Set password. */
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 
626                error message */
627             (void) strcpy(retval,message->db.login);
628         else
629             /* Otherwise, mark user as finished. */
630             status = set_final_status(message->db.login);
631     }
632     com_err(whoami, status, " returned from set_passwd");
633     
634     return status;
635 }
636     
637 /*
638  * Local Variables:
639  * mode: c
640  * c-argdecl-indent: 2
641  * c-brace-offset: -4
642  * c-continued-statement-offset: 4
643  * c-indent-level: 4
644  * c-label-offset: -2
645  * End:
646  */
This page took 0.110674 seconds and 5 git commands to generate.