Convert ubik_Call(x) calls to ubik_x()
[openafs.git] / src / uss / uss_kauth.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  *      Implementation of basic procedures for the AFS user account
12  *      facility.
13  */
14
15 /*
16  * --------------------- Required definitions ---------------------
17  */
18 #include <afsconfig.h>
19 #include <afs/param.h>
20
21
22 #include "uss_kauth.h"          /*Module interface */
23 #include "uss_common.h"         /*Common defs & operations */
24 #include <errno.h>
25 #include <pwd.h>
26
27 #include <string.h>
28
29 #include <afs/com_err.h>
30 #include <afs/kautils.h> /*MAXKTCREALMLEN*/
31 #include <afs/kaport.h>         /* pack_long */
32 #include <afs/kauth.h>
33 #define uss_kauth_MAX_SIZE      2048
34 #undef USS_KAUTH_DB
35 /*
36  * ---------------------- Exported variables ----------------------
37  */
38 struct ubik_client *uconn_kauthP;       /*Ubik connections
39                                          * to AuthServers */
40
41 /*
42  * ------------------------ Private globals -----------------------
43  */
44 static int initDone = 0;        /*Module initialized? */
45 static char CreatorInstance[MAXKTCNAMELEN];     /*Instance string */
46 static char UserPrincipal[MAXKTCNAMELEN];       /*Parsed user principal */
47 static char UserInstance[MAXKTCNAMELEN];        /*Parsed user instance */
48 static char UserCell[MAXKTCREALMLEN];   /*Parsed user cell */
49 int doUnlog = 0;
50
51 /*-----------------------------------------------------------------------
52  * EXPORTED uss_kauth_InitAccountCreator
53  *
54  * Environment:
55  *      The command line must have been parsed.
56  *
57  * Side Effects:
58  *      As advertised.
59  *-----------------------------------------------------------------------*/
60
61 afs_int32
62 uss_kauth_InitAccountCreator(void)
63 {                               /*uss_kauth_InitAccountCreator */
64
65     char *name;
66     struct passwd *pw;
67     int dotPosition;
68
69     /*
70      * Set up the identity of the principal performing the account
71      * creation (uss_AccountCreator).  It's either the administrator
72      * name provided at the call or the identity of the caller as
73      * gleaned from the password info.
74      */
75     if (uss_Administrator[0] != '\0') {
76         name = uss_Administrator;
77     } else {                    /* Administrator name not passed in */
78         pw = getpwuid(getuid());
79         if (pw == 0) {
80             fprintf(stderr,
81                     "%s: Can't figure out your name from your user id.\n",
82                     uss_whoami);
83             return (1);
84         }
85         name = pw->pw_name;
86     }
87
88     /* Break the *name into principal and instance */
89     dotPosition = strcspn(name, ".");
90     if (dotPosition >= MAXKTCNAMELEN) {
91         fprintf(stderr, "Admin principal name too long.\n");
92         return (1);
93     }
94     strncpy(uss_AccountCreator, name, dotPosition);
95     uss_AccountCreator[dotPosition] = '\0';
96
97     name += dotPosition;
98     if (name[0] == '.') {
99         name++;
100         if (strlen(name) >= MAXKTCNAMELEN) {
101             fprintf(stderr, "Admin instance name too long.\n");
102             return (1);
103         }
104         strcpy(CreatorInstance, name);
105     } else {
106         CreatorInstance[0] = '\0';
107     }
108
109 #ifdef USS_KAUTH_DB_INSTANCE
110     fprintf(stderr, "%s: Starting CreatorInstance is '%s', %d bytes\n",
111             uss_whoami, CreatorInstance, strlen(CreatorInstance));
112 #endif /* USS_KAUTH_DB_INSTANCE */
113
114     return (0);
115 }
116
117 /*-----------------------------------------------------------------------
118  * static InitThisModule
119  *
120  * Description:
121  *      Set up this module, namely set up all the client state for
122  *      dealing with the Volume Location Server(s), including
123  *      network connections.
124  *
125  * Arguments:
126  *      a_noAuthFlag : Do we need authentication?
127  *      a_confDir    : Configuration directory to use.
128  *      a_cellName   : Cell we want to talk to.
129  *
130  * Returns:
131  *      0 if everything went fine, or
132  *      lower-level error code otherwise.
133  *
134  * Environment:
135  *      This routine will only be called once.
136  *
137  * Side Effects:
138  *      As advertised.
139  *------------------------------------------------------------------------*/
140
141 int Pipe = 0;
142 static char *
143 getpipepass(void)
144 {
145     static char gpbuf[BUFSIZ];
146     /* read a password from stdin, stop on \n or eof */
147     int i, tc;
148     memset(gpbuf, 0, sizeof(gpbuf));
149     for (i = 0; i < (sizeof(gpbuf) - 1); i++) {
150         tc = fgetc(stdin);
151         if (tc == '\n' || tc == EOF)
152             break;
153         gpbuf[i] = tc;
154     }
155     return gpbuf;
156 }
157
158
159 afs_int32
160 InitThisModule(void)
161 {                               /*InitThisModule */
162 #ifdef USS_KAUTH_DB
163     static char rn[] = "uss_kauth:InitThisModule";
164 #endif
165     afs_int32 code;
166     char prompt[2 * MAXKTCNAMELEN + 20];
167     char *reasonString, longPassBuff[1024], shortPassBuff[9];
168     struct ktc_encryptionKey key;
169     struct ktc_token token;
170     struct ktc_principal Name, tok;
171
172     /*
173      * Only call this routine once.
174      */
175     if (initDone)
176         return (0);
177
178
179     /*
180      * Pull out the caller's administrator token if they have one.
181      */
182     code =
183         ka_GetAdminToken(0, 0, uss_Cell, 0, 10 * 60 * 60, &token,
184                          0 /*new */ );
185     if (code) {
186         if (Pipe) {
187             strncpy(longPassBuff, getpipepass(), sizeof(longPassBuff));
188         } else {
189             /*
190              * Nope, no admin tokens available.  Get the key based on the
191              * full password and try again.
192              */
193             sprintf(prompt, "Password for '%s", uss_AccountCreator);
194             if (CreatorInstance[0])
195                 sprintf(prompt + strlen(prompt), ".%s", CreatorInstance);
196             strcat(prompt, "': ");
197             code = ka_UserReadPassword(prompt,  /*Prompt to use */
198                                        longPassBuff,    /*Long pwd buffer */
199                                        sizeof(longPassBuff),    /*Size of above */
200                                        &reasonString);
201             if (code) {
202                 afs_com_err(uss_whoami, code, "while getting password ");
203 #ifdef USS_KAUTH_DB
204                 printf("%s: Error code from ka_UserReadPassword(): %d\n", rn,
205                        code);
206 #endif /* USS_KAUTH_DB */
207                 return (code);
208             }
209         }
210         ka_StringToKey(longPassBuff, uss_Cell, &key);
211         code =
212             ka_GetAdminToken(uss_AccountCreator, CreatorInstance, uss_Cell,
213                              &key, 24 * 60 * 60, &token, 0 /*new */ );
214         if (code) {
215             if ((code == KABADREQUEST) && (strlen(longPassBuff) > 8)) {
216                 /*
217                  * The key we provided just doesn't work, yet we
218                  * suspect that since the password is greater than 8
219                  * chars, it might be the case that we really need
220                  * to truncate the password to generate the appropriate
221                  * key.
222                  */
223                 afs_com_err(uss_whoami, code,
224                         "while getting administrator token (trying shortened password next...)");
225 #ifdef USS_KAUTH_DB
226                 printf("%s: Error code from ka_GetAdminToken: %d\n", rn,
227                        code);
228 #endif /* USS_KAUTH_DB */
229                 strncpy(shortPassBuff, longPassBuff, 8);
230                 shortPassBuff[8] = 0;
231                 ka_StringToKey(shortPassBuff, uss_Cell, &key);
232                 code =
233                     ka_GetAdminToken(uss_AccountCreator, CreatorInstance,
234                                      uss_Cell, &key, 24 * 60 * 60, &token,
235                                      0 /*new */ );
236                 if (code) {
237                     afs_com_err(uss_whoami, code,
238                             "while getting administrator token (possibly wrong password, or not an administrative account)");
239 #ifdef USS_KAUTH_DB
240                     printf("%s: Error code from ka_GetAdminToken: %d\n", rn,
241                            code);
242 #endif /* USS_KAUTH_DB */
243                     return (code);
244                 } else {
245                     /*
246                      * The silly administrator has a long password!  Tell
247                      * him or her off in a polite way.
248                      */
249                     printf
250                         ("%s: Shortened password accepted by the Authentication Server\n",
251                          uss_whoami);
252                 }
253             } /*Try a shorter password */
254             else {
255                 /*
256                  * We failed to get an admin token, but the password is
257                  * of a reasonable length, so we're just hosed.
258                  */
259                 afs_com_err(uss_whoami, code,
260                         "while getting administrator token (possibly wrong password, or not an administrative account)");
261 #ifdef USS_KAUTH_DB
262                 printf("%s: Error code from ka_GetAdminToken: %d\n", rn,
263                        code);
264 #endif /* USS_KAUTH_DB */
265                 return (code);
266             }                   /*Even the shorter password didn't work */
267         }                       /*Key from given password didn't work */
268     }
269
270     /*First attempt to get admin token failed */
271     /*
272      * At this point, we have acquired an administrator token.  Let's
273      * proceed to set up a connection to the AuthServer.
274      */
275 #ifdef USS_KAUTH_DB_INSTANCE
276     fprintf(stderr,
277             "%s: CreatorInstance after ka_GetAdminToken(): '%s', %d bytes\n",
278             rn, CreatorInstance, strlen(CreatorInstance));
279 #endif /* USS_KAUTH_DB_INSTANCE */
280
281     /*
282      * Set up the connection to the AuthServer read/write site.
283      */
284     code =
285         ka_AuthServerConn(uss_Cell, KA_MAINTENANCE_SERVICE, &token,
286                           &uconn_kauthP);
287     if (code) {
288         afs_com_err(uss_whoami, code,
289                 "while establishing Authentication Server connection");
290 #ifdef USS_KAUTH_DB
291         printf("%s: Error code from ka_AuthServerConn: %d\n", rn, code);
292 #endif /* USS_KAUTH_DB */
293         return (code);
294     }
295
296     if (uss_Administrator[0]) {
297         /*
298          * We must check to see if we have local tokens for admin since he'll may do
299          * various pioctl or calls to protection server that require tokens. Remember
300          * to remove this tokens at the end of the program...
301          */
302         strcpy(Name.name, "afs");
303         Name.instance[0] = '\0';
304         strncpy(Name.cell, uss_Cell, sizeof(Name.cell));
305         if ((code =
306             ktc_GetToken(&Name, &token, sizeof(struct ktc_token), &tok))) {
307             code =
308                 ka_UserAuthenticateLife(0, uss_AccountCreator,
309                                         CreatorInstance, uss_Cell,
310                                         longPassBuff, 10 * 60 * 60,
311                                         &reasonString);
312             if (!code)
313                 doUnlog = 1;
314         }
315     }
316
317     /*
318      * Declare our success.
319      */
320     initDone = 1;
321     return (0);
322
323 }                               /*InitThisModule */
324
325
326 /*-----------------------------------------------------------------------
327  * EXPORTED uss_kauth_AddUser
328  *
329  * Environment:
330  *      The uconn_kauthP variable may already be set to an AuthServer
331  *      connection.
332  *
333  * Side Effects:
334  *      As advertised.
335  *------------------------------------------------------------------------*/
336
337 afs_int32
338 uss_kauth_AddUser(char *a_user, char *a_passwd)
339 {                               /*uss_kauth_AddUser */
340 #ifdef USS_KAUTH_DB
341     static char rn[] = "uss_kauth_AddUser";     /*Routine name */
342 #endif
343     struct ktc_encryptionKey ktc_key;
344     EncryptionKey key;
345     afs_int32 code;
346
347     if (uss_SkipKaserver) {
348         /*
349          * Don't talk to the kaserver; assume calls succeded and simply return.
350          * Amasingly people want to update it (most likely kerberos) themselves...
351          */
352         if (uss_verbose)
353             printf
354                 ("[Skip Kaserver option - Adding of user %s in Authentication DB not done]\n",
355                  a_user);
356         return 0;
357     }
358
359
360     /*
361      * Make sure the module has been initialized before we start trying
362      * to talk to AuthServers.
363      */
364     if (!initDone) {
365         code = InitThisModule();
366         if (code)
367             exit(code);
368     }
369
370     /*
371      * Given the (unencrypted) password and cell, generate a key to
372      * pass to the AuthServer.
373      */
374     ka_StringToKey(a_passwd, uss_Cell, &ktc_key);
375
376     memcpy(&key, &ktc_key, sizeof(key)); /* XXX - we could just cast */
377
378     if (!uss_DryRun) {
379         if (uss_verbose)
380             fprintf(stderr, "Adding user '%s' to the Authentication DB\n",
381                     a_user);
382
383 #ifdef USS_KAUTH_DB_INSTANCE
384         fprintf(stderr,
385                 "%s: KAM_CreateUser: user='%s', CreatorInstance='%s', %d bytes\n",
386                 rn, a_user, CreatorInstance, strlen(CreatorInstance));
387 #endif /* USS_KAUTH_DB_INSTANCE */
388         code = ubik_KAM_CreateUser(uconn_kauthP, 0, a_user,
389                                    UserInstance,        /*set by CheckUsername() */
390                                    key);
391         if (code) {
392             if (code == KAEXIST) {
393                 if (uss_verbose)
394                     fprintf(stderr,
395                             "%s: Warning: User '%s' already in Authentication DB\n",
396                             uss_whoami, a_user);
397             } else {
398                 afs_com_err(uss_whoami, code,
399                         "while adding user '%s' to Authentication DB",
400                         a_user);
401 #ifdef USS_KAUTH_DB
402                 printf("%s: Error code from KAM_CreateUser: %d\n", rn, code);
403 #endif /* USS_KAUTH_DB */
404                 return (code);
405             }
406         }                       /*KAM_CreateUser failed */
407     } /*Not a dry run */
408     else
409         fprintf(stderr,
410                 "\t[Dry run - user '%s' NOT added to Authentication DB]\n",
411                 a_user);
412
413     return (0);
414
415 }                               /*uss_kauth_AddUser */
416
417
418 /*-----------------------------------------------------------------------
419  * EXPORTED uss_kauth_DelUser
420  *
421  * Environment:
422  *      The uconn_kauthP variable may already be set to an AuthServer
423  *      connection.
424  *
425  * Side Effects:
426  *      As advertised.
427  *------------------------------------------------------------------------*/
428
429 afs_int32
430 uss_kauth_DelUser(char *a_user)
431 {                               /*uss_kauth_DelUser */
432 #ifdef USS_KAUTH_DB
433     static char rn[] = "uss_kauth_DelUser";     /*Routine name */
434 #endif
435     afs_int32 code;     /*Return code */
436
437     if (uss_SkipKaserver) {
438         /*
439          * Don't talk to the kaserver; assume calls succeded and simply return.
440          * Amasingly people want to update it (most likely kerberos) themselves...
441          */
442         if (uss_verbose)
443             printf
444                 ("[Skip Kaserver option - Deleting of user %s in Authentication DB not done]\n",
445                  a_user);
446         return 0;
447     }
448
449     /*
450      * Make sure the module has been initialized before we start trying
451      * to talk to AuthServers.
452      */
453     if (!initDone) {
454         code = InitThisModule();
455         if (code)
456             exit(code);
457     }
458
459     if (!uss_DryRun) {
460 #ifdef USS_KAUTH_DB_INSTANCE
461         printf("%s: KAM_DeleteUser: user='%s', CreatorInstance='%s'\n",
462                uss_whoami, a_user, CreatorInstance);
463 #endif /* USS_KAUTH_DB_INSTANCE */
464         if (uss_verbose)
465             printf("Deleting user '%s' from Authentication DB\n", a_user);
466         code = ubik_KAM_DeleteUser(
467                          uconn_kauthP,  /*Ubik client connection struct */
468                          0,     /*Flags */
469                          a_user,        /*User name to delete */
470                          UserInstance); /*set in CheckUserName() */
471         if (code) {
472             if (code == KANOENT) {
473                 if (uss_verbose)
474                     printf
475                         ("%s: No entry for user '%s' in Authentication DB\n",
476                          uss_whoami, a_user);
477                 return (0);
478             } else {
479                 afs_com_err(uss_whoami, code,
480                         "while deleting entry in Authentication DB\n");
481 #ifdef USS_KAUTH_DB
482                 printf("%s: Error code from KAM_DeleteUser: %d\n", rn, code);
483 #endif /* USS_KAUTH_DB */
484                 return (code);
485             }
486         }                       /*KAM_DeleteUser failed */
487     } /*Not a dry run */
488     else
489         printf("\t[Dry run - user '%s' NOT deleted from Authentication DB]\n",
490                a_user);
491
492     return (0);
493
494 }                               /*uss_kauth_DelUser */
495
496
497 /*-----------------------------------------------------------------------
498  * EXPORTED uss_kauth_CheckUserName
499  *
500  * Environment:
501  *      The user name has already been parsed and placed into
502  *      uss_User.
503  *
504  * Side Effects:
505  *      As advertised.
506  *------------------------------------------------------------------------*/
507
508 afs_int32
509 uss_kauth_CheckUserName(void)
510 {                               /*uss_kauth_CheckUserName */
511 #ifdef USS_KAUTH_DB
512     static char rn[] = "uss_kauth_CheckUserName";       /*Routine name */
513 #endif
514     afs_int32 code;     /*Return code */
515
516     if (uss_SkipKaserver) {
517         /*
518          * Don't talk to the kaserver; assume calls succeded and simply return.
519          * Amasingly people want to update it (most likely kerberos) themselves...
520          */
521         if (uss_verbose)
522             printf
523                 ("[Skip Kaserver option - Checking of user name in Authentication DB not done]\n");
524         return 0;
525     }
526
527     /*
528      * Make sure the module has been initialized before we start trying
529      * to talk to AuthServers.
530      */
531     if (!initDone) {
532         code = InitThisModule();
533         if (code)
534             exit(code);
535     }
536
537     /*
538      * Use the AuthServer's own routine to decide if the parsed user name
539      * is legal.  Specifically, it can't have any weird characters or
540      * embedded instance or cell names.
541      */
542     code = ka_ParseLoginName(uss_User, UserPrincipal, UserInstance, UserCell);
543     if (strlen(UserInstance) > 0) {
544         fprintf(stderr,
545                 "%s: User name can't have an instance string ('%s')\n",
546                 uss_whoami, UserInstance);
547         return (-1);
548     }
549     if (strlen(UserCell) > 0) {
550         fprintf(stderr, "%s: User name can't have a cell string ('%s')\n",
551                 uss_whoami, UserCell);
552         return (-1);
553     }
554     if (strchr(UserPrincipal, ':') != NULL) {
555         fprintf(stderr, "%s: User name '%s' can't have a colon\n", uss_whoami,
556                 UserPrincipal);
557         return (-1);
558     }
559     if (strlen(UserPrincipal) > 8) {
560         fprintf(stderr,
561                 "%s: User name '%s' must have 8 or fewer characters\n",
562                 uss_whoami, UserPrincipal);
563         return (-1);
564     }
565
566     /*
567      * The name's OK in my book.  Replace the user name with the parsed
568      * value.
569      */
570     strcpy(uss_User, UserPrincipal);
571     return (0);
572
573 }                               /*uss_kauth_CheckUserName */
574
575
576 /*
577  * EXPORTED uss_kauth_SetFields
578  *
579  * Environment:
580  *      The uconn_kauthP variable may already be set to an AuthServer
581  *      connection.
582  *
583  * Side Effects:
584  *      As advertised.
585  */
586
587 afs_int32
588 uss_kauth_SetFields(char *username, char *expirestring, char *reuse,
589                     char *failures, char *lockout)
590 {
591 #ifdef USS_KAUTH_DB
592     static char rn[] = "uss_kauth_SetFields";
593 #endif
594     afs_int32 code;
595     char misc_auth_bytes[4];
596     int i;
597     afs_int32 flags = 0;
598     Date expiration = 0;
599     afs_int32 lifetime = 0;
600     afs_int32 maxAssociates = -1;
601     afs_int32 was_spare = 0;
602     char instance = '\0';
603     int pwexpiry;
604     int nfailures, locktime, hrs, mins;
605
606     if (strlen(username) > uss_UserLen) {
607         fprintf(stderr,
608                 "%s: * User field in add cmd too long (max is %d chars; truncated value is '%s')\n",
609                 uss_whoami, uss_UserLen, uss_User);
610         return (-1);
611     }
612
613     strcpy(uss_User, username);
614     code = uss_kauth_CheckUserName();
615     if (code)
616         return (code);
617
618     /*  no point in doing this any sooner than necessary */
619     for (i = 0; i < 4; misc_auth_bytes[i++] = 0);
620
621     pwexpiry = atoi(expirestring);
622     if (pwexpiry < 0 || pwexpiry > 254) {
623         fprintf(stderr, "Password lifetime range must be [0..254] days.\n");
624         fprintf(stderr, "Zero represents an unlimited lifetime.\n");
625         fprintf(stderr,
626                 "Continuing with default lifetime == 0 for user %s.\n",
627                 username);
628         pwexpiry = 0;
629     }
630     misc_auth_bytes[0] = pwexpiry + 1;
631
632     if (!strcmp(reuse, "noreuse")) {
633         misc_auth_bytes[1] = KA_NOREUSEPW;
634     } else {
635         misc_auth_bytes[1] = KA_REUSEPW;
636         if (strcmp(reuse, "reuse"))
637           fprintf(stderr, "must specify \"reuse\" or \"noreuse\": \"reuse\" assumed\n");
638     }
639
640     nfailures = atoi(failures);
641     if (nfailures < 0 || nfailures > 254) {
642         fprintf(stderr, "Failure limit must be in [0..254].\n");
643         fprintf(stderr, "Zero represents unlimited login attempts.\n");
644         fprintf(stderr, "Continuing with limit == 254 for user %s.\n",
645                 username);
646         misc_auth_bytes[2] = 255;
647     } else
648         misc_auth_bytes[2] = nfailures + 1;
649
650     hrs = 0;
651     if (strchr(lockout, ':'))
652         sscanf(lockout, "%d:%d", &hrs, &mins);
653     else
654         sscanf(lockout, "%d", &mins);
655
656     locktime = hrs*60 + mins;
657     if (hrs < 0 || hrs > 36 || mins < 0) {
658         fprintf(stderr,"Lockout times must be either minutes or hh:mm.\n");
659         fprintf(stderr,"Lockout times must be less than 36 hours.\n");
660         return KABADCMD;
661     } else if (locktime > 36*60) {
662         fprintf(stderr, "Lockout times must be either minutes or hh:mm.\n");
663         fprintf(stderr, "Lockout times must be less than 36 hours.\n");
664         fprintf(stderr, "Continuing with lock time == forever for user %s.\n",
665                 username);
666         misc_auth_bytes[3] = 1;
667     } else {
668         locktime = (locktime * 60 + 511) >> 9;  /* ceil(l*60/512) */
669         misc_auth_bytes[3] = locktime + 1;
670     }
671
672     if (uss_SkipKaserver) {
673         if (uss_verbose)
674             printf("[Skipping Kaserver as requested]\n");
675         return 0;
676     }
677
678     /*
679      * Make sure the module has been initialized before we start trying
680      * to talk to AuthServers.
681      */
682     if (!initDone) {
683         code = InitThisModule();
684         if (code)
685             exit(code);
686     }
687
688     if (!uss_DryRun) {
689         if (uss_verbose)
690             fprintf(stderr, "Setting options for '%s' in database.\n",
691                     username);
692
693         was_spare = pack_long(misc_auth_bytes);
694
695         if (was_spare || flags || expiration || lifetime
696             || (maxAssociates >= 0)) {
697
698             if (!expiration)
699                 expiration = uss_Expires;
700             code =
701                 ubik_KAM_SetFields(uconn_kauthP, 0, username, &instance,
702                           flags, expiration, lifetime, maxAssociates,
703                           was_spare, /* spare */ 0);
704         } else
705             fprintf(stderr,
706                     "Must specify one of the optional parameters. Continuing...\n");
707
708         if (code) {
709             afs_com_err(uss_whoami, code, "calling KAM_SetFields for %s",
710                     username);
711
712             return (code);
713         }
714     } /*Not a dry run */
715     else
716         fprintf(stderr, "\t[Dry run - user '%s' NOT changed.]\n", username);
717
718     return (0);
719
720 }                               /*uss_kauth_SetFields */