From 63259c34e9c29e18b3b5e258268d2108f70e1e44 Mon Sep 17 00:00:00 2001 From: rousskov <> Date: Sun, 22 Feb 1998 14:45:13 +0000 Subject: [PATCH] - Added "basic" authentication scheme for Cache Manager interface. - Added ERR_CACHE_MGR_ACCESS_DENIED page to notify of authentication failures when accessing Cache Manager. - Added "-v" (Verbose) and "-H" (extra Headers) options to client.c. --- ChangeLog | 18 +++++ errors/English/ERR_CACHE_MGR_ACCESS_DENIED | 31 ++++++++ src/HttpHeader.cc | 18 ++++- src/HttpReply.cc | 21 +++++- src/cache_manager.cc | 86 +++++++++++++++++++--- src/client.cc | 26 ++++++- src/enums.h | 1 + src/ftp.cc | 22 +++--- src/mime.cc | 22 +++++- src/protos.h | 4 +- 10 files changed, 221 insertions(+), 28 deletions(-) create mode 100644 errors/English/ERR_CACHE_MGR_ACCESS_DENIED diff --git a/ChangeLog b/ChangeLog index 11392b3a43..39d19ea56f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,23 @@ Future/Current Changes to squid-1.2.beta16 (Feb 23, 1998): + - Added "basic" authentication scheme for Cache Manager interface. + Tested with Netscape 3.01. When a password protected function is + accessed, Squid sends an HTTP_UNAUTHORIZED reply, and the browser + prompts the user for "user name" and "password" for the specified + action. The user name is currently used for logging purposes only. + The password is verified against must be an appropriate + "cachemgr_passwd" entry from squid.conf. Note that Netscape will + cache your authentication info and will reuse it for future + accesses. Caching is done on per-realm (per-action) basis, but + Netscape may try to use information from other realms when new realm + is accessed. The old interface (appending @password to the url) + is still supported but discouraged. + + - Added ERR_CACHE_MGR_ACCESS_DENIED page to notify of authentication + failures when accessing Cache Manager. + + - Added "-v" (Verbose) and "-H" (extra Headers) options to client.c. + - Added simple context-based debugging to debug.c. Currently, the context is defined as a constant string. Context reporting is triggered by debug() calls. Context debugging routines print diff --git a/errors/English/ERR_CACHE_MGR_ACCESS_DENIED b/errors/English/ERR_CACHE_MGR_ACCESS_DENIED new file mode 100644 index 0000000000..0e121ea605 --- /dev/null +++ b/errors/English/ERR_CACHE_MGR_ACCESS_DENIED @@ -0,0 +1,31 @@ + +ERROR: Cache Manager Access Denied + + +

ERROR

+

Cache Manager Access Denied

+
+

+While trying to retrieve the URL: +%U +

+The following error was encountered: +

+

+ +

Sorry, you are not currently allowed to request: +

    %U
+from this cache manager until you have authenticated yourself. +

+ +

You need to use Netscape version 2.0 or greater, or Microsoft Internet +Explorer 3.0, or an HTTP/1.1 compliant browser for this to work. Please +contact the cache administrator if you have +difficulties authenticating yourself or, if you are the +administrator, read Squid documentation on cache manager interface and check +cache log for more detailed error messages.

diff --git a/src/HttpHeader.cc b/src/HttpHeader.cc index 5c1fdd82d1..e14509d41d 100644 --- a/src/HttpHeader.cc +++ b/src/HttpHeader.cc @@ -1,5 +1,5 @@ /* - * $Id: HttpHeader.cc,v 1.3 1998/02/21 18:46:32 rousskov Exp $ + * $Id: HttpHeader.cc,v 1.4 1998/02/22 07:45:15 rousskov Exp $ * * DEBUG: section 55 HTTP Header * AUTHOR: Alex Rousskov @@ -784,6 +784,17 @@ httpHeaderSetStr(HttpHeader *hdr, http_hdr_type id, const char *str) httpHeaderSet(hdr, id, value); } +void +httpHeaderSetAuth(HttpHeader *hdr, const char *authScheme, const char *realm) +{ + MemBuf mb; + assert(hdr && authScheme && realm); + memBufDefInit(&mb); + memBufPrintf(&mb, "%s realm=\"%s\"", authScheme, realm); + httpHeaderSetStr(hdr, HDR_WWW_AUTHENTICATE, mb.buf); + memBufClean(&mb); +} + /* add extension header (these fields are not parsed/analyzed/joined, etc.) */ void httpHeaderAddExt(HttpHeader *hdr, const char *name, const char* value) @@ -955,6 +966,11 @@ httpHeaderEntryClean(HttpHeaderEntry *e) { httpSccDestroy(e->field.v_pscc); break; case ftPExtField: + /* tmp check to track a bug @?@ @?@ */ + if (e->field.v_int == 1) { + debug(55,0) ("BUG: attempt to free an invalid HeaderExtField (%p). Ignored.\n", + e->field.v_pefield); + } else if (e->field.v_pefield) httpHeaderExtFieldDestroy(e->field.v_pefield); break; diff --git a/src/HttpReply.cc b/src/HttpReply.cc index 4f8899bfe4..70ac733f64 100644 --- a/src/HttpReply.cc +++ b/src/HttpReply.cc @@ -1,5 +1,5 @@ /* - * $Id: HttpReply.cc,v 1.3 1998/02/21 18:46:33 rousskov Exp $ + * $Id: HttpReply.cc,v 1.4 1998/02/22 07:45:16 rousskov Exp $ * * DEBUG: section 58 HTTP Reply (Response) * AUTHOR: Alex Rousskov @@ -37,6 +37,7 @@ /* local constants */ /* local routines */ +static void httpReplyDoDestroy(HttpReply *rep); static int httpReplyParseStep(HttpReply *rep, const char *parse_start, int atEnd); static int httpReplyParseError(HttpReply *rep); static int httpReplyIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end); @@ -78,7 +79,7 @@ httpReplyDestroy(HttpReply *rep) assert(rep); tmp_debug(here) ("destroying rep: %p\n", rep); httpReplyClean(rep); - memFree(MEM_HTTPREPLY, rep); + httpReplyDoDestroy(rep); } void @@ -88,6 +89,17 @@ httpReplyReset(HttpReply *rep) httpReplyInit(rep); } +/* absorb: copy the contents of a new reply to the old one, destroy new one */ +void +httpReplyAbsorb(HttpReply *rep, HttpReply *new_rep) +{ + assert(rep && new_rep); + httpReplyClean(rep); + *rep = *new_rep; + /* cannot use Clean() on new reply now! */ + httpReplyDoDestroy(new_rep); +} + /* parses a buffer that may not be 0-terminated */ int httpReplyParse(HttpReply *rep, const char *buf) @@ -298,6 +310,11 @@ httpReplyHasScc(const HttpReply *rep, http_scc_type type) /* internal routines */ +/* internal function used by Destroy and Absorb */ +static void +httpReplyDoDestroy(HttpReply *rep) { + memFree(MEM_HTTPREPLY, rep); +} /* * parses a 0-terminating buffer into HttpReply. diff --git a/src/cache_manager.cc b/src/cache_manager.cc index 9f9c3e48cb..45bc7a7e30 100644 --- a/src/cache_manager.cc +++ b/src/cache_manager.cc @@ -1,6 +1,6 @@ /* - * $Id: cache_manager.cc,v 1.3 1998/02/21 00:56:50 rousskov Exp $ + * $Id: cache_manager.cc,v 1.4 1998/02/22 07:45:17 rousskov Exp $ * * DEBUG: section 16 Cache Manager Objects * AUTHOR: Duane Wessels @@ -36,6 +36,7 @@ typedef struct { StoreEntry *entry; char *action; + char *user_name; char *passwd; } cachemgrStateData; @@ -48,7 +49,12 @@ typedef struct _action_table { } action_table; static action_table * cachemgrFindAction(const char *action); +#if 0 static cachemgrStateData *cachemgrParse(const char *url); +#else +static cachemgrStateData *cachemgrParseUrl(const char *url); +#endif +static void cachemgrParseHeaders(cachemgrStateData *mgr, const request_t *request); static int cachemgrCheckPassword(cachemgrStateData *); static void cachemgrStateFree(cachemgrStateData *mgr); static char *cachemgrPasswdGet(cachemgr_passwd *, const char *); @@ -88,7 +94,7 @@ cachemgrFindAction(const char *action) } static cachemgrStateData * -cachemgrParse(const char *url) +cachemgrParseUrl(const char *url) { int t; LOCAL_ARRAY(char, host, MAX_URL); @@ -100,15 +106,47 @@ cachemgrParse(const char *url) if (t < 2) { xstrncpy(request, "menu", MAX_URL); } else if ((a = cachemgrFindAction(request)) == NULL) { - debug(16, 0) ("cachemgrParse: action '%s' not found\n", request); + debug(16, 0) ("cachemgrParseUrl: action '%s' not found\n", request); return NULL; } + /* set absent entries to NULL so we can test if they are present later */ mgr = xcalloc(1, sizeof(cachemgrStateData)); - mgr->passwd = xstrdup(t == 3 ? password : "nopassword"); + mgr->user_name = NULL; + mgr->passwd = t == 3 ? xstrdup(password) : NULL; mgr->action = xstrdup(request); return mgr; } +static void +cachemgrParseHeaders(cachemgrStateData *mgr, const request_t *request) +{ + const char *basic_cookie; /* base 64 _decoded_ user:passwd pair */ + const char *authField; + const char *passwd_del; + assert(mgr && request); + /* this parsing will go away when hdrs are added to request_t @?@ */ + basic_cookie = mime_get_auth(request->headers, "Basic", &authField); + debug(16,9) ("cachemgrParseHeaders: got auth: '%s'\n", authField ? authField:""); + if (!authField) + return; + if (!basic_cookie) { + debug(16, 1) ("cachemgrParseHeaders: unknown auth format in '%s'\n", authField); + return; + } + if (!(passwd_del = strchr(basic_cookie, ':'))) { + debug(16, 1) ("cachemgrParseHeaders: unknown basic_cookie '%s' format in '%s'\n", basic_cookie, authField); + return; + } + /* found user:password pair, reset old values */ + safe_free(mgr->user_name); + safe_free(mgr->passwd); + mgr->user_name = xstrdup(basic_cookie); + mgr->user_name[passwd_del - basic_cookie] = '\0'; + mgr->passwd = xstrdup(passwd_del+1); + /* warning: this prints decoded password which maybe not what you want to do @?@ @?@ */ + debug(16,9) ("cachemgrParseHeaders: got user: '%s' passwd: '%s'\n", mgr->user_name, mgr->passwd); +} + /* * return 0 if mgr->password is good */ @@ -124,6 +162,8 @@ cachemgrCheckPassword(cachemgrStateData * mgr) return 1; if (strcmp(pwd, "none") == 0) return 0; + if (!mgr->passwd) + return 1; return strcmp(pwd, mgr->passwd); } @@ -131,21 +171,19 @@ static void cachemgrStateFree(cachemgrStateData *mgr) { safe_free(mgr->action); + safe_free(mgr->user_name); safe_free(mgr->passwd); xfree(mgr); } void -cachemgrStart(int fd, StoreEntry * entry) +cachemgrStart(int fd, request_t *request, StoreEntry * entry) { cachemgrStateData *mgr = NULL; ErrorState *err = NULL; -#if 0 - char *hdr; -#endif action_table *a; debug(16, 3) ("objectcacheStart: '%s'\n", storeUrl(entry)); - if ((mgr = cachemgrParse(storeUrl(entry))) == NULL) { + if ((mgr = cachemgrParseUrl(storeUrl(entry))) == NULL) { err = errorCon(ERR_INVALID_URL, HTTP_NOT_FOUND); err->url = xstrdup(storeUrl(entry)); errorAppendEntry(entry, err); @@ -154,14 +192,42 @@ cachemgrStart(int fd, StoreEntry * entry) } mgr->entry = entry; entry->expires = squid_curtime; - debug(16, 1) ("CACHEMGR: %s requesting '%s'\n", + debug(16, 5) ("CACHEMGR: %s requesting '%s'\n", fd_table[fd].ipaddr, mgr->action); + /* get additional info from request headers */ + cachemgrParseHeaders(mgr, request); + debug(16, 1) ("CACHEMGR: %s (user: %s) requesting '%s'\n", + fd_table[fd].ipaddr, mgr->user_name ? mgr->user_name : "", + mgr->action); /* Check password */ if (cachemgrCheckPassword(mgr) != 0) { +#if 0 /* old response, we ask for authentication now */ cachemgrStateFree(mgr); debug(16, 1) ("WARNING: Incorrect Cachemgr Password!\n"); err = errorCon(ERR_INVALID_URL, HTTP_NOT_FOUND); errorAppendEntry(entry, err); +#else + /* build error message */ + ErrorState *err = errorCon(ERR_CACHE_MGR_ACCESS_DENIED, HTTP_UNAUTHORIZED); + HttpReply *rep; + /* warn if user specified incorrect password */ + if (mgr->passwd) + debug(16, 1) ("WARNING: CACHEMGR: Incorrect Password (user: %s, action: %s)!\n", + mgr->user_name ? mgr->user_name : "", mgr->action); + else + debug(16, 3) ("CACHEMGR: requesting authentication for action: '%s'.\n", + mgr->action); + err->request = requestLink(request); + rep = errorBuildReply(err); + errorStateFree(err); + /* add Authenticate header, use 'action' as a realm because password depends on action */ + httpHeaderSetAuth(&rep->hdr, "Basic", mgr->action); + /* move info to the mem_obj->reply */ + httpReplyAbsorb(entry->mem_obj->reply, rep); + /* store the reply */ + httpReplySwapOut(entry->mem_obj->reply, entry); + cachemgrStateFree(mgr); +#endif entry->expires = squid_curtime; storeComplete(entry); return; diff --git a/src/client.cc b/src/client.cc index 0b604a849b..c8782a29a2 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1,7 +1,7 @@ /* - * $Id: client.cc,v 1.55 1998/02/11 03:14:37 wessels Exp $ + * $Id: client.cc,v 1.56 1998/02/22 07:45:18 rousskov Exp $ * * DEBUG: section 0 WWW Client * AUTHOR: Harvest Derived @@ -124,19 +124,21 @@ static void usage(const char *progname) { fprintf(stderr, - "Usage: %s [-ars] [-i IMS] [-h host] [-p port] [-m method] [-t count] [-I ping-interval] url\n" + "Usage: %s [-arsv] [-i IMS] [-h host] [-p port] [-m method] [-t count] [-I ping-interval] [-H 'strings'] url\n" "Options:\n" " -P file PUT request.\n" " -a Do NOT include Accept: header.\n" " -r Force cache to reload URL.\n" " -s Silent. Do not print data to stdout.\n" + " -v Verbose. Print outgoing message to stderr.\n" " -i IMS If-Modified-Since time (in Epoch seconds).\n" " -h host Retrieve URL from cache on hostname. Default is localhost.\n" " -p port Port number of cache. Default is %d.\n" " -m method Request method, default is GET.\n" " -t count Trace count cache-hops\n" " -g count Ping mode, \"count\" iterations (0 to loop until interrupted).\n" - " -I interval Ping interval in seconds (default 1 second).\n", + " -I interval Ping interval in seconds (default 1 second).\n" + " -H 'string' Extra headers to send. Use quotes to protect new lines.\n", progname, CACHE_HTTP_PORT); exit(1); } @@ -151,7 +153,9 @@ main(int argc, char *argv[]) int keep_alive = 0; int opt_noaccept = 0; int opt_put = 0; + int opt_verbose = 0; char url[BUFSIZ], msg[BUFSIZ], buf[BUFSIZ], hostname[BUFSIZ]; + char extra_hdrs[BUFSIZ]; const char *method = "GET"; extern char *optarg; time_t ims = 0; @@ -163,6 +167,7 @@ main(int argc, char *argv[]) /* set the defaults */ strcpy(hostname, "localhost"); + extra_hdrs[0] = '\0'; port = CACHE_HTTP_PORT; to_stdout = 1; reload = 0; @@ -176,7 +181,7 @@ main(int argc, char *argv[]) strcpy(url, argv[argc - 1]); if (url[0] == '-') usage(argv[0]); - while ((c = getopt(argc, argv, "ah:P:i:km:p:rst:g:p:I:?")) != -1) + while ((c = getopt(argc, argv, "ah:P:i:km:p:rsvt:g:p:I:H:?")) != -1) switch (c) { case 'a': opt_noaccept = 1; @@ -221,6 +226,15 @@ main(int argc, char *argv[]) if ((ping_int = atoi(optarg) * 1000) <= 0) usage(argv[0]); break; + case 'H': + if (strlen(optarg)) { + strncpy(extra_hdrs, optarg, sizeof(extra_hdrs)); + } + break; + case 'v': + /* undocumented: may increase verb-level by giving more -v's */ + opt_verbose++; + break; case '?': /* usage */ default: usage(argv[0]); @@ -272,9 +286,13 @@ main(int argc, char *argv[]) snprintf(buf, BUFSIZ, "Connection: Keep-Alive\r\n"); strcat(msg, buf); } + strcat(msg, extra_hdrs); snprintf(buf, BUFSIZ, "\r\n"); strcat(msg, buf); + if (opt_verbose) + fprintf(stderr, "headers: '%s'\n", msg); + if (ping) { #if HAVE_SIGACTION struct sigaction sa, osa; diff --git a/src/enums.h b/src/enums.h index 88128f3012..ccac5f7ff6 100644 --- a/src/enums.h +++ b/src/enums.h @@ -44,6 +44,7 @@ typedef enum { ERR_URN_RESOLVE, ERR_ACCESS_DENIED, ERR_CACHE_ACCESS_DENIED, + ERR_CACHE_MGR_ACCESS_DENIED, ERR_MAX } err_type; diff --git a/src/ftp.cc b/src/ftp.cc index bac074a529..56b63ffaa0 100644 --- a/src/ftp.cc +++ b/src/ftp.cc @@ -1,6 +1,6 @@ /* - * $Id: ftp.cc,v 1.195 1998/02/21 00:56:55 rousskov Exp $ + * $Id: ftp.cc,v 1.196 1998/02/22 07:45:19 rousskov Exp $ * * DEBUG: section 9 File Transfer Protocol (FTP) * AUTHOR: Harvest Derived @@ -135,7 +135,9 @@ static PF ftpStateFree; static PF ftpTimeout; static PF ftpReadControlReply; static CWCB ftpWriteCommandCallback; +#if 0 static char *ftpGetBasicAuth(const char *); +#endif static void ftpLoginParser(const char *, FtpStateData *); static wordlist *ftpParseControlReply(char *buf, size_t len, int *code); static void ftpAppendSuccessHeader(FtpStateData * ftpState); @@ -814,6 +816,7 @@ ftpDataRead(int fd, void *data) } } +#if 0 /* moved to mime.c because cachemgr needs it too */ static char * ftpGetBasicAuth(const char *req_hdr) { @@ -831,6 +834,7 @@ ftpGetBasicAuth(const char *req_hdr) return NULL; return base64_decode(t); } +#endif /* * ftpCheckAuth @@ -842,7 +846,7 @@ static int ftpCheckAuth(FtpStateData * ftpState, char *req_hdr) { char *orig_user; - char *auth; + const char *auth; ftpLoginParser(ftpState->request->login, ftpState); if (ftpState->user[0] && ftpState->password[0]) return 1; /* name and passwd both in URL */ @@ -851,7 +855,7 @@ ftpCheckAuth(FtpStateData * ftpState, char *req_hdr) if (ftpState->password[0]) return 1; /* passwd with no name? */ /* URL has name, but no passwd */ - if ((auth = ftpGetBasicAuth(req_hdr)) == NULL) + if (!(auth = mime_get_auth(req_hdr, "Basic", NULL))) return 0; /* need auth header */ orig_user = xstrdup(ftpState->user); ftpLoginParser(auth, ftpState); @@ -950,6 +954,7 @@ ftpStart(request_t * request, StoreEntry * entry) storeAppend(entry, response, strlen(response)); httpParseReplyHeaders(response, entry->mem_obj->reply); #else + /* create reply */ { HttpReply *reply = entry->mem_obj->reply; assert(reply); @@ -2063,18 +2068,17 @@ ftpAuthRequired(const request_t * request, const char *realm) #endif static void -ftpAuthRequired(HttpReply *reply, request_t *request, const char *realm) +ftpAuthRequired(HttpReply *old_reply, request_t *request, const char *realm) { ErrorState *err = errorCon(ERR_ACCESS_DENIED, HTTP_UNAUTHORIZED); HttpReply *rep; err->request = requestLink(request); rep = errorBuildReply(err); - /* add Authenticate header */ - httpHeaderSetStr(&rep->hdr, HDR_WWW_AUTHENTICATE, realm); errorStateFree(err); - /* substitute, should be OK because we clean it @?@ */ - httpReplyClean(reply); - *reply = *rep; /* @?@ warning is generated due to hdr_sz being constant */ + /* add Authenticate header */ + httpHeaderSetAuth(&rep->hdr, "Basic", realm); + /* move new reply to the old one */ + httpReplyAbsorb(old_reply, rep); } char * diff --git a/src/mime.cc b/src/mime.cc index 6079ff804d..42339891b9 100644 --- a/src/mime.cc +++ b/src/mime.cc @@ -1,6 +1,6 @@ /* - * $Id: mime.cc,v 1.50 1998/02/21 00:56:59 rousskov Exp $ + * $Id: mime.cc,v 1.51 1998/02/22 07:45:20 rousskov Exp $ * * DEBUG: section 25 MIME Parsing * AUTHOR: Harvest Derived @@ -236,6 +236,26 @@ mk_mime_hdr(char *result, const char *type, int size, time_t ttl, time_t lmt) } +const char * +mime_get_auth(const char *hdr, const char *auth_scheme, const char **auth_field) +{ + char *auth_hdr; + char *t; + if (auth_field) *auth_field = NULL; + if (hdr == NULL) + return NULL; + if ((auth_hdr = mime_get_header(hdr, "Authorization")) == NULL) + return NULL; + if (auth_field) *auth_field = auth_hdr; + if ((t = strtok(auth_hdr, " \t")) == NULL) + return NULL; + if (strcasecmp(t, auth_scheme) != 0) + return NULL; + if ((t = strtok(NULL, " \t")) == NULL) + return NULL; + return base64_decode(t); +} + char * mimeGetIcon(const char *fn) { diff --git a/src/protos.h b/src/protos.h index e29ad56e44..5d25c8b762 100644 --- a/src/protos.h +++ b/src/protos.h @@ -293,6 +293,8 @@ extern int ipcacheUnregister(const char *name, void *data); extern char *mime_get_header(const char *mime, const char *header); extern char *mime_headers_end(const char *mime); extern int mk_mime_hdr(char *result, const char *type, int size, time_t ttl, time_t lmt); +extern const char *mime_get_auth(const char *hdr, const char *auth_scheme, const char **auth_field); + extern void mimeInit(char *filename); extern char *mimeGetContentEncoding(const char *fn); extern char *mimeGetContentType(const char *fn); @@ -340,7 +342,7 @@ extern int netdbHostHops(const char *host); extern int netdbHostRtt(const char *host); extern void netdbUpdatePeer(request_t *, peer * e, int rtt, int hops); -extern void cachemgrStart(int fd, StoreEntry *); +extern void cachemgrStart(int fd, request_t *request, StoreEntry * entry); extern void cachemgrRegister(const char *, const char *, OBJH *, int); extern void cachemgrInit(void); -- 2.47.3