client/cifs.spnego.c

説明を見る。
00001 /*
00002 * CIFS SPNEGO user-space helper.
00003 * Copyright (C) Igor Mammedov (niallain@gmail.com) 2007
00004 *
00005 * Used by /sbin/request-key for handling
00006 * cifs upcall for kerberos authorization of access to share and
00007 * cifs upcall for DFS srver name resolving (IPv4/IPv6 aware).
00008 * You should have keyutils installed and add following line to
00009 * /etc/request-key.conf file
00010 
00011 create cifs.spnego * * /usr/local/sbin/cifs.spnego [-v][-c] %k
00012 create cifs.resolver * * /usr/local/sbin/cifs.spnego [-v] %k
00013 
00014 * This program is free software; you can redistribute it and/or modify
00015 * it under the terms of the GNU General Public License as published by
00016 * the Free Software Foundation; either version 2 of the License, or
00017 * (at your option) any later version.
00018 * This program is distributed in the hope that it will be useful,
00019 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00021 * GNU General Public License for more details.
00022 * You should have received a copy of the GNU General Public License
00023 * along with this program; if not, write to the Free Software
00024 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
00025 */
00026 
00027 #include "includes.h"
00028 #include <keyutils.h>
00029 
00030 #include "cifs_spnego.h"
00031 
00032 const char *CIFSSPNEGO_VERSION = "1.1";
00033 static const char *prog = "cifs.spnego";
00034 typedef enum _secType {
00035         KRB5,
00036         MS_KRB5
00037 } secType_t;
00038 
00039 /*
00040  * Prepares AP-REQ data for mechToken and gets session key
00041  * Uses credentials from cache. It will not ask for password
00042  * you should receive credentials for yuor name manually using
00043  * kinit or whatever you wish.
00044  *
00045  * in:
00046  *      oid -           string with OID/ Could be OID_KERBEROS5
00047  *                      or OID_KERBEROS5_OLD
00048  *      principal -     Service name.
00049  *                      Could be "cifs/FQDN" for KRB5 OID
00050  *                      or for MS_KRB5 OID style server principal
00051  *                      like "pdc$@YOUR.REALM.NAME"
00052  *
00053  * out:
00054  *      secblob -       pointer for spnego wrapped AP-REQ data to be stored
00055  *      sess_key-       pointer for SessionKey data to be stored
00056  *
00057  * ret: 0 - success, others - failure
00058 */
00059 int handle_krb5_mech(const char *oid, const char *principal,
00060                      DATA_BLOB * secblob, DATA_BLOB * sess_key)
00061 {
00062         int retval;
00063         DATA_BLOB tkt, tkt_wrapped;
00064 
00065         /* get a kerberos ticket for the service and extract the session key */
00066         retval = cli_krb5_get_ticket(principal, 0,
00067                                      &tkt, sess_key, 0, NULL, NULL);
00068 
00069         if (retval)
00070                 return retval;
00071 
00072         /* wrap that up in a nice GSS-API wrapping */
00073         tkt_wrapped = spnego_gen_krb5_wrap(tkt, TOK_ID_KRB_AP_REQ);
00074 
00075         /* and wrap that in a shiny SPNEGO wrapper */
00076         *secblob = gen_negTokenInit(OID_KERBEROS5, tkt_wrapped);
00077 
00078         data_blob_free(&tkt_wrapped);
00079         data_blob_free(&tkt);
00080         return retval;
00081 }
00082 
00083 #define DKD_HAVE_HOSTNAME       1
00084 #define DKD_HAVE_VERSION        2
00085 #define DKD_HAVE_SEC            4
00086 #define DKD_HAVE_IPV4           8
00087 #define DKD_HAVE_IPV6           16
00088 #define DKD_HAVE_UID            32
00089 #define DKD_MUSTHAVE_SET (DKD_HAVE_HOSTNAME|DKD_HAVE_VERSION|DKD_HAVE_SEC)
00090 
00091 int decode_key_description(const char *desc, int *ver, secType_t * sec,
00092                            char **hostname, uid_t * uid)
00093 {
00094         int retval = 0;
00095         char *pos;
00096         const char *tkn = desc;
00097 
00098         do {
00099                 pos = index(tkn, ';');
00100                 if (strncmp(tkn, "host=", 5) == 0) {
00101                         int len;
00102 
00103                         if (pos == NULL) {
00104                                 len = strlen(tkn);
00105                         } else {
00106                                 len = pos - tkn;
00107                         }
00108                         len -= 4;
00109                         SAFE_FREE(*hostname);
00110                         *hostname = SMB_XMALLOC_ARRAY(char, len);
00111                         strlcpy(*hostname, tkn + 5, len);
00112                         retval |= DKD_HAVE_HOSTNAME;
00113                 } else if (strncmp(tkn, "ipv4=", 5) == 0) {
00114                         /* BB: do we need it if we have hostname already? */
00115                 } else if (strncmp(tkn, "ipv6=", 5) == 0) {
00116                         /* BB: do we need it if we have hostname already? */
00117                 } else if (strncmp(tkn, "sec=", 4) == 0) {
00118                         if (strncmp(tkn + 4, "krb5", 4) == 0) {
00119                                 retval |= DKD_HAVE_SEC;
00120                                 *sec = KRB5;
00121                         }
00122                 } else if (strncmp(tkn, "uid=", 4) == 0) {
00123                         errno = 0;
00124                         *uid = strtol(tkn + 4, NULL, 16);
00125                         if (errno != 0) {
00126                                 syslog(LOG_WARNING, "Invalid uid format: %s",
00127                                        strerror(errno));
00128                                 return 1;
00129                         } else {
00130                                 retval |= DKD_HAVE_UID;
00131                         }
00132                 } else if (strncmp(tkn, "ver=", 4) == 0) {      /* if version */
00133                         errno = 0;
00134                         *ver = strtol(tkn + 4, NULL, 16);
00135                         if (errno != 0) {
00136                                 syslog(LOG_WARNING,
00137                                        "Invalid version format: %s",
00138                                        strerror(errno));
00139                                 return 1;
00140                         } else {
00141                                 retval |= DKD_HAVE_VERSION;
00142                         }
00143                 }
00144                 if (pos == NULL)
00145                         break;
00146                 tkn = pos + 1;
00147         } while (tkn);
00148         return retval;
00149 }
00150 
00151 int cifs_resolver(const key_serial_t key, const char *key_descr)
00152 {
00153         int c;
00154         struct addrinfo *addr;
00155         char ip[INET6_ADDRSTRLEN];
00156         void *p;
00157         const char *keyend = key_descr;
00158         /* skip next 4 ';' delimiters to get to description */
00159         for (c = 1; c <= 4; c++) {
00160                 keyend = index(keyend+1, ';');
00161                 if (!keyend) {
00162                         syslog(LOG_WARNING, "invalid key description: %s",
00163                                         key_descr);
00164                         return 1;
00165                 }
00166         }
00167         keyend++;
00168 
00169         /* resolve name to ip */
00170         c = getaddrinfo(keyend, NULL, NULL, &addr);
00171         if (c) {
00172                 syslog(LOG_WARNING, "unable to resolve hostname: %s [%s]",
00173                                 keyend, gai_strerror(c));
00174                 return 1;
00175         }
00176 
00177         /* conver ip to string form */
00178         if (addr->ai_family == AF_INET) {
00179                 p = &(((struct sockaddr_in *)addr->ai_addr)->sin_addr);
00180         } else {
00181                 p = &(((struct sockaddr_in6 *)addr->ai_addr)->sin6_addr);
00182         }
00183         if (!inet_ntop(addr->ai_family, p, ip, sizeof(ip))) {
00184                 syslog(LOG_WARNING, "%s: inet_ntop: %s",
00185                                 __FUNCTION__, strerror(errno));
00186                 freeaddrinfo(addr);
00187                 return 1;
00188         }
00189 
00190         /* setup key */
00191         c = keyctl_instantiate(key, ip, strlen(ip)+1, 0);
00192         if (c == -1) {
00193                 syslog(LOG_WARNING, "%s: keyctl_instantiate: %s",
00194                                 __FUNCTION__, strerror(errno));
00195                 freeaddrinfo(addr);
00196                 return 1;
00197         }
00198 
00199         freeaddrinfo(addr);
00200         return 0;
00201 }
00202 
00203 int main(const int argc, char *const argv[])
00204 {
00205         struct cifs_spnego_msg *keydata = NULL;
00206         DATA_BLOB secblob = data_blob_null;
00207         DATA_BLOB sess_key = data_blob_null;
00208         secType_t sectype;
00209         key_serial_t key;
00210         size_t datalen;
00211         long rc = 1;
00212         uid_t uid;
00213         int kernel_upcall_version;
00214         int c, use_cifs_service_prefix = 0;
00215         char *buf, *hostname = NULL;
00216 
00217         openlog(prog, 0, LOG_DAEMON);
00218         if (argc < 1) {
00219                 syslog(LOG_WARNING, "Usage: %s [-c] key_serial", prog);
00220                 goto out;
00221         }
00222 
00223         while ((c = getopt(argc, argv, "cv")) != -1) {
00224                 switch (c) {
00225                 case 'c':{
00226                         use_cifs_service_prefix = 1;
00227                         break;
00228                         }
00229                 case 'v':{
00230                         syslog(LOG_WARNING, "version: %s", CIFSSPNEGO_VERSION);
00231                         fprintf(stderr, "version: %s", CIFSSPNEGO_VERSION);
00232                         break;
00233                         }
00234                 default:{
00235                         syslog(LOG_WARNING, "unknow option: %c", c);
00236                         goto out;
00237                         }
00238                 }
00239         }
00240         /* get key and keyring values */
00241         errno = 0;
00242         key = strtol(argv[optind], NULL, 10);
00243         if (errno != 0) {
00244                 syslog(LOG_WARNING, "Invalid key format: %s", strerror(errno));
00245                 goto out;
00246         }
00247 
00248         rc = keyctl_describe_alloc(key, &buf);
00249         if (rc == -1) {
00250                 syslog(LOG_WARNING, "keyctl_describe_alloc failed: %s",
00251                        strerror(errno));
00252                 rc = 1;
00253                 goto out;
00254         }
00255 
00256         if (strncmp(buf, "cifs.resolver", sizeof("cifs.resolver")-1) == 0) {
00257                 rc = cifs_resolver(key, buf);
00258                 goto out;
00259         }
00260 
00261         rc = decode_key_description(buf, &kernel_upcall_version, &sectype,
00262                                     &hostname, &uid);
00263         if ((rc & DKD_MUSTHAVE_SET) != DKD_MUSTHAVE_SET) {
00264                 syslog(LOG_WARNING,
00265                        "unable to get from description necessary params");
00266                 rc = 1;
00267                 SAFE_FREE(buf);
00268                 goto out;
00269         }
00270         SAFE_FREE(buf);
00271 
00272         if (kernel_upcall_version != CIFS_SPNEGO_UPCALL_VERSION) {
00273                 syslog(LOG_WARNING,
00274                        "incompatible kernel upcall version: 0x%x",
00275                        kernel_upcall_version);
00276                 rc = 1;
00277                 goto out;
00278         }
00279 
00280         if (rc & DKD_HAVE_UID) {
00281                 rc = setuid(uid);
00282                 if (rc == -1) {
00283                         syslog(LOG_WARNING, "setuid: %s", strerror(errno));
00284                         goto out;
00285                 }
00286         }
00287 
00288         /* BB: someday upcall SPNEGO blob could be checked here to decide
00289          * what mech to use */
00290 
00291         // do mech specific authorization
00292         switch (sectype) {
00293         case KRB5:{
00294                         char *princ;
00295                         size_t len;
00296 
00297                         /* for "cifs/" service name + terminating 0 */
00298                         len = strlen(hostname) + 5 + 1;
00299                         princ = SMB_XMALLOC_ARRAY(char, len);
00300                         if (!princ) {
00301                                 rc = 1;
00302                                 break;
00303                         }
00304                         if (use_cifs_service_prefix) {
00305                                 strlcpy(princ, "cifs/", len);
00306                         } else {
00307                                 strlcpy(princ, "host/", len);
00308                         }
00309                         strlcpy(princ + 5, hostname, len - 5);
00310 
00311                         rc = handle_krb5_mech(OID_KERBEROS5, princ,
00312                                               &secblob, &sess_key);
00313                         SAFE_FREE(princ);
00314                         break;
00315                 }
00316         default:{
00317                         syslog(LOG_WARNING, "sectype: %d is not implemented",
00318                                sectype);
00319                         rc = 1;
00320                         break;
00321                 }
00322         }
00323 
00324         if (rc) {
00325                 goto out;
00326         }
00327 
00328         /* pack SecurityBLob and SessionKey into downcall packet */
00329         datalen =
00330             sizeof(struct cifs_spnego_msg) + secblob.length + sess_key.length;
00331         keydata = (struct cifs_spnego_msg*)SMB_XMALLOC_ARRAY(char, datalen);
00332         if (!keydata) {
00333                 rc = 1;
00334                 goto out;
00335         }
00336         keydata->version = CIFS_SPNEGO_UPCALL_VERSION;
00337         keydata->flags = 0;
00338         keydata->sesskey_len = sess_key.length;
00339         keydata->secblob_len = secblob.length;
00340         memcpy(&(keydata->data), sess_key.data, sess_key.length);
00341         memcpy(&(keydata->data) + keydata->sesskey_len,
00342                secblob.data, secblob.length);
00343 
00344         /* setup key */
00345         rc = keyctl_instantiate(key, keydata, datalen, 0);
00346         if (rc == -1) {
00347                 syslog(LOG_WARNING, "keyctl_instantiate: %s", strerror(errno));
00348                 goto out;
00349         }
00350 
00351         /* BB: maybe we need use timeout for key: for example no more then
00352          * ticket lifietime? */
00353         /* keyctl_set_timeout( key, 60); */
00354       out:
00355         data_blob_free(&secblob);
00356         data_blob_free(&sess_key);
00357         SAFE_FREE(hostname);
00358         SAFE_FREE(keydata);
00359         return rc;
00360 }

Sambaに対してSat Aug 29 21:22:53 2009に生成されました。  doxygen 1.4.7