/* $Id$ * * Check access to queries * * Copyright (C) 1987-1998 by the Massachusetts Institute of Technology * For copying and distribution information, please see the file * . */ #include #include "mr_server.h" #include "qrtn.h" #include "query.h" #include #include EXEC SQL INCLUDE sqlca; RCSID("$Header$"); extern char *whoami; extern int dbms_errno, mr_errcode; EXEC SQL WHENEVER SQLERROR DO dbmserr(); /* Specialized Access Routines */ /* access_user - verify that client name equals specified login name * * - since field validation routines are called first, a users_id is * now in argv[0] instead of the login name. */ int access_user(struct query *q, char *argv[], client *cl) { if (cl->users_id != *(int *)argv[0]) return MR_PERM; else return MR_SUCCESS; } /* access_login - verify that client name equals specified login name * * argv[0...n] contain search info. q-> */ int access_login(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int id; EXEC SQL END DECLARE SECTION; if (q->argc != 1) return MR_ARGS; if (!strcmp(q->shortname, "gual")) { EXEC SQL SELECT users_id INTO :id FROM users WHERE login = :argv[0] AND users_id != 0; } else if (!strcmp(q->shortname, "gubl")) { EXEC SQL SELECT users_id INTO :id FROM users u WHERE u.login = :argv[0] AND u.users_id != 0; } else if (!strcmp(q->shortname, "guau")) { EXEC SQL SELECT users_id INTO :id FROM users WHERE unix_uid = :argv[0] AND users_id != 0; } else if (!strcmp(q->shortname, "gubu")) { EXEC SQL SELECT users_id INTO :id FROM users u WHERE u.unix_uid = :argv[0] AND u.users_id != 0; } if (sqlca.sqlcode == SQL_NO_MATCH) return MR_NO_MATCH; /* ought to be MR_USER, but this is what gual returns, so we have to be consistent */ else if (sqlca.sqlerrd[2] != 1 || id != cl->users_id) return MR_PERM; else return MR_SUCCESS; } /* access_spob - check access for set_pobox */ int access_spob(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int id; EXEC SQL END DECLARE SECTION; if (!strcmp(argv[1], "IMAP")) { EXEC SQL SELECT owner INTO :id FROM filesys f WHERE f.label = :argv[2] AND f.type = 'IMAP' AND f.lockertype = 'USER'; if (cl->users_id != id) return MR_PERM; } if (cl->users_id != *(int *)argv[0]) return MR_PERM; else return MR_SUCCESS; } /* access_list - check access for most list operations * * Inputs: argv[0] - list_id * q - query name * argv[2] - member ID (only for queries "amtl" and "dmfl") * argv[7] - group ID (only for query "ulis") * cl - client name * * - check that client is a member of the access control list * - OR, if the query is add_member_to_list or delete_member_from_list * and the list is public, allow access if client = member */ int access_list(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int list_id, acl_id, flags, gid, users_id, member_id, member_acl_id; int memacl_id; char acl_type[LIST_ACL_TYPE_SIZE], name[LIST_NAME_SIZE], *newname; char member_acl_type[LIST_ACL_TYPE_SIZE], memacl_type[LIST_ACL_TYPE_SIZE]; EXEC SQL END DECLARE SECTION; int status; list_id = *(int *)argv[0]; member_id = *(int *)argv[2]; EXEC SQL SELECT acl_id, acl_type, memacl_id, memacl_type, gid, publicflg, name INTO :acl_id, :acl_type, :memacl_id, :memacl_type, :gid, :flags, :name FROM list WHERE list_id = :list_id; if (sqlca.sqlerrd[2] != 1) return MR_INTERNAL; /* if update_list, don't allow them to change the GID or rename to a username other than their own */ if (!strcmp("ulis", q->shortname)) { if (!strcmp(argv[7], UNIQUE_GID)) { if (gid != -1) return MR_PERM; } else { if (gid != atoi(argv[7])) return MR_PERM; } newname = argv[1]; if (!strcmp("ulis", q->shortname)) { /* Check that it doesn't conflict with the Grouper namespace. */ if (strlen(newname) > 4 && isdigit(newname[2]) && isdigit(newname[3]) && newname[4] == '-') { if (!strncasecmp(newname, "fa", 2) || !strncasecmp(newname, "sp", 2) || !strncasecmp(newname, "su", 2) || !strncasecmp(newname, "ja", 2)) return MR_RESERVED; } /* Don't let anyone take owner-foo list names. They interact * weirdly with the aliases automatically generated by * mailhub.gen. */ if (!strncasecmp(newname, "owner-", 6)) return MR_RESERVED; } EXEC SQL SELECT users_id INTO :users_id FROM users WHERE login = :newname; if ((sqlca.sqlcode != SQL_NO_MATCH) && strcmp(strtrim(name), newname) && (users_id != cl->users_id)) return MR_PERM; } /* check for client in access control list and return success right * away if it's there. */ if (find_member(acl_type, acl_id, cl)) return MR_SUCCESS; /* If not amtl, atml, or dmfl, we lose. */ if (strcmp(q->shortname, "amtl") && strcmp(q->shortname, "atml") && strcmp(q->shortname, "dmfl") && strcmp(q->shortname, "tmol")) return MR_PERM; if (find_member(memacl_type, memacl_id, cl)) return MR_SUCCESS; if (flags || q->type == DELETE) { if (!strcmp("USER", argv[1]) && *(int *)argv[2] == cl->users_id) return MR_SUCCESS; if (!strcmp("KERBEROS", argv[1]) && *(int *)argv[2] == -cl->client_id) return MR_SUCCESS; if (!strcmp("LIST", argv[1]) && !strcmp("dmfl", q->shortname)) { EXEC SQL SELECT acl_id, acl_type INTO :member_acl_id, :member_acl_type FROM list WHERE list_id = :member_id; if (find_member(member_acl_type, member_acl_id, cl)) return MR_SUCCESS; } } /* Otherwise fail. */ return MR_PERM; } /* access_visible_list - allow access to list only if it is not hidden, * or if the client is on the ACL * * Inputs: argv[0] - list_id * cl - client identifier */ int access_visible_list(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int list_id, acl_id, memacl_id, flags ; char acl_type[LIST_ACL_TYPE_SIZE], memacl_type[LIST_ACL_TYPE_SIZE]; EXEC SQL END DECLARE SECTION; int status; list_id = *(int *)argv[0]; EXEC SQL SELECT hidden, acl_id, acl_type, memacl_id, memacl_type INTO :flags, :acl_id, :acl_type, :memacl_id, :memacl_type FROM list WHERE list_id = :list_id; if (sqlca.sqlerrd[2] != 1) return MR_INTERNAL; if (!flags) return MR_SUCCESS; /* check for client in access control list */ status = find_member(acl_type, acl_id, cl); if (!status) { status = find_member(memacl_type, memacl_id, cl); if (!status) return MR_PERM; } return MR_SUCCESS; } /* access_vis_list_by_name - allow access to list only if it is not hidden, * or if the client is on the ACL * * Inputs: argv[0] - list name * cl - client identifier */ int access_vis_list_by_name(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int acl_id, memacl_id, flags, rowcount; char acl_type[LIST_ACL_TYPE_SIZE], memacl_type[LIST_ACL_TYPE_SIZE]; char *listname; EXEC SQL END DECLARE SECTION; int status; listname = argv[0]; EXEC SQL SELECT hidden, acl_id, acl_type, memacl_id, memacl_type INTO :flags, :acl_id, :acl_type, :memacl_id, :memacl_type FROM list WHERE name = :listname; rowcount = sqlca.sqlerrd[2]; if (rowcount > 1) return MR_WILDCARD; if (rowcount == 0) return MR_NO_MATCH; if (!flags) return MR_SUCCESS; /* check for client in access control list */ status = find_member(acl_type, acl_id, cl); if (!status) { status = find_member(memacl_type, memacl_id, cl); if (!status) return MR_PERM; } return MR_SUCCESS; } /* access_member - allow user to access member of type "USER" and name matches * username, or to access member of type "KERBEROS" and the principal matches * the user, or to access member of type "LIST" and list is one that user is * on the acl of, or the list is visible. */ int access_member(struct query *q, char *argv[], client *cl) { if (!strcmp(argv[0], "LIST") || !strcmp(argv[0], "RLIST")) return access_visible_list(q, &argv[1], cl); if (!strcmp(argv[0], "USER") || !strcmp(argv[0], "RUSER")) { if (cl->users_id == *(int *)argv[1]) return MR_SUCCESS; } if (!strcmp(argv[0], "KERBEROS") || !strcmp(argv[0], "RKERBEROS")) { if (cl->client_id == -*(int *)argv[1]) return MR_SUCCESS; } return MR_PERM; } /* access_qgli - special access routine for Qualified_get_lists. Allows * access iff argv[0] == "TRUE" and argv[2] == "FALSE". */ int access_qgli(struct query *q, char *argv[], client *cl) { if (!strcmp(argv[0], "TRUE") && !strcmp(argv[2], "FALSE")) return MR_SUCCESS; return MR_PERM; } /* access_service - allow access if user is on ACL of service. Don't * allow access if a wildcard is used. */ int access_service(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int acl_id; char *name, acl_type[LIST_ACL_TYPE_SIZE]; EXEC SQL END DECLARE SECTION; int status; char *c; name = argv[0]; for (c = name; *c; c++) { if (islower(*c)) *c = toupper(*c); } EXEC SQL SELECT acl_id, acl_type INTO :acl_id, :acl_type FROM servers WHERE name = :name; if (sqlca.sqlerrd[2] > 1) return MR_PERM; /* check for client in access control list */ status = find_member(acl_type, acl_id, cl); if (!status) return MR_PERM; return MR_SUCCESS; } /* access_filesys - verify that client is owner or on owners list of filesystem * named by argv[0] */ int access_filesys(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int users_id, list_id; char *name; EXEC SQL END DECLARE SECTION; int status; name = argv[0]; EXEC SQL SELECT owner, owners INTO :users_id, :list_id FROM filesys WHERE label = :name; if (sqlca.sqlerrd[2] != 1) return MR_PERM; if (users_id == cl->users_id) return MR_SUCCESS; status = find_member("LIST", list_id, cl); if (status) return MR_SUCCESS; else return MR_PERM; } /* access_host - successful if owner of host, or subnet containing host */ int access_host(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int mid, sid, id, subnet_status; char mtype[MACHINE_OWNER_TYPE_SIZE], stype[SUBNET_OWNER_TYPE_SIZE]; char *account_number; EXEC SQL END DECLARE SECTION; int status, idx; if (q->version < 6) idx = 0; else if (q->version >= 6 && q->version < 8) idx = 1; else idx = 2; if (q->type == RETRIEVE) { if (strcmp(argv[0], "*") || strcmp(argv[1], "*") || strcmp(argv[2], "*") || strcmp(argv[3], "*")) return MR_SUCCESS; else return MR_PERM; } if (q->type == APPEND) { /* Non-query owner must set use to zero */ if (atoi(argv[6 + idx]) != 0) return MR_PERM; /* ... and start the hostname with a letter */ if (isdigit(argv[0][0])) return MR_BAD_CHAR; id = *(int *)argv[8 + idx]; EXEC SQL SELECT s.owner_type, s.owner_id, s.status INTO :stype, :sid, :subnet_status FROM subnet s WHERE s.snet_id = :id; mid = 0; /* Non query owner must provide valid billing information. */ if (q->version >= 8) { if (subnet_status == SNET_STATUS_BILLABLE) { account_number = argv[7]; EXEC SQL SELECT account_number FROM accountnumbers WHERE account_number = :account_number; if (sqlca.sqlcode == SQL_NO_MATCH) return MR_ACCOUNT_NUMBER; } } if (find_member(stype, sid, cl)) return MR_SUCCESS; else return MR_PERM; } else /* q-type == UPDATE */ { EXEC SQL BEGIN DECLARE SECTION; int status, acomment, use, ocomment, snid; char contact[MACHINE_CONTACT_SIZE], address[MACHINE_ADDRESS_SIZE]; char name[MACHINE_NAME_SIZE]; char billing_contact[MACHINE_BILLING_CONTACT_SIZE]; EXEC SQL END DECLARE SECTION; id = *(int *)argv[0]; EXEC SQL SELECT m.name, m.use, m.contact, m.billing_contact, m.status, m.address, m.owner_type, m.owner_id, m.acomment, m.ocomment, m.snet_id, s.owner_type, s.owner_id, s.status INTO :name, :use, :contact, :billing_contact, :status, :address, :mtype, :mid, :acomment, :ocomment, :snid, :stype, :sid, :subnet_status FROM machine m, subnet s WHERE m.mach_id = :id AND s.snet_id = m.snet_id; if (dbms_errno) return mr_errcode; /* Non query owner must provide valid billing information. */ if (q->version >= 8) { if (subnet_status == SNET_STATUS_BILLABLE) { account_number = argv[8]; EXEC SQL SELECT account_number FROM accountnumbers WHERE account_number = :account_number; if (sqlca.sqlcode == SQL_NO_MATCH) return MR_ACCOUNT_NUMBER; } } /* non-query-owner cannot change use or ocomment */ if ((use != atoi(argv[7 + idx])) || (ocomment != *(int *)argv[14 + idx])) return MR_PERM; /* or rename to start with digit */ if (isdigit(argv[1][0]) && strcmp(strtrim(name), argv[1])) return MR_BAD_CHAR; if (!find_member(stype, sid, cl)) { if (find_member(mtype, mid, cl)) { /* host owner also cannot change contact, status, address, owner, or acomment */ if (strcmp(argv[6], strtrim(contact)) || (status != atoi(argv[8 + idx])) || strcmp(argv[10 + idx], strtrim(address)) || strcmp(argv[11 + idx], strtrim(mtype)) || (mid != *(int *)argv[12 + idx]) || (acomment != *(int *)argv[13 + idx])) return MR_PERM; /* Billing contact field didn't appear until version 6 */ if (q->version >= 6) if (strcmp(argv[7], strtrim(billing_contact))) return MR_PERM; } else return MR_PERM; } /* If moving to a new subnet, make sure user is on acl there */ id = *(int *)argv[9 + idx]; if (id != snid) { EXEC SQL SELECT owner_type, owner_id INTO :stype, :sid FROM subnet WHERE snet_id=:id; if (!find_member(stype, sid, cl)) return MR_PERM; } return MR_SUCCESS; } } /* access_ahal - check for adding a host alias. * successful if host has less then 2 aliases and (client is owner of * host or subnet). * If deleting an alias, any owner will do. */ int access_ahal(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int cnt, id, mid, sid; char mtype[MACHINE_OWNER_TYPE_SIZE], stype[SUBNET_OWNER_TYPE_SIZE]; EXEC SQL END DECLARE SECTION; int status; if (q->type == RETRIEVE) return MR_SUCCESS; id = *(int *)argv[1]; if (q->type == APPEND && isdigit(argv[0][0])) return MR_BAD_CHAR; EXEC SQL SELECT count(name) INTO :cnt from hostalias WHERE mach_id = :id; if (dbms_errno) return mr_errcode; /* if the type is APPEND, this is ahal and we need to make sure there * will be no more than 2 aliases. If it's not, it must be dhal and * any owner will do. */ if (q->type == APPEND && cnt >= 2) return MR_PERM; EXEC SQL SELECT m.owner_type, m.owner_id, s.owner_type, s.owner_id INTO :mtype, :mid, :stype, :sid FROM machine m, subnet s WHERE m.mach_id = :id and s.snet_id = m.snet_id; status = find_member(mtype, mid, cl); if (status) return MR_SUCCESS; status = find_member(stype, sid, cl); if (status) return MR_SUCCESS; else return MR_PERM; } /* access_snt - check for retrieving network structure */ int access_snt(struct query *q, char *argv[], client *cl) { if (q->type == RETRIEVE) return MR_SUCCESS; return MR_PERM; } /* access_printer */ int access_printer(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; char type[PRINTSERVERS_OWNER_TYPE_SIZE]; int id, mach_id; EXEC SQL END DECLARE SECTION; int status; mach_id = *(int *)argv[PRN_RM]; EXEC SQL SELECT owner_type, owner_id INTO :type, :id FROM printservers WHERE mach_id = :mach_id; if (sqlca.sqlcode) return MR_PERM; status = find_member(type, id, cl); if (status) return MR_SUCCESS; else return MR_PERM; } /* access_zephyr */ int access_zephyr(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; char type[ZEPHYR_OWNER_TYPE_SIZE]; char *class; int id; EXEC SQL END DECLARE SECTION; int status; class = argv[ZA_CLASS]; EXEC SQL SELECT owner_type, owner_id INTO :type, :id FROM zephyr WHERE class = :class; if (sqlca.sqlcode) return MR_PERM; status = find_member(type, id, cl); if (status) return MR_SUCCESS; else return MR_PERM; } /* access_container - check access for most container operations * * Inputs: argv[0] - cnt_id * q - query name * cl - client name * * - check if that client is a member of the access control list * - OR, if the query is add_machine_to_container or delete_machine_from_container * check if the client is a memeber of the mem_acl list * - if the query is update_container and the container is to be renamed and * it is a top-level container, only priviledged users can do it */ int access_container(struct query *q, char *argv[], client *cl) { EXEC SQL BEGIN DECLARE SECTION; int cnt_id, acl_id, memacl_id; char acl_type[CONTAINERS_ACL_TYPE_SIZE], memacl_type[CONTAINERS_ACL_TYPE_SIZE]; char name[CONTAINERS_NAME_SIZE], *newname; EXEC SQL END DECLARE SECTION; int status; cnt_id = *(int *)argv[0]; /* if amcn or dmcn, container id is the second argument */ if (strcmp(q->shortname, "amcn") == 0 || strcmp(q->shortname, "dmcn") == 0) cnt_id = *(int *)argv[1]; EXEC SQL SELECT acl_id, acl_type, memacl_id, memacl_type, name INTO :acl_id, :acl_type, :memacl_id, :memacl_type, :name FROM containers WHERE cnt_id = :cnt_id; if (sqlca.sqlerrd[2] != 1) return MR_INTERNAL; /* trim off the trailing spaces */ strcpy(name, strtrim(name)); /* if the query is update_container and the containers is to be renamed * and it is a top-level container, only dbadmin can do it */ if (!strcmp(q->shortname, "ucon")) { newname = argv[1]; if (strcmp(name, newname) && strchr(name, '/') == NULL) return MR_PERM; } /* check for client in access control list and return success right * away if it's there. */ if (find_member(acl_type, acl_id, cl)) return MR_SUCCESS; /* If not amcn, dmcn, we lose. */ if (strcmp(q->shortname, "amcn") && strcmp(q->shortname, "dmcn")) return MR_PERM; if (find_member(memacl_type, memacl_id, cl)) return MR_SUCCESS; /* Otherwise fail. */ return MR_PERM; }