]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libpam/modules/pam_cracklib/pam_cracklib.c
Initial import of virgin Linux-PAM 0.65, slightly stripped down.
[FreeBSD/FreeBSD.git] / contrib / libpam / modules / pam_cracklib / pam_cracklib.c
1 /* pam_cracklib module */
2
3 /*
4  * 0.85.  added six new options to use this with long passwords.
5  * 0.8. tidied output and improved D(()) usage for debugging.
6  * 0.7. added support for more obscure checks for new passwd.
7  * 0.6. root can reset user passwd to any values (it's only warned)
8  * 0.5. supports retries - 'retry=N' argument
9  * 0.4. added argument 'type=XXX' for 'New XXX password' prompt
10  * 0.3. Added argument 'debug'
11  * 0.2. new password is feeded to cracklib for verify after typed once
12  * 0.1. First release
13  */
14
15 /*
16  * Written by Cristian Gafton <gafton@redhat.com> 1996/09/10
17  * Long password support by Philip W. Dalrymple <pwd@mdtsoft.com> 1997/07/18
18  * See the end of the file for Copyright Information
19  *
20  * Modification for long password systems (>8 chars).  The original
21  * module had problems when used in a md5 password system in that it
22  * allowed too short passwords but required that at least half of the
23  * bytes in the new password did not appear in the old one.  this
24  * action is still the default and the changes should not break any
25  * current user. This modification adds 6 new options, one to set the
26  * number of bytes in the new password that are not in the old one,
27  * the other five to control the length checking, these are all
28  * documented (or will be before anyone else sees this code) in the PAM
29  * S.A.G. in the section on the cracklib module.
30  */
31
32 #include <stdio.h>
33 #define __USE_BSD
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <syslog.h>
38 #include <stdarg.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <ctype.h>
42
43 extern char *FascistCheck(char *pw, const char *dictpath);
44
45 #ifndef CRACKLIB_DICTPATH
46 #define CRACKLIB_DICTPATH "/usr/lib/cracklib_dict"
47 #endif
48
49 #define PROMPT1 "New %s password: "
50 #define PROMPT2 "Retype new %s password: "
51 #define MISTYPED_PASS "Sorry, passwords do not match"
52
53 /*
54  * here, we make a definition for the externally accessible function
55  * in this file (this definition is required for static a module
56  * but strongly encouraged generally) it is used to instruct the
57  * modules include file to define the function prototypes.
58  */
59
60 #define PAM_SM_PASSWORD
61
62 #include <security/pam_modules.h>
63 #include <security/_pam_macros.h>
64
65 #ifndef LINUX_PAM 
66 #include <security/pam_appl.h>
67 #endif  /* LINUX_PAM */
68
69 /* some syslogging */
70
71 static void _pam_log(int err, const char *format, ...)
72 {
73     va_list args;
74
75     va_start(args, format);
76     openlog("PAM-Cracklib", LOG_CONS|LOG_PID, LOG_AUTH);
77     vsyslog(err, format, args);
78     va_end(args);
79     closelog();
80 }
81
82 /* argument parsing */
83 #define PAM_DEBUG_ARG       0x0001
84
85 /* module data - AGM: please remove these static variables... PAM was
86  * designed to be reentrant based soley on a unique pamh... this
87  * breaks that.  */
88
89 static int retry_times = 0;
90 static int diff_ok = 10;
91 static int min_length = 9;
92 static int dig_credit = 1;
93 static int up_credit = 1;
94 static int low_credit = 1;
95 static int oth_credit = 1;
96 static char prompt_type[BUFSIZ];
97
98 static int _pam_parse(int argc, const char **argv)
99 {
100      int ctrl=0;
101
102      /* step through arguments */
103      for (ctrl=0; argc-- > 0; ++argv) {
104          char *ep = NULL;
105
106          /* generic options */
107
108          if (!strcmp(*argv,"debug"))
109              ctrl |= PAM_DEBUG_ARG;
110          else if (!strncmp(*argv,"type=",5))
111              strcpy(prompt_type, *argv+5);
112          else if (!strncmp(*argv,"retry=",6)) {
113              retry_times = strtol(*argv+6,&ep,10);
114              if (!ep || (retry_times < 1))
115                  retry_times = 1;
116          } else if (!strncmp(*argv,"difok=",6)) {
117              diff_ok = strtol(*argv+6,&ep,10);
118              if (!ep || (diff_ok < 0))
119                  diff_ok = 10;
120          } else if (!strncmp(*argv,"minlen=",7)) {
121              min_length = strtol(*argv+7,&ep,10);
122              if (!ep || (min_length < 5))
123                  min_length = 5;
124          } else if (!strncmp(*argv,"dcredit=",8)) {
125              dig_credit = strtol(*argv+8,&ep,10);
126              if (!ep || (dig_credit < 0))
127                  dig_credit = 0;
128          } else if (!strncmp(*argv,"ucredit=",8)) {
129              up_credit = strtol(*argv+8,&ep,10);
130              if (!ep || (up_credit < 0))
131                  up_credit = 0;
132          } else if (!strncmp(*argv,"lcredit=",8)) {
133              low_credit = strtol(*argv+8,&ep,10);
134              if (!ep || (low_credit < 0))
135                  low_credit = 0;
136          } else if (!strncmp(*argv,"ocredit=",8)) {
137              oth_credit = strtol(*argv+8,&ep,10);
138              if (!ep || (oth_credit < 0))
139                  oth_credit = 0;
140          } else {
141              _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv);
142          }
143      }
144
145      return ctrl;
146 }
147
148 /* Helper functions */
149
150 /* this is a front-end for module-application conversations */
151 static int converse(pam_handle_t *pamh, int ctrl, int nargs,
152                     struct pam_message **message,
153                     struct pam_response **response)
154 {
155     int retval;
156     struct pam_conv *conv;
157
158     retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv); 
159
160     if ( retval == PAM_SUCCESS ) {
161         retval = conv->conv(nargs, (const struct pam_message **)message,
162                                         response, conv->appdata_ptr);
163         if (retval != PAM_SUCCESS && (ctrl && PAM_DEBUG_ARG)) {
164             _pam_log(LOG_DEBUG, "conversation failure [%s]",
165                                  pam_strerror(pamh, retval));
166         }
167     } else {
168         _pam_log(LOG_ERR, "couldn't obtain coversation function [%s]",
169                 pam_strerror(pamh, retval));
170     }
171
172     return retval;                  /* propagate error status */
173 }
174
175 static int make_remark(pam_handle_t *pamh, unsigned int ctrl,
176                        int type, const char *text)
177 {
178     struct pam_message *pmsg[1], msg[1];
179     struct pam_response *resp;
180     int retval;
181
182     pmsg[0] = &msg[0];
183     msg[0].msg = text;
184     msg[0].msg_style = type;
185     resp = NULL;
186
187     retval = converse(pamh, ctrl, 1, pmsg, &resp);
188     if (retval == PAM_SUCCESS)
189         _pam_drop_reply(resp, 1);
190
191     return retval;
192 }
193
194 /* use this to free strings. ESPECIALLY password strings */
195 static char *_pam_delete(register char *xx)
196 {
197     _pam_overwrite(xx);
198     free(xx);
199     return NULL;
200 }
201
202 /*
203  * can't be a palindrome - like `R A D A R' or `M A D A M'
204  */
205 static int palindrome(const char *old, const char *new)
206 {
207     int i, j;
208
209         i = strlen (new);
210
211         for (j = 0;j < i;j++)
212                 if (new[i - j - 1] != new[j])
213                         return 0;
214
215         return 1;
216 }
217
218 /*
219  * more than half of the characters are different ones.
220  * or at least diff_ok are different
221  * NOTE that the defaults are NOT the same as befor this
222  * change. as long as there are at least 10 different bytes
223  * in a new password it will now pass even if the password
224  * is longer than 20 bytes (MD5)
225  */
226
227 static int similiar(const char *old, const char *new)
228 {
229         int     i, j;
230
231         for (i = j = 0;new[i] && old[i];i++)
232                 if (strchr (new, old[i]))
233                         j++;
234
235         if (j >= diff_ok || i >= j * 2)
236                 return 0;
237
238         return 1;
239 }
240
241 /*
242  * a nice mix of characters.
243  */
244 static int simple(const char *old, const char *new)
245 {
246         int     digits = 0;
247         int     uppers = 0;
248         int     lowers = 0;
249         int     others = 0;
250         int     size;
251         int     i;
252
253         for (i = 0;new[i];i++) {
254                 if (isdigit (new[i]))
255                         digits++;
256                 else if (isupper (new[i]))
257                         uppers++;
258                 else if (islower (new[i]))
259                         lowers++;
260                 else
261                         others++;
262         }
263
264         /*
265          * The scam was this - a password of only one character type
266          * must be 8 letters long.  Two types, 7, and so on.
267          * This is now changed, the base size and the credits or defaults
268          * see the docs on the module for info on these parameters, the
269          * defaults cause the effect to be the same as before the change
270          */
271
272         if (digits > dig_credit)
273             digits = dig_credit;
274
275         if (uppers > up_credit)
276             uppers = up_credit;
277
278         if (lowers > low_credit)
279             lowers = low_credit;
280
281         if (others > oth_credit)
282             others = oth_credit;
283
284         size = min_length;
285         size -= digits;
286         size -= uppers;
287         size -= lowers;
288         size -= others;
289
290         if (size <= i)
291                 return 0;
292
293         return 1;
294 }
295
296 static char * str_lower(char *string)
297 {
298         char *cp;
299
300         for (cp = string; *cp; cp++)
301                 *cp = tolower(*cp);
302         return string;
303 }
304
305 static const char * password_check(const char *old, const char *new)
306 {
307         const char *msg = NULL;
308         char *oldmono, *newmono, *wrapped;
309
310         if (strcmp(new, old) == 0) {
311         msg = "is the same as the old one";
312         return msg;
313     }
314
315         newmono = str_lower(x_strdup(new));
316         oldmono = str_lower(x_strdup(old));
317         wrapped = malloc(strlen(oldmono) * 2 + 1);
318         strcpy (wrapped, oldmono);
319         strcat (wrapped, oldmono);
320
321         if (palindrome(oldmono, newmono))
322                 msg = "is a palindrome";
323
324         if (!msg && strcmp(oldmono, newmono) == 0)
325                 msg = "case changes only";
326
327         if (!msg && similiar(oldmono, newmono))
328                 msg = "is too similiar to the old one";
329
330         if (!msg && simple(old, new))
331                 msg = "is too simple";
332
333         if (!msg && strstr(wrapped, newmono))
334                 msg = "is rotated";
335
336         memset(newmono, 0, strlen(newmono));
337         memset(oldmono, 0, strlen(oldmono));
338         memset(wrapped, 0, strlen(wrapped));
339         free(newmono);
340         free(oldmono);
341         free(wrapped);
342
343         return msg;
344 }
345
346
347 static int _pam_unix_approve_pass(pam_handle_t *pamh,
348                                   unsigned int ctrl,
349                                   const char *pass_old,
350                                   const char *pass_new)
351 {
352     const char *msg = NULL;
353     
354     if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) {
355         if (ctrl && PAM_DEBUG_ARG)
356             _pam_log(LOG_DEBUG, "bad authentication token");
357         make_remark(pamh, ctrl, PAM_ERROR_MSG,
358                     pass_new == NULL ?
359                     "No password supplied":"Password unchanged" );
360         return PAM_AUTHTOK_ERR;
361     }
362
363     /*
364      * if one wanted to hardwire authentication token strength
365      * checking this would be the place
366      */
367     msg = password_check(pass_old,pass_new);
368     if (msg) {
369         char remark[BUFSIZ];
370         
371         memset(remark,0,sizeof(remark));
372         sprintf(remark,"BAD PASSWORD: %s",msg);
373         if (ctrl && PAM_DEBUG_ARG)
374             _pam_log(LOG_NOTICE, "new passwd fails strength check: %s",
375                                   msg);
376         make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
377         return PAM_AUTHTOK_ERR;
378     };   
379     return PAM_SUCCESS;
380     
381 }
382
383 /* The Main Thing (by Cristian Gafton, CEO at this module :-) 
384  * (stolen from http://home.netscape.com)
385  */
386 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
387                                 int argc, const char **argv)
388 {
389     unsigned int ctrl;
390     
391     retry_times = 1;
392     memset(prompt_type,0,sizeof(prompt_type));
393     ctrl = _pam_parse(argc, argv);
394
395     D(("called."));
396     if (!prompt_type[0])
397         strcpy(prompt_type,"UNIX");
398
399     if (flags & PAM_PRELIM_CHECK) {
400         /* Check for passwd dictionary */       
401         struct stat st;
402         char buf[sizeof(CRACKLIB_DICTPATH)+10];
403
404         D(("prelim check"));
405
406         memset(buf,0,sizeof(buf)); /* zero the buffer */
407         sprintf(buf,"%s.pwd",CRACKLIB_DICTPATH);
408
409         if (!stat(buf,&st) && st.st_size)
410             return PAM_SUCCESS;
411         else {
412             if (ctrl & PAM_DEBUG_ARG)
413                 _pam_log(LOG_NOTICE,"dict path '%s'[.pwd] is invalid",
414                                      CRACKLIB_DICTPATH);
415             return PAM_ABORT;
416         }
417         
418         /* Not reached */
419         return PAM_SERVICE_ERR;
420
421     } else if (flags & PAM_UPDATE_AUTHTOK) {
422         int retval;
423         char *token1, *token2, *oldtoken;
424         const char *item;
425         struct pam_message msg[1],*pmsg[1];
426         struct pam_response *resp;
427         const char *cracklib_dictpath = CRACKLIB_DICTPATH;
428         char prompt[BUFSIZ];
429
430         D(("do update"));
431         retval = pam_get_item(pamh, PAM_OLDAUTHTOK,
432                               (const void **)&oldtoken);
433         if (retval != PAM_SUCCESS) {
434             if (ctrl & PAM_DEBUG_ARG)
435                 _pam_log(LOG_ERR,"Can not get old passwd");
436             oldtoken=NULL;
437             retval = PAM_SUCCESS;
438         }
439
440         do {        
441         /*
442          * make sure nothing inappropriate gets returned
443          */
444         token1 = token2 = NULL;
445         
446         if (!retry_times) {
447             D(("returning %s because maxtries reached",
448                pam_strerror(pamh, retval)));
449             return retval;
450         }
451
452         /* Planned modus operandi:
453          * Get a passwd.
454          * Verify it against cracklib.
455          * If okay get it a second time. 
456          * Check to be the same with the first one.
457          * set PAM_AUTHTOK and return
458          */
459
460         /* Prepare to ask the user for the first time */
461         memset(prompt,0,sizeof(prompt));
462         sprintf(prompt,PROMPT1,prompt_type);
463         pmsg[0] = &msg[0];
464         msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
465         msg[0].msg = prompt;
466
467         resp = NULL;
468         retval = converse(pamh, ctrl, 1, pmsg, &resp);
469         if (resp != NULL) {
470             /* interpret the response */
471             if (retval == PAM_SUCCESS) {     /* a good conversation */
472                 token1 = x_strdup(resp[0].resp);
473                 if (token1 == NULL) {
474                     _pam_log(LOG_NOTICE,
475                              "could not recover authentication token 1");
476                     retval = PAM_AUTHTOK_RECOVER_ERR;
477                 }
478             }
479             /*
480              * tidy up the conversation (resp_retcode) is ignored
481              */
482             _pam_drop_reply(resp, 1);
483         } else {
484             retval = (retval == PAM_SUCCESS) ?
485                      PAM_AUTHTOK_RECOVER_ERR:retval ;
486         }
487
488         if (retval != PAM_SUCCESS) {
489             if (ctrl && PAM_DEBUG_ARG)
490                 _pam_log(LOG_DEBUG,"unable to obtain a password");
491             continue;
492         }
493
494         D(("testing password, retval = %s", pam_strerror(pamh, retval)));
495         /* now test this passwd against cracklib */
496         {
497             char *crack_msg;
498             char remark[BUFSIZ];
499             
500             bzero(remark,sizeof(remark));
501             D(("against cracklib"));
502             if ((crack_msg = FascistCheck(token1, cracklib_dictpath))) {
503                 if (ctrl && PAM_DEBUG_ARG)
504                     _pam_log(LOG_DEBUG,"bad password: %s",crack_msg);
505                 sprintf(remark,"BAD PASSWORD: %s", crack_msg);
506                 make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
507                 if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
508                     retval = PAM_AUTHTOK_ERR;
509                 else
510                     retval = PAM_SUCCESS;
511             } else {
512                 /* check it for strength too... */
513                 D(("for strength"));
514                 if (oldtoken) {
515                     retval = _pam_unix_approve_pass(pamh,ctrl,
516                                                oldtoken,token1);
517                     if (retval != PAM_SUCCESS)
518                         if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
519                             retval = PAM_AUTHTOK_ERR;
520                         else
521                             retval = PAM_SUCCESS;
522                 }
523             }
524         }
525
526         D(("after testing: retval = %s", pam_strerror(pamh, retval)));
527         /* if cracklib/strength check said it is a bad passwd... */
528         if ((retval != PAM_SUCCESS) && (retval != PAM_IGNORE)) {
529             int temp_unused;
530
531             temp_unused = pam_set_item(pamh, PAM_AUTHTOK, NULL);
532             token1 = _pam_delete(token1);
533             continue;
534         }
535
536         /* Now we have a good passwd. Ask for it once again */
537         
538         bzero(prompt,sizeof(prompt));
539         sprintf(prompt,PROMPT2,prompt_type);
540         pmsg[0] = &msg[0];
541         msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
542         msg[0].msg = prompt;
543
544         resp = NULL;
545         retval = converse(pamh, ctrl, 1, pmsg, &resp);
546         if (resp != NULL) {
547             /* interpret the response */
548             if (retval == PAM_SUCCESS) {     /* a good conversation */
549                 token2 = x_strdup(resp[0].resp);
550                 if (token2 == NULL) {
551                     _pam_log(LOG_NOTICE,
552                              "could not recover authentication token 2");
553                     retval = PAM_AUTHTOK_RECOVER_ERR;
554                 }
555             }
556             /*
557              * tidy up the conversation (resp_retcode) is ignored
558              */
559             _pam_drop_reply(resp, 1);
560         } else {
561             retval = (retval == PAM_SUCCESS) ?
562                      PAM_AUTHTOK_RECOVER_ERR:retval ;
563         }
564
565         if (retval != PAM_SUCCESS) {
566             if (ctrl && PAM_DEBUG_ARG)
567                 _pam_log(LOG_DEBUG
568                          ,"unable to obtain the password a second time");
569             continue;
570         }
571
572         /* Hopefully now token1 and token2 the same password ... */
573         if (strcmp(token1,token2) != 0) {
574             /* tell the user */
575             make_remark(pamh, ctrl, PAM_ERROR_MSG, MISTYPED_PASS);
576             token1 = _pam_delete(token1);
577             token2 = _pam_delete(token2);
578             pam_set_item(pamh, PAM_AUTHTOK, NULL);
579             if (ctrl & PAM_DEBUG_ARG)
580                 _pam_log(LOG_NOTICE,"Password mistyped");
581             retval = PAM_AUTHTOK_RECOVER_ERR;
582             continue;
583         }
584         
585         /* Yes, the password was typed correct twice
586          * we store this password as an item
587          */
588
589         retval = pam_set_item(pamh, PAM_AUTHTOK, token1);
590         /* clean it up */
591         token1 = _pam_delete(token1);
592         token2 = _pam_delete(token2);
593         if (
594             (retval != PAM_SUCCESS) ||
595             (
596              (
597                 retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&item)
598              ) != PAM_SUCCESS
599             )
600            ) {
601                 _pam_log(LOG_CRIT, "error manipulating password");
602                 continue;
603         }
604         item = NULL;                 /* break link to password */
605         return PAM_SUCCESS;
606         
607         } while (retry_times--);
608
609     } else {
610         if (ctrl & PAM_DEBUG_ARG)
611             _pam_log(LOG_NOTICE, "UNKNOWN flags setting %02X",flags);
612         return PAM_SERVICE_ERR;
613     }
614
615     /* Not reached */
616     return PAM_SERVICE_ERR;                            
617 }
618
619
620
621 #ifdef PAM_STATIC
622 /* static module data */
623 struct pam_module _pam_cracklib_modstruct = {
624      "pam_cracklib",
625      NULL,
626      NULL,
627      NULL,
628      NULL,
629      NULL,
630      pam_sm_chauthtok
631 };
632 #endif
633
634 /*
635  * Copyright (c) Cristian Gafton <gafton@redhat.com>, 1996.
636  *                                              All rights reserved
637  *
638  * Redistribution and use in source and binary forms, with or without
639  * modification, are permitted provided that the following conditions
640  * are met:
641  * 1. Redistributions of source code must retain the above copyright
642  *    notice, and the entire permission notice in its entirety,
643  *    including the disclaimer of warranties.
644  * 2. Redistributions in binary form must reproduce the above copyright
645  *    notice, this list of conditions and the following disclaimer in the
646  *    documentation and/or other materials provided with the distribution.
647  * 3. The name of the author may not be used to endorse or promote
648  *    products derived from this software without specific prior
649  *    written permission.
650  *
651  * ALTERNATIVELY, this product may be distributed under the terms of
652  * the GNU Public License, in which case the provisions of the GPL are
653  * required INSTEAD OF the above restrictions.  (This clause is
654  * necessary due to a potential bad interaction between the GPL and
655  * the restrictions contained in a BSD-style copyright.)
656  *
657  * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
658  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
659  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
660  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
661  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
662  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
663  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
664  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
665  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
666  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
667  * OF THE POSSIBILITY OF SUCH DAMAGE.
668  *
669  * The following copyright was appended for the long password support
670  * added with the libpam 0.58 release:
671  *
672  * Modificaton Copyright (c) Philip W. Dalrymple III <pwd@mdtsoft.com>
673  *       1997. All rights reserved
674  *
675  * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO
676  * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
677  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
678  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
679  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
680  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
681  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
682  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
683  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
684  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
685  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
686  * OF THE POSSIBILITY OF SUCH DAMAGE.
687  */