1 /*
2 * Quota routines for the CUPS scheduler.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright 2007-2011 by Apple Inc.
6 * Copyright 1997-2007 by Easy Software Products.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
9 */
10
11 /*
12 * Include necessary headers...
13 */
14
15 #include "cupsd.h"
16
17
18 /*
19 * Local functions...
20 */
21
22 static cupsd_quota_t *add_quota(cupsd_printer_t *p, const char *username);
23 static int compare_quotas(const cupsd_quota_t *q1,
24 const cupsd_quota_t *q2);
25
26
27 /*
28 * 'cupsdFindQuota()' - Find a quota record.
29 */
30
31 cupsd_quota_t * /* O - Quota data */
cupsdFindQuota(cupsd_printer_t * p,const char * username)32 cupsdFindQuota(
33 cupsd_printer_t *p, /* I - Printer */
34 const char *username) /* I - User */
35 {
36 cupsd_quota_t *q, /* Quota data pointer */
37 match; /* Search data */
38 char *ptr; /* Pointer into username */
39
40
41 if (!p || !username)
42 return (NULL);
43
44 strlcpy(match.username, username, sizeof(match.username));
45 if ((ptr = strchr(match.username, '@')) != NULL)
46 *ptr = '\0'; /* Strip @domain/@KDC */
47
48 if ((q = (cupsd_quota_t *)cupsArrayFind(p->quotas, &match)) != NULL)
49 return (q);
50 else
51 return (add_quota(p, username));
52 }
53
54
55 /*
56 * 'cupsdFreeQuotas()' - Free quotas for a printer.
57 */
58
59 void
cupsdFreeQuotas(cupsd_printer_t * p)60 cupsdFreeQuotas(cupsd_printer_t *p) /* I - Printer */
61 {
62 cupsd_quota_t *q; /* Current quota record */
63
64
65 if (!p)
66 return;
67
68 for (q = (cupsd_quota_t *)cupsArrayFirst(p->quotas);
69 q;
70 q = (cupsd_quota_t *)cupsArrayNext(p->quotas))
71 free(q);
72
73 cupsArrayDelete(p->quotas);
74
75 p->quotas = NULL;
76 }
77
78
79 /*
80 * 'cupsdUpdateQuota()' - Update quota data for the specified printer and user.
81 */
82
83 cupsd_quota_t * /* O - Quota data */
cupsdUpdateQuota(cupsd_printer_t * p,const char * username,int pages,int k)84 cupsdUpdateQuota(
85 cupsd_printer_t *p, /* I - Printer */
86 const char *username, /* I - User */
87 int pages, /* I - Number of pages */
88 int k) /* I - Number of kilobytes */
89 {
90 cupsd_quota_t *q; /* Quota data */
91 cupsd_job_t *job; /* Current job */
92 time_t curtime; /* Current time */
93 ipp_attribute_t *attr; /* Job attribute */
94
95
96 if (!p || !username)
97 return (NULL);
98
99 if (!p->k_limit && !p->page_limit)
100 return (NULL);
101
102 if ((q = cupsdFindQuota(p, username)) == NULL)
103 return (NULL);
104
105 cupsdLogMessage(CUPSD_LOG_DEBUG,
106 "cupsdUpdateQuota: p=%s username=%s pages=%d k=%d",
107 p->name, username, pages, k);
108
109 curtime = time(NULL);
110
111 if (curtime < q->next_update)
112 {
113 q->page_count += pages;
114 q->k_count += k;
115
116 return (q);
117 }
118
119 if (p->quota_period)
120 curtime -= p->quota_period;
121 else
122 curtime = 0;
123
124 q->next_update = 0;
125 q->page_count = 0;
126 q->k_count = 0;
127
128 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
129 job;
130 job = (cupsd_job_t *)cupsArrayNext(Jobs))
131 {
132 /*
133 * We only care about the current printer/class and user...
134 */
135
136 if (_cups_strcasecmp(job->dest, p->name) != 0 ||
137 _cups_strcasecmp(job->username, q->username) != 0)
138 continue;
139
140 /*
141 * Make sure attributes are loaded; we always call cupsdLoadJob() to ensure
142 * the access_time member is updated so the job isn't unloaded right away...
143 */
144
145 if (!cupsdLoadJob(job))
146 continue;
147
148 if ((attr = ippFindAttribute(job->attrs, "time-at-completion",
149 IPP_TAG_INTEGER)) == NULL)
150 if ((attr = ippFindAttribute(job->attrs, "time-at-processing",
151 IPP_TAG_INTEGER)) == NULL)
152 attr = ippFindAttribute(job->attrs, "time-at-creation",
153 IPP_TAG_INTEGER);
154
155 if (attr->values[0].integer < curtime)
156 {
157 /*
158 * This job is too old to count towards the quota, ignore it...
159 */
160
161 if (JobAutoPurge && !job->printer && job->state_value > IPP_JOB_STOPPED)
162 cupsdDeleteJob(job, CUPSD_JOB_PURGE);
163
164 continue;
165 }
166
167 if (q->next_update == 0)
168 q->next_update = attr->values[0].integer + p->quota_period;
169
170 if ((attr = ippFindAttribute(job->attrs, "job-media-sheets-completed",
171 IPP_TAG_INTEGER)) != NULL)
172 q->page_count += attr->values[0].integer;
173
174 if ((attr = ippFindAttribute(job->attrs, "job-k-octets",
175 IPP_TAG_INTEGER)) != NULL)
176 q->k_count += attr->values[0].integer;
177 }
178
179 return (q);
180 }
181
182
183 /*
184 * 'add_quota()' - Add a quota record for this printer and user.
185 */
186
187 static cupsd_quota_t * /* O - Quota data */
add_quota(cupsd_printer_t * p,const char * username)188 add_quota(cupsd_printer_t *p, /* I - Printer */
189 const char *username) /* I - User */
190 {
191 cupsd_quota_t *q; /* New quota data */
192 char *ptr; /* Pointer into username */
193
194
195 if (!p || !username)
196 return (NULL);
197
198 if (!p->quotas)
199 p->quotas = cupsArrayNew((cups_array_func_t)compare_quotas, NULL);
200
201 if (!p->quotas)
202 return (NULL);
203
204 if ((q = calloc(1, sizeof(cupsd_quota_t))) == NULL)
205 return (NULL);
206
207 strlcpy(q->username, username, sizeof(q->username));
208 if ((ptr = strchr(q->username, '@')) != NULL)
209 *ptr = '\0'; /* Strip @domain/@KDC */
210
211 cupsArrayAdd(p->quotas, q);
212
213 return (q);
214 }
215
216
217 /*
218 * 'compare_quotas()' - Compare two quota records...
219 */
220
221 static int /* O - Result of comparison */
compare_quotas(const cupsd_quota_t * q1,const cupsd_quota_t * q2)222 compare_quotas(const cupsd_quota_t *q1, /* I - First quota record */
223 const cupsd_quota_t *q2) /* I - Second quota record */
224 {
225 return (_cups_strcasecmp(q1->username, q2->username));
226 }
227